Flutter BLoC/Cubit Tutorial with REST API and Repository Pattern

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
in this video you will learn everything about the block plus repository pattern by building a to-do app i have explained all the important features of the flutter block library and by the end of this video you will become comfortable enough to start using the block pattern in your own flutter apps the flutter block library added something called as a qubit in its version 6 release a qubit is basically an extension over the block and it makes the implementation of the block pattern faster and simpler so in this entire video we will be using qubits to implement the block pattern now this application is also going to require some back end support but since this video is only about the front end we will be writing zero back-end code so how will we get the back-end there is a package named json server which allows us to set up temporary rest servers we will install this json server with node.js and if you do not know node.js then you don't have anything to be disappointed about because the only node thing that we'll be doing in this entire video will be the installation of node so the installation of node and the json server will not take more than one minute so you don't have anything to worry about and this entire project is available in a github repository i have it linked in the description now before we start writing the code let's take a look at what we are going to create when the application starts you are going to be presented with this empty screen and you can add a to-do by clicking on this plus icon here on this new screen you can enter a to-do message and when you click on the add to do button then you can see this circular progress indicator and after that the to-do will get added on the screen let me add another to-do you can see this red indicator this represents that the to-do has not been completed and if we want to complete a to-do then we can just swipe the to-do after that you can see it has turned green and if you want to undo it then we can again swipe it so green means that the to-do has been completed and red means that we have to do this thing now if we click on a to-do then it will take us to this screen where we will be able to update or delete it so if i click on this delete icon then that will delete the to do and if i want to update it then i'll click on the update to do button all the features of this application have been implemented with qubits for every single feature of your app you have to use a qubit that's the purpose of a qubit it is going to replace all the logic of your application so even if it is a very small feature then it has to be implemented with a qubit because that's what the block pattern suggests and it also makes sense to use a single pattern for the entire application let's start by setting up the json server because without the json server you will not be able to proceed with this tutorial and to install json server we first need node.js installed in our computer so if you don't have node.js then head over to nodejs.org and download the latest stable version of node once it is downloaded install it the installation process is very simple you can just go ahead with the flow and it will get installed once you have node installed open up command prompt or the terminal and with npm we will install the json server and we will do this globally npm is a package manager for node and it gets automatically installed with node.js and we are installing this globally because then we can access the json server from any directory in the terminal hit enter and it will get installed and now to start the json server we have to run the command json server double dash watch db.json this db.json is a file that we will create and all of our data will get stored into this db.json file so open vs code create a new file save this file with the name db.json in a directory where you will be able to easily access it doing this is not enough we also have to give it a start and by that i mean we have to create an object and since in this tutorial we are going to create a to-do's application i am going to create a to-do's key inside this object and this key will store an array of to-do's now creating this object tells the json server that we have a to-do's resource that should be treated as a rest api endpoint so for example if we want to access any to-do with an id then we can just access it on the localhost with the to-do's endpoint and let's say if we had named it something else let's say profile then we could have accessed it with the profile endpoint but in our case it is going to be todos so after setting up this db.json you can go ahead and run it but i need to navigate to the folder where this db.json is located i'm going to run json server double dash watch db.json now if i hit enter then this json server will be accessible on the local host of this device and we will be able to run our application on the emulator and it will work perfectly but if you want to run the application on a real device then running this command will not work and the real device will not be able to access the json server so to make this json server available to a real device we have this double dash host attribute where we can provide the ip address on which we want to run this json server this ip address is going to be the ipv4 address of your computer and any device which is connected to the same network as your computer will be able to access this json server i will show you how to run this application on a real device at the end of this video until then we can run this on the emulator and make the application so for now i'm just going to run it on the local host so i don't need this double dash host and you can see my resource is available on this local host i accidentally did that okay let me just rerun it yeah so if i go to localhost on port 3000 to do's then you can see we have an empty array for now now i will just quickly demonstrate that our endpoint is working and we will be able to add data into it using postman you don't have to follow this step i'll just show you that it is working so i have set up a post request on the to-do's end point and this post request is sending a to-do with this value and a completed key with the value false let me just change this completed to is completed because that makes more sense and i will just send this post request and now if we go to vs code you can see this has been added by the json server and you can also see that this object has been automatically given a unique id so that we can refer to this particular object when we are making our requests and just like we made the post request we can also make put patch delete all kinds of requests on this json server and it will automatically handle those requests for us okay so now that we have our json server setup we can go ahead and write the flutter application to create a flutter project run the command flutter create and the name of your project i'm going to name this one to do app and now open the project in vs code on windows you can run the command code to do underscore app now here i want you to have a few vs code extensions installed because without it it will be very difficult to work with the block library this is a very important library if you do not have this installed then it is going to be very difficult to code flutter block other than that you should also have dart installed and you should also have pub spec assist installed this helps us add libraries very easily into the pub spec file and once you have that let's create the folder structure we are going to have a folder that will be named presentation and this folder will contain all the ui files like any widgets screens and all of that and then we should have one folder for data this is going to store repositories services models and all of that and then we will have another folder for qubit it is important that you separate these three things the logic the data and the presentation of your app otherwise it will get all clustered now after this go to main.dart and just delete everything that you have here import material dot dot and then create the main function run app and let's create a stateless widget i'm going to call this widget to do app and in the run app pass the to-do app widget this is going to return a material app and you can see there is an error in the test folder so go there and replace this my app with to-do app let me also launch the emulator we have an error because this test does not make any sense with what we have here so let's just forget about this and go to main dot dot and learn this one i accidentally ran the test i was supposed to run this we again have an error because this material app does not have anything to display in it it does not have any home okay so let's first create all the screens that our app is going to have and it is going to be mainly three screens the first will be the to-do screen which will show all the to-do's that we have added other one will be a screen to add the to-do and one will be to edit a to-do so inside the presentation folder create another folder and name this screens and inside the screens create a dart file name this to-do's screen dot dot import the material dot dot package and then create a stateless widget named to-do's screen make this widget return a scaffold let's also give this scaffold a body which will be a center text and this text will say to-do screen and for now just go to the main dot dot and set the home to the to-do screen because i don't want to see this error and it is still there why is it still there yeah i just had to refresh it and i also want to get rid of this debug banner so i'm going to set debug show checked mode banner to false all right we are going to have two more screens so let's create them as well inside the screens folder create a new file name this one add underscore to do screen dot dot not dart dart import the material dot dot package stateless widget name this one add to do screen this as well will return a scaffold with a center text it will say add to do create another screen named edit underscore to do underscore screen dot dot import material. stateless widget edit to do screen and a text that will say edit to do okay now we have the screens ready let's set up a way to navigate between them i'm going to use generated routes to go from one screen to another and if you do not know what generated routes are or how routing works in flutter then do check out my other video i'll have it linked in the description i have explained routing in flutter there it's like a three minute long video you must watch it if you do not know what routing or how routing works in flutter so inside the presentation create a new file name this file router.dart and this file will have a class named router and inside this there will be a function that will return a route object i'm going to name this function generator out that will accept a route settings object as a parameter and then i'm going to have a switch case where i will be switching the name of the route again if you do not know about routing then you must watch my other video now the first route that we are going to check for is the home route this is the default route and if this route is hit then we are going to return a material page route widget implement the builder function here the first parameter in this function is a context but we are not going to use it so i just replace it with an underscore and the widget that should be shown in the home route is the to-do's screen next case is going to be edit to do just copy this line paste it below and just change the tools screen to edit to do and the last case that can happen is add to do paste that thing here as well and change to do's screen to add to do screen and by default we will return null now we have this function ready let's give this to the material app so that it can manage the route changes so inside the to-do app create a final object of of what yes of the router class and press ctrl dot create constructor for final fields and then here in the run app function we are passing the to-do app widget here pass this router as a dependency so router okay let me rename this router to something else because i guess there is another flutter thing with this name so go to router dot dot and change the name to maybe app router now let's go back to main dot dot and change router with app router and here as well and now in the material app remove this home argument and instead add the on generator out here we will have to pass the generate route function and the material app will call this function whenever we push a screen with the navigator so pass the router dot generate route function make sure you don't pass the curly braces only the name and we have an error because we have to restart the app now go to the to-do screen widget and this screen is going to have an app bar the title of the app bar will be to do's and in the right end of this app bar we are going to add an icon and when that icon is pressed we will open the add to do screen so to add actions we have this actions parameter and here we are going to add an icon icons add we are going to give this icon a padding so that it is not at the edge of the screen a padding of 10 would be fine and now we need to detect any taps on this icon and when it is tapped we will open the add to do screen so wrap this padding with an ink well this equal has an on tap parameter and when this is tapped we will push the add to do screen with the navigator what is the name of our add to do route let's go and see in the router dot dot it is slash add to do i am going to extract these route names in a variable so that it is easier to work with them so inside the lift folder create a new folder and this folder is going to hold all the constants and inside the constants create a strings dot dot this will hold all the string constants create a variable named add to do route create another variable named edit to do route now go to the router and replace this edit to do with edit to do route variable and replace this one with add to do route now go to the to do screen dot dot by the way i'm pressing control plus p to open this file search window and now in this push named function the route name is going to be add to do route and if we press the plus icon then you can see we are being taken to the add to do screen now we are going to create a qubit for our to-do screen which will fetch the data that is going to be displayed on the to-do screen and it will perform a few more functions but before that let's understand what is a qubit a qubit manages all the logic of your app let's understand the statement with this simple scenario that i have set up we have an application that takes in a user's phone number and sends an otp if the send otp button is clicked and if the phone number is not five digits long then it will show an error message and it will also clear the input box this colorful shape here is our qubit which is going to contain all the logical code so when the send otp button is clicked we will call the send otp method of our qubit and this method will check if the mobile number is five digits long if it is not five digits long then it will emit a state now what is a state so every qubit that we will make can have a state and based on the state of the qubit the ui will show different things and we define these states it is not prebuilt or something so in this case if the mobile number is not five digits long then our qubit will emit a mobile is not valid state and our ui will be observing the state of the qubit and will show this error message enter a five digit number and it will also clear the input box but imagine that the mobile number was five digit long then in this case our qubit will send the otp to the mobile number and after it has sent the otp it will emit a state otp is sent and when this otp is sent state is emitted our app can show a different screen where the user can verify that odp and one more thing the qubit only contains the logical code so if your app needs data from the internet then you will use the repository pattern to do so let's understand the flow of logic when fetching data from the internet with a qubit the first thing that happens is that the user takes some action which tells the qubit to fetch data from the internet but the qubit does not perform the network request the qubit further goes to a repository of data now the repository also doesn't make the network request the repository only holds and models the data the reason why we don't make network requests directly in the repository is because the source of the data can be multiple for example let's say you are making a big application that gets the data from the internet and caches it so that when the app opens the next time the app will first load the data stored in the cache memory and then it will get it from the internet so in this case our repository can decide where to get the data from when the qubit asks it for the data firstly the repository will ask the cache data service for the data then it will model the data and return the data to the qubit and then the repository will ask the network service for the data and will model it and return it to the qubit after the modeled data is returned to the qubit the qubit will emit a data loaded state and the ui will receive the data and display it since it will be observing the state of the qubit by the way what is this data modeling that i keep saying when we get data from an api we usually get it in the form of a json now converting this json into a class object is basically what data modeling is data modeling is required because working with a json can be difficult let's say that the api that was providing you the json data changes the structure of the json a little bit so if you would have directly used the json everywhere in the app then it will get very tedious to adapt to the new json structure but if you model the data into a class object then you just have one place to fix the json structure and then the rest of your app will work just fine before we create our first qubit we need to add the flutter block library in our project and to do that go to the pubspec.yaml file and here press ctrl shift np if you have installed the extensions that i asked you to install at the beginning of this video then you will be able to see this pub spec assist add slash update dependencies hit enter and here type flutter underscore block hit enter again and it will automatically add the flutter block library with the latest stable version and now to install the library just press ctrl s while installing this flutter block library you might face any issues with the compatibility of this library and the flutter sdk installed in your computer so if you face any version compatibility issues then what you need to do is open up the terminal by pressing ctrl j and here run flutter upgrade if you hit enter then your flutter will get upgraded to the latest stable version and this flutter block library will become compatible with it and after that you can save the project again reopen vs code and everything will work fine but if there is no compatibility issues then you don't have to do all of this you can just add the library and go ahead now we are going to create our first qubit so right click on the lib folder and here you can see this action new qubit you will see these options available only if you have installed the block extension in vs code so click on new qubit and give a name of the qubit i am going to call this to-do's qubit you can see a folder named qubit is automatically created and inside this we have two files to do is underscore qubit dot dot and to do's underscore state dot dot if you go to the qubit then you will see that there is a class automatically created which extends the qubit this qubit comes with the flutter block library and here you can see it accepts a generic type which is the state of this qubit now if you go to this to-do state you can see that this to-do state is an abstract class and you can define as many states as you want by extending to this to-do's state so for example let's say if you have a state to-do's loading then you can write class to-do's loading extends to do's state and now this to-do's qubit will be able to emit that to do's loading state in the same way you can have as many states as you want but we will come back to the state again after some time for now i'm going to show you how to provide a qubit to a widget so that the widget can observe the states of the qubit and also call the methods that we are going to write in this qubit now to provide a qubit to a widget we just have to wrap that widget with a block provider this block provider comes with the block library so i'm going to go to the router.dart file and if i surround this to do's screen with a block provider press ctrl dot and import this library and you can see we need to add the argument create okay let me just format it a little bit so it is more clear yeah so here in this block provider we have two arguments first is the child here we have to pass the widget to which we want to pass our qubit and the second is the create in this create argument we have a function and this function is going to return the qubit that we want to create here we have the to-do's qubit and now by doing this our to-do's qubit will now be available to this entire to-do screen widget so it will not just be available to the to-do screen widget but any widget that is a child of this widget things will get a lot clearer once you implement this entire thing with me so just hold on now this is not the only way to provide a qubit to a widget let's say that your app has a few screens and all of those screens share some data and that data is retrieved from a qubit so if you wrap each of those widgets with a blog provider having a new instance of the to-do's qubit because here you can see in the create function we are creating an entirely new qubit so if you do this then the data will not be shared each of those widgets will have a different instance of the qubit and it will have different data so in order to persist the data of a qubit across different widgets we have this block provider dot value function and through this we can provide a copy of a qubit to different widgets i will be doing this later in this video but for now let's just have this create function returning the qubit but we will come back to it again where we will use the value method to provide the qubit to our widget and now inside the data folder create a new class new file name this file repository dot dot our app is going to have just one repository i'm going to call this repository and for now just leave it like this and in the data class create one more file name this file network service dot dot this file will contain the network service class which will perform all the network requests leave this class empty we will come back to it again if you have more than one service or if you have more than one repository then you should consider creating subfolders to store those services and repositories separately and our app is going to have just a single model i'm going to store this model in a models folder and the name of the model will be to do dot dart so create to do class this will have a string member to do message then a string member is completed and an integer which will be the id of the to-do our json server will return the to-do's in the form of adjacent so we are going to write to do dot from json this is going to take a json object and with this we will create an instance of the to do class so to do that just press to do message is equals to json and the key that i'm going to have for the to-do message is to do and then is completed this is just going to be is completed and then the id of the to-do so id will be equals to json id and this will be as an integer so just cast it to an integer now that our to-do model is ready we can write the code that will fetch the to-do's from the json server and it will convert those json objects into to-do's and then we can display the data onto the to-do's screen so go to the to-do's underscore screen dot dart since we have provided this to-do screen our qubit you can see in the router dot dot that we have provided the to-do's qubit we can access it from here but how do we access a qubit well for that we have this block provider of context and here we have to pass a generic type that is going to be the name of the qubit that you want to access because one widget can have multiple qubits i will show you how to provide multiple qubits to a single widget in some time but for now we are going to access this to-do's qubit that we have provided to the to-do's screen via this block provider dot off method so this is the syntax block provider off and the name of the qubit that we want to access and this will give us the instance of the to-do qubit that we have created right now our to-do qubit is empty but we are going to have a fetch to do's method in it so type that and now place your cursor on this method press ctrl dot and click on create method now go to the tools qubit this fetch to do's method is not going to make the network request directly from here it will ask the repository for the data and therefore this to do's qubit requires an instance of the repository so go to the router.dart and here declare an object of the repository and we will initialize the repository in the app router constructor so repository equals repository and now we need to pass this repository to the to-do's qubit so go to the to-do's qubit create an object of repository press ctrl dot on the constructor and click on add final field formal parameters i'm also going to wrap this thing with curly braces so that we can see the parameters available to the constructor go to the router dot dot and here if you type repository then you can see that we have to pass a repository and now our to-do's qubit has an instance of the repository and now from the fetch to do's method we can ask the repository to get us the data to get us the to-do's data the repository does not have any methods right now but if we press ctrl dot then we will be able to create this method inside the repository now this fetch to do's method in the repository what it is going to do is it will ask the network service to get us an array of to-do's and then it will convert that array of to-do's into a list of to-do's that we have created we have created a to-do model and the repository will be converting the json array into a list of this to-do objects so the return type of this method is going to be a list of to-do's since we are going to get the data from the network it is going to take some time so we are going to return a future list of to-do's if you do not know what is a future then it is similar to promises in javascript and if you do not know what is a promise either then just search on youtube about futures in flutter and you will get like three four minutes long video that will explain this entire future thing press ctrl dot and import the todo.dart model our repository also requires an instance of a network service press control dot and create a constructor surround this with curly braces so that we know what are the parameters available to this now go to the router.dart and give the repository a network service so directly create a network service inside this constructor and now our repository has an access to an object of network service since this fetch to do's method is going to return a future we have to add the async keyword so that flutter knows that this method is going to take some time to execute and it should not be done on the main thread and now this fetch to do's function can further ask the network service to get the json array that it will be converting into a list of to-do's so type network service dot get fetch to do's let's just call fetch everywhere and the network service is also empty it does not have this method so press ctrl dot create this method i will tell you the return type of this method after we write the code inside it so just wait for that now to make network requests we will have to add the http library in our project so go to the pub spec dot yaml file press ctrl shift and p add slash update dependencies http press ctrl s and save it now go to the network service if you remember at the start of this video i had shown you how to start the json server and how to configure a to-do's route to it i am going to restart that json server because we are going to need it now since we are building the network service so open up command prompt and go to the folder where your db.json file is located if you do not have the file then create it now and then run the command json server minus minus watch db.json and now you can see we have the resource available on the local host on port 3000 let me also open the db.json file in vs code so that we can see any changes that will happen when we make the request through it so for now we just have one object here and we are going to fetch this data in the network service the resource is available on port 3000 on the local host and if you are running this application on the android emulator then you can access the local host of your computer not from the local host but from the ip address 10.0.2.2 so let me create a variable named base url this will hold a string which will be the path to the json server 10.0.2.2 colon 3000. if you pass localhost here then it will not work you have to pass this if you are running on android but if you are running this app on the ios simulator then 10.0.2.2 is not going to work instead you will require localhost so based on the system you are on either choose localhost or 10.0.2.2 and if you are going to run this on a real device then i will show you what to put here at the end of this video and now we are going to make an http get request to get the to-do's from the json server and to do that we have this get method available in the http package here we have to pass an object of uri so type dot parse and the url of the server so it is going to be base url and our to-do's resource lies on the slash to-do's route and since this is a network request it is going to take some time so we are going to add the await keyword in front of it and we are also going to add the async keyword for our method this http get request can throw error let's say if you do not have internet connection then it is going to throw an error or if this route is wrong then also it is going to throw an error so we need to surround this with try catch and when an error is thrown we are going to return null from this method otherwise inside the try block we are going to create a response variable and we will hold the response of the get request in this variable and the data of the response lies in the response dot body which is a string so this is going to be a json string and we need to decode this into a json and to do that we have the json decode function here we have to pass the string json that we want to decode the resource that will be coming from this slash to do's route is going to be an array so we can safely cast this decode to a list and now the return type of our method is going to be a future of list dynamic now go to the repository dot dot and store the value returned by this function in a variable i am going to name this variable to do's raw and now i am going to map through each of the values in this to do's raw list and i'm going to convert that into a to-do object and then i'm going to create a list out of it and return it to the qubit i said so many things but all of this will happen in a single line of code so return to do raw dot map why do we not have a map here okay yeah we need to add a wait since this is going to return a future so to do raw dot map and with each of this element we are going to create a to-do object we have the to-do dot from json function and we are going to pass the element this element is nothing but a json object that resembles this it has a to-do keyword and is completed and an id and we are making a to-do object out of all those keys so after this we are going to convert this map result into a list and then we will just simply return that to the qubit and now go to the to-do's qubit in the to-do qubit when the to-do's are fetched from the internet then this dot then function will be called and this value is nothing but the list of the to do as you can see here so rename this to todos and now that the qubit has the to-do's list it can emit a new state with the to-do's data so that the ui can take that data and display it i will show you how the ui observes the state of a qubit in a few moments but for now i'm going to go to the to-do state and here i am going to create a state that will be called to-do's loaded so when the to-do is fetched the to-do's qubit will emit this state and we need to extend the to-do's state because that's our abstract class and this to-do's loaded state is going to have a list of to-do which i'm going to call to-do's press ctrl dot and import this to-do model and then press ctrl dot on this as well and create constructor surround this with curly braces so that you know we can see what is the parameters to this constructor and now we can emit a to do's loaded state from our to-do's qubit and this state will hold the data that we have fetched from the internet and now to emit a state we just have to call the emit state available in the qubit and this accepts the state so here it is going to be to do's loaded and the to-do's loaded can take the to-do's that we have fetched now before i write the code that will observe the state of this qubit and show the to-do's onto the screen i am going to make sure that we are receiving the to-do's data so go to the network service and here i am going to print out the response body and also in the catch block instead of returning null just return an empty list now let me see if i'm receiving this response body or not restart the application we have nothing it means that there must be some error so let me print this error bad state in secure http is not allowed by platform our json server is running on http and not on https so by default http is disabled because it is insecure so for this application i will have to allow insecure http and we will have to do it separately for ios and android so for android go to the androidmanifest.xml file and here inside the application tag add android uses clear text traffic equals true restart the application that still throws an error i guess we will have to terminate the application and restart it let me rerun it why so many errors okay this is wrong uses clear text traffic is not available this t in the text is not capital it is small let me do that and rerun it and you can see that we have received our to-do's array now if you want to allow insecure http on ios then you need to go to the info.plist file and here you have to type a few things i will put it into the description so you can directly copy it from there and you don't have to type it so we have to add a key and the key is going to be ns app transport security below this we are going to have a dict this dict will have a key and the name of the key will be ns allows rb tree loads it is better that you don't type it and copy directly from the description because if you misspell anything then it will not work and below the key just add true and adding these four lines will allow in secure http on your ios device and now that we have the data coming from the json server we can go to the to do's screen observe the state of the to-do's qubit get the to-do's data and display it onto the screen on this screen we need to show a list of to-do's and that will be a scrollable list so the body of our scaffold is going to be a single child scroll view and the child will be a column and then the children of this column will be the to do items but before i give this column its children we need to observe the changes to the to-do's qubit so when the to-do's loaded state is emitted we need to take the to-do's data from that and then we need to convert that data into ui so how do we observe a qubits state for that the flutter block library provides us a block builder widget i'll just add that widget here and then i'll explain what it is doing so press control dot on the single child scroll view click on wrap with block builder let me first explain the syntax of this block builder widget so the first thing that it accepts here is the name of the qubit here it is going to be to do's qubit and the second is the state of the qubit and what is the state that we have given it has the to-do state and then you can see this block builder has a builder function now this builder method is called every time when there is a new state emitted by the to-do's qubit and then through this builder method we have to return our ui that we want to build when the state is emitted and to access the data of the state we are given this state parameter to this builder function now currently our to do state can have two states first is to do's initial and the second is to do is load it so this is the default state of our qubit so what i want to do is i want to show a circular progress loading bar when the to do is not loaded and to do that what i will do is i will check if the state is to do is loaded or not so if the state is not to do is loaded then i will return a circular progress indicator and if i refresh this page then i will not see the circular progress indicator that is because the json server is running on the local host and getting data from the local host is very fast and because of which the to-do's initial state does not stay for a long time so what i will do is i'll just put a delay of maybe two seconds so we can emulate a fake loading progress so for that we have this timer class the first parameter here is duration i'm gonna put three seconds and the second parameter is the function that must be called after three seconds and inside this function i will fetch my to-do's so it will take a minimum of three seconds for the to-do's to get fetched and i think if i refresh now then we should be able to see the circular progress indicator do we see it yes we see it but it is i need to put it in the center so i will wrap this widget with center all right now our to-do's data is stored in this to-do's loaded state so to extract that data here i'm going to create a to-do's variable and i'm going to cast this state to to-do's loaded and then i can access the to-do's and now we can create the children of this column so the children are going to be to do's dot map to list and then for each map i will return it to do object and i am going to create it now so it is going to be a separate function to do that will take this to do element and then it will return a widget for it create this function and the parameter here is the to-do object and from this i'm going to return a container when the to do tile is swiped left or right then we will finish or unfinish a particular to do and to detect any swipes on a widget we have this dismissible widget available in flutter so the widget that we will be returning from this function will be a dismissible and we need to provide a unique key to this dismissible so give it an object of key and the key requires a string what is the unique key that we can pass well the to-do object has a unique id so we will just pass that to do dot id and then the child of this dismissible will be a container let me format it the width of this container will be equal to the width of the parent we need to pass a context so we will take the context from this build method we will give this to do tile asymmetric padding on the horizontal the padding will be 15 and on the vertical it will be 20 and a little bit of decoration i'm gonna give the tile a color white and it will also have a bottom border border side and the color of this border will be gray 200 and the child of this container will be a row the first child of this row will be a text and this text will be our to-do message if we save it then we can see the to-do message apart from this i also want to show a completion indicator so that indicator will be green if the to-do has been completed and if it is not completed then it will be read let me create a separate function for the completion indicator and i'm going to name this function completion indicator this will take the to-do object as a parameter and will return a container width will be 20 height will be 20 this will also have a decoration and this will be circular so we are going to give this a border radius of 50. width of the border will be 4 and the color will be green color will be green if the to-do is completed and if it is not completed it will be red so we will check if to-do is completed if it is completed then it will be color start green otherwise it will be colors dot red why do we have this error okay the is completed is a string and we expect a boolean here if you go to the db.json then you can see that our boolean is stored as a string so to get the boolean value from that what we can do is we can just simply check if it is equals to true and this will be a bool as well so if the string holds true then this will be true and if it holds anything else then it will be false now go back to to do's screen yeah we need a semicolon here and other than that we can just provide this completion indicator in this row i want to push this indicator at the edge of the screen and to do that we can set the main axis alignment on our row to main axis alignment dot space between this dismissible is also getting a little ugly so i will just extract this container into a separate method and that method will be named to do tile this will also take the to do and i think it will also require the context and then we will return that thing here and then we can give the dismissible a child of to do tile let me format it a little bit now let's see if the to-do tile is swipeable yes it is but i want the background of this dismissible to be some solid color and we can do that by giving this dismissible a background widget i'm just gonna give it a container which will have the color indigo and now if i try to swipe then we can see the background color you can see that when we swipe the entire thing gets dismissed but what functionality do we want when it is swiped we just have to do or undo a to-do and then the to-do should come back to its original state instead of getting removed from the screen and we can implement that functionality with this confirm dismiss function from here we have to return a boolean if the boolean is false then the dismissible will come back to its original state and if it is true then it will be removed from the screen this confirm dismiss function also has a parameter dismiss direction which tells us in which direction the dismissible was swiped it could be left or right but we don't care for the direction whatever is the direction we just have to undo or do the to do so i don't care for this parameter i'll just put an underscore this confirm dismiss function is called when the dismissible is swiped and when it is swiped then we know what we have to do and we will change the completion of the to do from the to-do qubit we will create a function in the to-do qubit and to access the to-do we have this block provider off to do cubit dot change completion is the name of the function that i'm going to create and here i'm going to pass the to-do object for which we want to change the completion now this confirmed dismiss function accepts a future boolean so i'm just going to add an async here and then i will return false from here now press control dot and create this method the to-do's qubit is not going to make the network request so we will ask the repository to carry on from here and here as well i'm going to create a change completion method and we just have to undo the to do completion and we can do that by putting this exclamation mark in front of this boolean and apart from this boolean we also need to give the id of the to-do for which we want to make the change press ctrl dot and create this method now go to the repository here rename this variable to is completed the repository will also not make the network request it will just convert this data into json format which will be readable by this json server and then the json server will be able to update our to-do now before i write anything further i want you to explain what is a patch request in http a patch request is made when we want to modify a few elements or a few things in an object so for example if we go to this db.json so if we want to modify just one value in an object then we have to make a patch request telling the server that this is the object that i want to make changes to and this is the new value for the existing key just like input request input request what you do is you change this entire thing but if you want to make changes to just one value then you need to send a put request now for this example we don't need to know what is happening on the server when we are sending a put or a patch request that is being taken care by our json server we will just tell the server to do our patch so in this change completion method create an object i'm going to name this patch object the thing that we want to change is the is completed variable and we need to pass this as a string and from this change completion method we will return a future boolean so that our qubit gets to know after the request is completed and if it is a future then we need to make our function async and now we can ask the network service to make our patch request and to do that i am going to create a function named patch to do in this network service and i will pass the patch object here and along with that the id of the to-do for which we want to make the patch and then this function is going to return a future boolean so i will just do that press ctrl dot and create this method from this method return a future bool it's an async function now we will make our network request in try catch and if any error occurs then we will just return false and the try block we are going to make our request to send a patch request the http package has this patch function here we have to pass the url of the server it is going to be base url plus our resource is available on the slash to-dos and then we need to pass the id of the to-do for which we want to make the patch and after that we can tell the server what is the patch required by giving this patch object as a body to this request so the server will get to know that we want to change the is completed value of an object with this id on the to-do's route on the to-do's route since this is a network request it is going to take time and we will add the await keyword in front of it and after the request is completed we will return true from our method we have an error but we will come to it in some moment now go to the to-do's qubit and when the network request is completed then we will be notified in this dot then function let me change this variable name to something meaningful i'm going to call it is changed if the to-do is updated then we have to update the ui as well and to update the ui we will have to emit a new state because the builder method just a second here the builder method will be called only if we emit a new state and once that new state is emitted we are getting the to-do's and then we are building our ui but first we will have to update the to do value since this to do is an object it is a reference and if we change the value of this to do then the value of the to do that we have passed in this function will also get changed and this to do is nothing but a reference to the to do that we have in the to do's list so what we need to do is we just have to update this to do value so i'll just do that to do dot is completed equals not to do dot is completed now this will update the to do everywhere and we just have to emit a new state so that our ui builds all over again with the new data and i will do that in this update to do list function that i'm going to create now in this function we just have to emit a to do's loaded state and here we just have to pass the to-do's array that we already have and we don't need to make any other change because this to do is a reference and this to do already exists in the to do state object here and now we need to emit a new state with the same to-do's array and our ui will get updated how do we access that to do from this qubit this qubit is going to have this state variable that holds the current state of the qubit and we will store this current state in a current state variable and we will check if the current state is to do's loaded if it is to do is loaded then we will get the to-do's data from the current state so i guess this is fine here let me see about this error that we have let me try refreshing the page and the error is gone let's try to swipe it and you can see that the indicator changes to green let's see if any change has happened to the db.json file it has turned to true and if i try to swipe again then it turns false in real time so this is very cool and let's go forward when a to do tile is clicked then we have to open the edit to do screen and there we should be able to change the message of the to-do so what i'm going to do is i'm going to wrap this dismissible with an inquil and this inquilt has an on tap function and when this function is pressed we will tell the navigator to push the edit screen and we have this edit to do route all right let me click on it and see if it is taking us to the edit to do screen it is now we are finished with this to do screen we just have the edit to do screen and the add to do screen to complete so let's first go to the add to do screen and implement the functionalities over there let me also switch the app screen to the add to-do screen and here the first thing that i'm going to add is an app bar the app bar will have a title add to do and then it is going to have a text field and a button so the body of this app bar is going to be a container and i'm going to give this body a margin of 20 and then a child i will create the body in a separate function we are going to need the context to access the block provider the body will return a column and the first child in this column is going to be a text field and i am going to set auto to true so when the add to do screen is opened the text field automatically gains focus and the keyboard opens we also need a text editing controller through which we can access the data of this text field create a variable text editing controller and give this controller to this text field let me also make this thing final and other than that i just want to give this text field a hint and we can give this by the decoration parameter and the hint text is going to be a string that will say enter to do message let me save and see if it is there and then we will have a button but i also want to have some space between the text field and the button so i will add a size box with a height of 10 and i will get the button from a separate function which will be add button make this button return a container the width of the button will be match parent so media query of context dot size dot width and then we will get the context from the body the height of this container will be 50 and some decoration color will be black and we will also give this a border radius to make it look fancy and the child of this button will be a center text the text will be add to do we see no text because the color is black here and i'm gonna change it to white when this add button is clicked then we will send the to-do data to the server and the server will store it but for that we need a qubit so right click on the lib folder and create a new qubit the name of this qubit is going to be add to do and now for this add to do screen to access that qubit we need to go to the router and provide it from there we are going to provide the qubit to this ad to do the same way that we did to the to do qubit we just have to wrap it with a block provider so instead of me doing a lot of hard work i'll just copy this entire thing and paste it in this add to do case and then i will simply change the name of a few things so here we want to pass it the add to do qubit and this does not have any parameters so i'll just remove this and instead of the to do screen it will be returning the add to do screen and now we are going to restart the application for these changes to take effect now go back to the add to do screen and when the add button is clicked so we are going to wrap it with an inquil and when this inquil is tabbed then we are going to get the to-do message from this text editing controller so underscore controller dot text and then we are going to access the add to do qubit and this qubit is going to have a add to do function we will pass the message here press ctrl dot and create this method but before i write this function i want to set up the states of this qubit so go to the add to do state this add to do qubit will be able to emit three states the first will be an add to do error state so when the add to do button is clicked and the text is empty then we should be able to see a toast message which will say that the to-do message is empty so create a state for that this added to do error is going to extend the add to do state and it will also have a string parameter which will be the error that should be shown press control dot and create the constructor for this and i'm gonna put it in curly braces apart from the add to do error there are going to be two more states one will be adding to do so when this state is emitted instead of showing a text we will show a circular progress indicator in this button so create the state adding to do which is going to extend the add to do state and then a final state that will be to do added and when this state is limited then we will close this add to do screen and then we'll go back to the main screen now go back to the add to do qubit this add to-do qubit is going to require the repository so create an object of the repository and apart from the repository we will also require an object of the to-do's qubit so when this screen adds a to-do into the database then that should immediately take effect in the main screen without making a network request and to do that this ad to do qubit should be able to communicate with the todos quebec so create an object of the to-do's qubit and then press ctrl dot on the constructor curly braces is required i mean not required but preferred now go to the router again and here we have to pass the repository and the to-do's qubit so the to-do's qubit that we are providing here should be same as this to-do cubic object otherwise it will not work because this will have some other data and this will have some other data if we create a new object to it now what i'm going to do instead of passing this to do screen a new instance of this to do qubit we are going to pass it by a value it will make more sense after i implement it so i am going to create a variable named to do squbit and i am going to initiate it in the constructor and we also need to pass it the repository and now for this to do screen instead of block provider we are going to do block provider dot value and now you can see that we have an error because this requires a value to be passed and that value is just going to be the to-do qubit and then we can remove this create function and now we can pass our to-do qubit to the add to-do qubit so let me just pass it by value now go back to the add to do qubit and here the first thing that i'm going to check if the message is empty if the message is empty then a state should be returned which state add to do error state and the error is going to be to do message is empty and then we can return from this method and if the message is not empty then i'm going to emit a state adding to do and then we will make the network request now since our server is running on the local host it is going to be very fast so we will provide the network request some delay by default so create a timer a duration of maybe two seconds the timer has not been imported so press ctrl dot and import that the second parameter is the function and inside the function we will ask the repository to carry on further this repository will also have a add to do function and we will pass the message here in this function we are going to create the json that we will be sending to the server the name of the json will be to do object and we need to provide two values into it first will be with the key to do and that will just be the message and the second will be is the to-do completed so by default it should be false we do not need to provide it an id because that is automatically added by the json server and then we will ask the network service to make our request this ad to do is going to be an asynchronous function network service dot add to do is going to be the name of the function pass the to-do object go to the network service inside a try catch block we'll be making our request this time we need to send a post request and we can do that with the post function available in the http library the url will be base url slash to dose this is where we want to post our data and then the body that we want to add now after this request is successful the server will return back that object to us and this time it will contain the id of the object as well so we are going to catch that object that it is sending in a response variable it will be sending the data to us in the form of a json string so we will be converting it into a json map and then we will be returning that json map to the repository the repository will then convert that json into a to-do model object and then that to do object will be sent through the add to do qubit and then the add to do qubit will send that object to the to-do's qubit and then the to-do cubit will add that to-do object in the array and it will emit a new state we have a lot of stuff to do return json decode response dot body we need to put the await keyword and the function is going to be async and it will also return a future map but if the request fails then we will just return null now go to the repository and here we are going to accept that to do map coming from the add to do function and then we can return a to do object and the json we need to pass this function will also return a future of to-do object and by the way if the to-do map is null then we are just going to return a null now go to the add to do qubit when the network request is completed we can accept that with the then function and this value is nothing but a to do we will inform the to do qubit now that we have a new item added and we will only inform if the to-do is not null otherwise errors will happen so to inform the to-do qubit i am going to create a new function in the to-do qubit that will be added to do as well and we are going to pass the to-do that we received from the repository press ctrl dot and create this method what is this error oh yeah this is an error because of these arrows so remove them now go to the to-do qubit and here we are going to access this to do's we will add this to do to this list and then we will emit a new state that will inform the to-do screen that we have new data in our list so i'll just copy paste these three lines and inside the if condition i am going to create a variable named to do list that will hold a reference to the to-do's list and to this list i am going to add the to-do that is coming from the add to do qubit function and then we can emit a to-do's loaded state with this to-do list now go back to the add to-do qubit function and here the last thing that we are going to do is emit the state to do added okay so the back end is done and now we need to go to the add to do screen and observe the states of this add to do qubit and respond based on that okay so the first thing that we need to do is we need to show a circular progress indicator when the adding to do state is emitted so press ctrl dot on this text and wrap this with a block builder the name of the qubit is add to do qubit and the state is add to do state and here we will check if the state is adding to do and when that is the case then we will return a circular progress indicator and other than that when the to-do added state is emitted then we need to go to the main screen this flutter block library has a widget named block listener this widget only listens to the state of a qubit and does not return a new ui for the state it only executes a function but no ui is returned so the ui does not change so for our this case where we want to go back to the previous screen we don't have any ui to change we just have to go back to the previous screen so instead of using a block builder i am going to use a block listener so for this container i am going to wrap this with a block listener the name of the qubit is add to do and here i will check if the state is to do added and when the to-do is added then we have to go to the previous screen and we can do that by calling the pop function on the navigator which will remove the add to do screen from the navigation stack and the screen below it which is the to-do screen will get visible after that we will return from this method oh yeah by the way i will explain the block listener this has the same syntax as the block builder and instead of a builder it has a child that returns the ui and we have this listener function which is of course called when a new state is emitted let's refresh the page and see if everything is working let me type something random yes it works when the add to do error state is emitted then we are going to show the error it contains so check if the state is add to do error [Music] we are going to use toasts to display the error message but by default flutter does not have any toast implementation so go to the pubspec.yaml file and here i am going to add a library named toast using this library we can show our toast messages now go back to the add to do screen and to display a toast we are going to call the show method on it the message is state dot error i am also going to give this toast a duration of 3 seconds the background color on this will be colors red and this toast will be shown in the center of the screen and this return from this if condition is useless so remove that now go to the add to do screen and let's see if our toast is displayed and the toast message is displayed but if we add some random stuff and then add this to do then it gets added in the to-do's list now we are going to make the edit rule screen so go to the edit to do screen and when a tile is clicked then it will open in this edit tutorial screen and we will be able to edit the message in it and other than editing will also be able to delete it if required so this edit to do screen needs a reference to the to-do object that we want to edit let me make it a final and we can get this to do reference in the constructor now if we go to the router.dart then here in this edit to do screen we have to provide the to do now how will we access a to do clicked on a tile and then how will be able to add it here well if we go to the to-do's screen and here we are pushing the edit to do route now this push named function has a parameter arguments and through this arguments we can pass any data and then we can receive that data in the router here it is going to be this to do now go back to the router and here we can access the to do object from this settings parameter settings dot arguments we will store this data in a variable named to do and then we will cast this argument to it to do now we can pass it to the edit to do screen now go back to the edit to do screen [Music] i guess we will have to restart the app because we made changes to the router this edit to do screen will also have an app bar and the title of the app bar will be edit to do other than that i will also give this a delete icon at the edge of the app bar and when that thing is clicked then we will delete the to do and we can provide it an icon with the actions parameter icons dot delete i will give this a padding of 10 and then i will wrap this with an inquil to detect any taps i will come to the implementation of this in some time after we create the edit to do qubit this screen is going to have a text field and a button so i will just quickly add that button and the text field the body will come from a separate function and it will also require the context so i will add that here and let me quickly create the ui [Music] okay so the ui is ready it is similar to what we had in the add to do screen we have a text field an update button and yeah that's it now since we are editing a to-do this text field should hold the to-do message by default the message of this to do so i'm going to create a controller object and i will remove this const because we are instantiating it over here and this constructor cannot be const anymore and then we can give the controller text equals to do message and now we can pass that thing to the text field and if i save it then we can see the to-do message over here let me first implement this delete functionality but before i do that we will have to create the edit to do qubit so right click on the lib folder name this qubit edit to do and then i will go to the router and provide the edit to do screen the qubit our edit to do qubit will also require a repository and a to-do qubit to be able to communicate and make changes so instead of typing it manually i will just copy this entire thing paste it in this edit to do route and change a few things first of all this will be a edit to do screen and to do must be passed and this one will not be an add to do cubit it will be an edit to do qubit right now this does not have any parameters we will add it now so go to the edit to do qubit create an object of the repository and one for the to-do skewbit press ctrl dot add these to the constructor and wrap it up with curly braces now go to the router and we do not have any error now let me reload the app and go back to the edit to do screen when the delete icon is pressed we will access the edit to do qubit and then we will create a function named delete to do here and we will pass the to-do object and of course we need to pass the generic type edit to do qubit and now i can press ctrl dot and create this method but before i write this method i will add the states for this edit to do and this time i'm just going to have two more states because i don't want to do the circular progress indicator thing all over again you get the idea you will be able to do it on your own so the two states that we are going to have is first one will be edit to do error so if any error occurs then we will emit this state and it will have a string member error press ctrl dot create the constructor and then we will have another state which will be to do edited this state will be emitted when the to-do is deleted or updated go back to the qubit and in the delete to do function we will tell the repository to carry on further and this time to the delete to do function in the repository we just have to pass the id of the to-do that we want to delete press ctrl dot and create this method this function will be an async function and if the to-do deletion is successful then this function is going to return a boolean for it and now we will make the network request in the network service so there as well we will create a function named delete to do which will take the id of the to-do that needs to be deleted the return type will be future bool asynchronous function network request in a try catch and on error we will return false other than that the http library has this delete function to send any delete requests the url is going to be same base url plus slash studios slash the id of the to-do that we want to delete and we need to put an await in front of the delete because it's a network request and it is going to take time and then we can return true now go back to the edit to do qubit implement the dot then function and change the value to is deleted if our to-do is deleted then we have to remove it from the main screen and to delete it from the main screen list we are going to create a function in the to-do cubit and then we will pass the to-do that we want to delete to this function press control dot and create this function in todos qubit here as well we will need the to-do's list so let me just copy these four lines and now here we just have to remove this to do from the list so any list will have this where function this where function will simply filter out the list if we pass false then the new list created will not contain the element and if we pass through then the new list created will contain the element so here we just have to check if the element id is not equals to to-do id this basically reads as get me the to-do's where element id is not to-do id and that will remove this to-do from the to-do list and after that we can emit it out now go back to the edit to do qubit and here after deleting it we will emit a state to do edited and when this state is emitted then our edit to do screen will remove the edit to do screen from the navigation stack and it will show the main screen so go to the edit to do screen and here we have to listen to the states of the edit to do qubit so i can simply wrap this entire scaffold with headblock listener it does not matter what i wrap if i wrap the body with the block listener then as well the same thing will happen and now here it is going to be edit to do qubit and the state if the state is to do edited then just call navigator.pop and this state can also be an error and in that case we have to show the toast message so let's do that here as directly so if the state is edit to do error then we are going to show a toast message [Music] and now we are going to give the functionality to the update to do button press ctrl dot on this wrap it with an inquil and on tap we are going to update it so get the block provider and create a method update to do pass the to-do that we want to update control dot create method apart from this to do object we were also going to need the to-do message that this thing contains so go back and here pass the to-do message as a parameter and we can access it from the controller now go to the update to do and add that string parameter here and now i'm going to check if the message is empty if it is empty then we will emit the edit to do error state the error will be message is empty and if that is not the case then we will ask the repository to update the to do [Music] this function will require the message and the id of the to do which we want to update press control dot and create this method this function will return a future boolean to tell if the request was success or not now to edit the to do message we can send a patch request and to make any patch requests we already have this patch to do method in our network service so let me just copy these two lines and then let's edit this function so first make this one an async function and this time we are not editing the is completed this time it is going to be the to do key and pass the message as the new value now go back to the edit to do qubit implement the dot then function if it is edited if the editing was successful then i will simply update the message in this to do and after that i will tell the to-do's qubit that our list has updated and that should reflect in the ui so to do that just set to do dot to do message equals message and this to do qubit already has this update to do list function and since this to do object is just a reference to the to-do list that is already there in the to-do qubit we can simply ask the to-do's qubit to update its list because this to-do message has already been changed and it exists in the to do's list so just call this update to do list function and that will do and apart from that we just have to emit the to do edited state i guess this completes everything let's test if it is working let me click on the delete button and that deletes the to do let's see if it has been deleted from the db.json yes we only have two values here let me also try to edit the message something meaningful that's not the spelling of meaning i guess it is not right all right something meaningful update it does not work why doesn't it work let me see so the update to do is not working let me see why that is the case on tap update to do if message is empty then do shared print message this function is not being called why is that let me restart the app yeah i just had to restart it everything else was working let's see if the thing has updated in the db.json yes it has let me try again something meaningful yes i got the spelling right yes it is working let me try to complete it so i guess everything is working now and now i will show you how to run this application on a real device to be able to run the application on a real device we will have to restart the json server with some different settings go to the directory where your db.json file is located and here we are going to run the command json server minus minus watch db.json and along with this we are also going to give it the host attribute now here we have to pass the ip address on which we want to run the json server so if we open another command prompt window or a terminal if you are on a mac and if it is windows then run the command ipconfig but if you are on a mac then you will have to run the command if config grep inet [Music] so after running the command you will be able to see an ipv4 address for your computer this ipv4 address will go as the parameter to the host in this command and this ip address will be accessible to all the devices on your network so start the json server this way and now you can see our resource is available on this route so copy the new route and paste it in the base url if you run the application now on a real device then it should work given that the computer and your mobile device both are connected to the same internet connection i'm gonna take two three more minutes of your time to explain two important features of the flutter block library that i couldn't add in the to-do app and after that i also want to address a wrong idea that you might have developed during this very long video let's start with the first feature a screen or a widget might require more than one qubit to function so we can provide multiple qubits to a widget in two ways first way would be to wrap the widget with a block provider and then wrap the block provider again with a block provider and do this nested wrapping for all the block providers that you want to give to your widget now this solution will be naive and can get ugly because of the nesting so for this use case the library has a widget named multi-block provider here instead of wrapping the widget with a blog provider we wrap it with the multi-block provider this widget has a provider's parameter that accepts a list containing all the providers that we want to give to the widget and then there is a child parameter that takes the widget to which the list of the providers needs to be given to and after this the widget will be able to access all the qubits the same way we did in the to-do app another important widget the library has is block consumer when we wanted to build our ui based on the state of a qubit we used the block builder widget and when we wanted to listen to the states of a qubit we used the block listener widget but there can be a case where we want to listen to the states of a qubit and at the same time we also want to build the ui based on the state of the qubit for this use case we are given the block consumer here in this example you can see that the block consumer is a combination of the other two widgets it has both the listener function and the builder function and now the wrong idea that you might have developed in this entire video we provided blog providers to widgets in the app router only where we were pushing new screens in the navigation stack so i want to clear this thing that the new screens getting pushed in the navigation stack are not the only widgets that can be given the block providers any widget added anywhere in your code can be wrapped with a block provider for example let's say that there is a feature in your app that displays some text when the dark mode is on and displays some other text when it is off so in this case we do not require to wrap the entire screen that is containing the text widget with the dark mode qubit instead we can just wrap the text widget since it is the only widget in your entire app that is using the dark mode qubit
Info
Channel: Hey! Let's Code
Views: 24,126
Rating: undefined out of 5
Keywords: bloc pattern, bloc pattern flutter, bloc pattern tutorial, bloc pattern tutorial flutter, cubits, flutter cubit tutorial, flutter bloc with rest api, flutter cubit with rest api, how to use flutter cubit with a repository, Flutter Bloc & Cubit Tutorial, Flutter BLoC Pattern Tutorial From Scratch, Flutter BLoC Pattern, BLoC Pattern with Flutter || State Management
Id: 3iaDZqFFfnQ
Channel Id: undefined
Length: 98min 1sec (5881 seconds)
Published: Thu Apr 01 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.