Chat App with Flutter, Dart Frog & WebSockets

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
what's up guys welcome to another flatter tutorial today we're going to build a full stock flatter chat application and so we're going to cover both front end and back end in overall as we go through this tutorial we're going to touch multiple topics so if you're learning flatter and dart here's three things that you will learn as you go forward with this tutorial first of all you will learn how to set up realtime communication using websockets here we will understand when and why we should use websocket over arrest API and vice versa plus we will build a fully functional server that powers the chat application but keep in mind that overall websockets help us to create a live bir directional Communication channel between the app and the server and in this way we can transmit messages instantaneously between the front end and the back end and then we're going to implement a secure authentication mechanism so we will Define how the users can authenticate inside the cloud Cent and as well how we verify that the user tokens are valid whenever the client send a request to the server because most of the routes in our server are going to be accessible only to authenticated user and in this way we will be able to restrict the access to the data that we are storing inside our database and talking about database we will also Define a robust database schema so we will learn how to design and Implement that in our postgress database phe so we will need to Define how we model all the different tables in which we're going to store the data of our chat room application and however if you're curious about how the chat application will look like here's a quick Showcase of some of the screens that we're going to develop throughout this project and wel come back to the UI design later on but for now before we start coding let's take a look at the server structure for this project and remember that the goal is to implement a chat application that integrates two different communication protocols so a rest API and a websocket with the rest API we will have a request response type of communication while with the websocket setup we will be able to send a real time data transfer between the chat client and the server and by the way we're going to use dart frog for the backend because it enable us to develop our server with a single project framework and lets us manage both the rest API and the websocket connection so let's begin by taking a look at the user management feature in our application and here we're going to focus on the user resource this is all the data related to each user in our system so here we will only use the rest API because for basic functionalities like updating or retrieving the user profile we don't need realtime data streaming but remember that's a Design's choice for this project so if that is a key feature feature in your project you can use realtime data stream for this as well anyway in our initial design of the rest API we have two end points to handle the user data so first of all when a user open the app we need to display a list of contacts to which he can send messages and to achieve this we will let the client send a get request to the user's endpoint and in response the server will provide a list of users so for this scenario for example if you want to refresh the data you could Implement a pool to refresh UI so that the user can manually refresh their contact list by triggering a new get request anyway beside the initial endpoint that fetches a list of contact we also have another key endpoint for the user and this is for individual users to retrieve their own data and to update them so for instance when the user go to the profile page they can fetch their latest information by sending a guest request then here we can support multiple type of request so we can send a get request to retrieve the data a patch request for updating specific user details and a delete request if we want to delete all the data or the current user okay now let's move on and let's shift the focus to the messaging part of the application which is where we use both the rest API and the websocket so we have a restful and point here that is designed to handle the message retrieval in a chat room so whenever the user load the chat room page the client will send a get request to load all the messages that have been previously sent in that specific room and the endpoint structure is chat room chat room ID messages and the chat room ID is a placeholder and it will change based on the chat that the user is entering and you will see later that this ID is crucial to filter and retrieve the correct message data from the database so again the API call to this end point is a one-time event that happen as soon as the user open the page however once the page is open all the new updates are going to come through the websocket so remember this is a bir directional communication line so that whenever the user try to send a new message the event will be added to the websocket so this is going to be the message. create event this is going to have a payload that will include the data or the new message and Versa whenever a new message is recorded in the server and then has to be sent from the server to the client the websocket will send an event to all the channel that are connected to that specific chat room so it will send the message created event and it will contain old information of the newly created message and by sending this information we will be then able to display the new message in the UI without refreshing the page and that's it for now in term of how we're going to structure the server we will not dive into the chat room and the media attachment features as we will cover them later on in the tutorial so let's go ahead let's open Visual Studio code and let's get started coding okay so let's get started with this new project as you can see I created a new flatter app and it only contains the Full code that comes with every new flatter application so as you can see we have the my app stateless widget and this is displayed right now inside the simulator so essentially we just have the stateful widget that you can see here in row 23 and this is available inside the simulator so I haven't added any new file or made any other changes so essentially this is a new Fresh canvas for us to begin the project so now let's go ahead and the first step is to create our backend server so we will be using dth frog for this so let's go to pub. Dev and let's follow step-by-step the documentation to create a new dart frog project and the first thing is to make sure that you have the dart frog CLI installed inside your computer so you need to run Dart PB Global activate dart frog CLI in my case this is already active so if it's not active for you it will download it and set it up for you then as a next step we can create a new dart frog project so we need to use dart frog Creator and then we can Define the name of the project so in this case this will be the server of our application so we will use dart frog create API so this will create the new project inside the API directory of our project and you can see that this is now appearing inside the root level of our project so if we go inside the new folder you can see that we have the routes folder where we will add all the different end points for our server and here we will include all the endpoint that we need for the rest API and as well for the web socket and in practice we will create another route for that that will manage the connection between the app and the websocket so essentially we can establish a bidirectional communication line between the client and the server and we will Deep dive into this later on because before we develop the server we need to add a few more components to our project and first of all we will develop a new package to store all the models that we use in the project the reason is that we need to use the models both in the API and in the client so we need the same models across two different project because the backend and the client are essentially separate entities so however we can reuse the same model in both of them so we can create a package and this will contain all the different data models that we will create and it will allow us to reuse them across the two different projects so let's go ahead and let's start to create this new package so to do this we need to run flatter create doh do template package and then we need to define the name of this new package so let's take this and let's go back to the terminal and first we will create a new directory so we just need to use make directory inside the terminal and then we can call it packages and here again we can store all the different packages that we will create for the application so here we only store the custom package that we develop directly so in this case we will only have the model package so let's go ahead and let's create this so as I mentioned here we will Define all the different data models for the project and we will go inside the Library folder and create all the models there then in the model file we can export all the different data models so is going to be easy to import them everywhere else but the actual Dart classes that we will create are going to be inside the source folder so we will come back to this shortly for now the test are testing the calculator that was added by default so we can remove this but anyway we won't be making any further changes to the model for now so in fact our next step is to create a superas project because we will use superas as an authentication provider for the application and as well to create and deploy a postgrad database so let's go ahead and take a look at the superas documentation because before creating an account I recommend you review both the database and the authentication documents so let's go ahead and let's take a quick look at the database because this will be a crucial part this tutorial in fact we will store all of our data here and our server will connect with this database to retrieve the data for the UI and this work both for the websocket and the rest API that we will create and overall it will be quite easy to get started with this database because we won't have to deploy any architecture so it will all come out of the box from on the super base project that we will create so let's go ahead and let's take a look at how we're going to store the data so these are the five different tables that we're going to create so let me explain a bit how we're going to store those data so the user table will contain all the information about the users of the application and here you will see some terms like PKA and FKA so keep in mind that PK stand for primary key and FK stand for forign key so the primary key of the user table is just a unique ID that identifies each user then we will use both the primary and the forign keys to link multiple tables so for instance here we have the user ID but then if you take a look at the chat room participant table we have the participant ID and this is a forign key that allow us to join the data that we are storing here inside this table with the user table so essentially in the chat room par participant table we have all the information about who are the user that are part of a specific chat room and then if we want we can connect those data with the user table so that we can retrieve the information that we're storing about the user inside the user table and remember that in the chat room participant table there might be multiple rows with the same user ID since a user can participate and be part of multiple chats so essentially for each user we might find one or more rows inside the chat room participant table obviously it could be even zero if the user has not created or has not been added to any chat room yet now let's take a look at the other forign key so this is the chat room ID and with this we can link the data that we're going to store in the chat room participant table with the chatro table which essentially just store the ID of each different chat room that are going to be be created for this project so this also has the last message ID and this indicate which is the last message that has been sent to the chat room so overall this will help us to retrieve the last message data and it will help us to show those data in the chat room preview screen where the user can see the list of chat rooms and you will see one message which is the last one that has been sent then we have the message table and this will contain all the data that we need to send a message inside the application so we have the message ID the chat room ID and then we have the user ID or the two different users so the sender user ID which is the user sending the message and the receiver user ID so the user who's receiving it then here we also have the attachment ID which is a foreing key that allow us to join the data or the message with attachment table so here is where we're going to store the information about the attachment which could be an image a video or an audio so whenever a user send that those attachment are going to be stored inside our storage and then we're going to take a URL and we're going to save it inside the attachment table that will include as well the attachment ID and the attachment type which is going to be image video or audio then we can join those data with the message table essentially by using the forign key of the messages and the primary key of the attachment and this is the structure of the database for our project so for now we're going to keep it like this so let's go ahead back to super.com and the next step is to create an account and to loging into your new project so as soon as you finish to create your account you can move on to the dashboard and you can click on new project so you can call it whatever you want and in my case I'm going to just call the project chat application next you need to set a password for the database so you can either generate one with superas or just type your own remember to copy it but if you forget it don't worry because you can always retrieve this from the setting of The Project then you need to choose as well a region which is where you're going to deploy the project so just choose the region that is the closest to the location of the user of your application then you can go ahead and you can create the new project and once you complete all of these steps you'll be redirected to the setting page and here is where you find a few different information of your project including the project API Keys then you will also find the project URL that will allow you to connect with your database now we will come back here later on and we will discuss this more in depth but for now let's focus on creating the tables for the postgress database so this will involve creating all the different tables but it might take a few minutes for the project to be fully set up so in the meantime let's just go back to visual studio code and let's take a look at a query that include the SQL script that allow us to create these tables now the first table is the user table so with this script we're going to be able to create the table to store the user details and keep in mind that the full code for this query is available in this project so don't worry you will not have to type it out now here we have the following so first of all we need to Define all the different columns and their data types so for example the ID will be a unique identifier and actually it will reference the O user table from superas and this is part of superas O and we will come back to that later on because we will use superas o in order to let the users create accounts and authenticate inside the client application however for now just remember that whenever a user authenticate through super base o the user will be added to the O user table which is managed under the wood by the superas o service then our goal here is to create a new user in our actual user table every time a user authenticate for the first time via superpa so essentially the user authenticate and we will create a new instance of that inside our table and to achieve this we will add a function that will run directly inside the super base back end so as you can see in row 13 of the script this function will insert a new user inside our user table whenever the user authenticate through supera and we will also need to set it up a trigger and this will Define when the function run so essentially it will activate upon a new user completing the registration process now this is just the default code and you can get it directly from superas is included in one of the different quick start so I haven't created anything here so you can find this code even if you just look at Authentication tutorial that is available here on super.com by the way keep in mind that we will use email and password authentication and we will also enable the phone login and in this way the user of the application will be able to input their phone number and they will authenticate by typing the OTP that they will receive to confirm the login but for now let's just go back to our query and let's continue with the next step which is to create the messages table and here the syntax slightly different from before because by default each new message will get an unique ID and this is going to be generated by the function that you see here so we're going to generate a unique ID every time we create a new message and this is going to be generated automatically in the back end so we will not pass the ID it will just be generated automatically then we Define all the other field such as the creation date which is a non-nullable time stamp and it has a default value so again every time we create a new message you will have automatically the current day time as a time stamp then we also need to set up the relationship within the tables so in this case the message will have a for eny which is the sender user ID and this will link the messages with the user table and then it will have another forign key so that we can link the messages with the user table as well using the receiver user ID now the logic for all the other queries and the other tables is pretty much similar so we're just defining which are the columns that are available for each of the table so you can find the code inside the database.sql query and you can take a look at it then if you scroll at the bottom you will find a few different scripts that are a bit different so here we're just altering the messages table to add more foreign keys and we could not do this before because we had not created yet the attachment and the chat rooms table so whenever we run the message script for the first time the other two tables are not generated yet so we cannot connect them yet so first we need to create them and then we can come back and set up the connection between the different table now feel free to go ahead and take a look more in depth at all the other tables that we are creating here so including the messages the chat room the attachment and the chat room participant for now we're going to go ahead and we're going to take this database.sql file and we can copy the content inside the superas project so we just need to go to the script editor and we can run this directly over there in this way we will run all of these create statement and we will create the tables inside our database so this will Define the schema of our database and at this point if you get the success message message you will have the table available on the database now all the tables are going to be empty and you can check that here by looking at the number of rows so we have zero rows in all of the different tables then overall if you want you can go ahead and take a look at the schema and all the columns that are available but for now we're going to move ahead and we're going to come back to the datab base later on when we will insert some sample data but before we do that we need to create create some custom Dart classes to represent the different data models that we will use inside the application so let's go back to visual studio code and let's get started with data models so if you remember a few minutes ago we have created the models packages so you just need to make sure that you have this new package inside the packages folder before I made a mistake and I created it directly inside the root level of our project so I fixed this and I moved it back inside the packages folder now remember we're going to use these models both in our client application so inside the Library folder and inside the API anyway the first model that we can take a look at is the user class so this is a class that will contain all the information about each user of the application here we are extending equatable and if you're new to flatter equatable is a library that help you to implement value based equality and in practice with value based the quality implemented you can compare two instances of the user class and you can check whether they are the same or Not by comparing the values of the different properties of the class keep in mind that you can also choose which properties you want to compare and these are simply the properties that we add to the list of props which is the override that we need to add to every class that extend equatable and that's the one that you see here in row number six 63 so overall we are passing all the different properties now let's scroll back up and let's take a look at the implementation of this class so first of all we have six different properties and they are all strings so we have information like the username the phone the mail the image URL and as well the status that will tell us whether the user is online or not then if you take a look at the class Constructor you will notice that all this information are required which means that if we need to create an instance of the user we need to have all of this information available otherwise we won't be able to create the instance but keep in mind that we will not have always the phone or the mail so we will need to change this later on then we have the user copy withd method with this we can decide to modify the value for the user instance so we can't really modify because it's going to be immutable but we will replace it with a new inst or the user class where we pass the existing values of the properties or an updated version so we can choose to update one or more properties and to return a new instance that will replace the old one then let's go ahead and let me show you how I implemented the serialization and der serialization so we will need to retrieve the data from an external server and they will be passed here inside the client as a map then we need to extract the data so that we can create an instance of the user object by accessing the key value purse that are stored inside the Json then we pass as an input so inside the map so for example for the ID we will take the value of the key ID and we will pass it to the property then we're going to do the same for all the other properties and for example for the phone the mail and the user name if we don't find the value inside the database we will just pass an empty string so essentially we have a placeholder value and that's the same for the Avatar URL where we're just passing a random image from umash if we don't find an actual Avatar URL then we have also the two jaon method and with this we do the opposite so we take the data and we convert them into a map because when we need to send an instance of the user class to the database we cannot send our custom Dart class but we need to convert it into a map first and that's pretty much all we have inside the user. dart now let's continue and take a look at the chat room class and here we store the chat room ID then we store a list of participant so two different users that have been added to that specific chat room then we store the last message which is a message that we need to display on the chat room listing screen so that's why we are adding it here and potentially we can also keep track of the unread count so that we tell to the current user how many messages he has not seen yet that have been sent inside the chat room then let's go ahead and you can see that all of these properties are required inside the class Constructor then we have the copy with method which perform the same logic as the previous one that we have seen and finally we have the factory Constructor chatro fromjson and the tojon method and overall the implementation of the factory Constructor is a bit more complex than the previous one that we have seen because some of the keys can be just accessed directly like the ID however for others like the participant we need to implement some more logic so the participant is going to hold the value of multiple users it's going to be a list with multiple Maps so we need to check that first because if there is an error and we don't get a list we cannot perform the next step which is to take one map at a time and to create an instance of the user object from the map so that we can create a list with the two different instances or the users which are the actual participant of that chat room then keep in mind we might end up in the case in which there is an error and then we just have a fullback so we just return the empty list that you see in row 39 then we are using another Factory Constructor in row 40 and this is to convert the map that will hold all the data for the last message into an instance of a message object and for now we are also adding a fullback if we don't find the value for that specific key then we also have the unread count and actually this will be always set to zero because we will not store this data inside the database so we will never find unread count so we will always end up with the fullback scenario which is using zero for this property then let's go ahead and let's take a look at the two Json for now we're just taking the values that are stored inside the chat room and we are returning them inside the map keep in mind we might need to make some changes here because we can't pass the participants list as it is right now and we will need to change this later on but for now let's just go ahead and take a look at the message class and here we have a bit more properties than the other classes so as you can see we have the ID which can be optional and this is because whenever the user create a new message he will not Define the ID of the message but the ID will only be defined as the message is saved inside the database because the database will automatically generate The UU ID for the message and this is because we have added this logic inside the query that we we have used to create the table in the database before then all the properties that we have in the class are just matching the data that we are storing inside our database so you can go ahead and take a look at the schema again and then we're just passing all the different values then is slightly different for the attachment ID as we're going to use the attachment ID that we can get from the messages table to check whether there are attachment stored inside the attachment table then if that's the case is we're going to create an instance of the attachment object and we're going to pass it as an input whenever we create a new message so that's why we are adding the whole attachment class here as an input then the rest of the class is very similar to the previous one that we had developed so we have the copy with method and then we have the factory Constructor and the tojon method so that we can serialize and the serialized the data then if we want to stop a second on the message. from jaason you we can take a look at the attachment again because the first step here is to check whether the value of the attachment is null or not because if the attachment is available we can use the attachment. from Jason to convert the data that we retrieve from the attachments table into an instance or the attachment class otherwise this is nullable so we can just pass null then we can modify the create dat because we are trying to pass the DAT time from from the create that key value per however if that's null we just create a new date time and we convert it to a string however that's not needed because the create that will never be null because whenever we add a neuro inside the messages table the created that will be automatically generated essentially that's how we Define the table when we run the create statement before so similarly to The UU ID the create that is automatically added even if we don't pass it and that's pretty much it for the message so let's move on and let's take a look at another class this is the attachment and it will simply represent the object so the media that we want to display together with a message if any has been added so here we have broken down the code into two different elements so we have the attachment type which represent the potential attachment that can be added so it can be a video it can be an image or it can be an audio then we have the attachment class which stores the ID the message ID that represent the message to which this attachment is connected to finally we have the attachment type and even the attachment URL so with this we can retrieve the data from the storage and we can display it inside the app UI so whether it's an image a video or an audio we will apply the same logic and anyway the rest of the class is very similar to the others so just go ahead and take a look at the copy WID and also how we are serializing and dis realizing the data when it comes to the attachment class okay now let's continue to The Next Step because we need to connect the client and the server with supab so we need to add a few different keys inside our project and we will manage them with the envied Library during development now let's go ahead and let's set this Library but first let's retrieve the necessary variable that we need from the API section or the setting page so we need to copy the project URL that you're going to find here and as well the unnon key that you're going to find inside the next section then you can use the unon key inside the client but definitely you should not use the service Ro secret that you have down below so you don't need to use this inside the client but we will use it for the server so we're going to copy this and we're going to add it as well inside our environment variable file then let's continue and let's set up the MVD Library so we're going to use this to manage the environment variables so let's go ahead and let's install all the dependency that we need so first we need to start by running the terminal command flatter Pub add envd so we need to add this at the root level of the project and this will install the depend inside the root level ppec doam file then as a next step we need to run flatter Pub ad D- Dev mved generator and this is a Dev dependency and we will use it along the build Runner and it will help us to generate a file that will contain all the environment variable for the project now keep in mind that we need to run this process twice because we need to add these dependencies in the root level project so that they are available here inside the prospect. yaml file and then we will need to take these dependencies and we will need to add them as well inside the API project so let's open the API directory and let's add the two different dev dependencies then we need to repeat the process again and we need to copy the envied library that we have here and we will need to add it as well inside this Prospect yaml file in this way we're going to have them available even for the dart from server then just make sure to run Dart Pub get so you're going to install the dependencies now we can go ahead and we can create the environment file at the root level of our project so here we will add the superas URL the superas Anon key and the super Bas service key but keep in mind that we will remove this and we will only make it available inside the environment file inside the API directory because we need to make sure not to use this service roll key inside the client because if you use it in the client you risk to leak the database access and it's a danger for your application instead unon key we can keep it in the client so we can use it as long as we enable R level security so with row level security users will only be able to access to their data inside their database or anyway we can Define the rules of access to the data and we're going to do that later on for now let's just make sure to add the EMV to the G ignore file so that we can prevent pushing this file to Version Control because remember if you push this file to Version Control so to GitHub you risk of leaking your API Keys now let's go ahead and let's start to develop the EMV dodart file so here is where we're going to use the EnV library to generate the dart class that will allow us to use the environment variable in inside our project so first of all we need to import the dependency then we need to create an abstract class then we will call EMV so here we will add the MV annotation and then we will create three different static variables so each of these will represent a different environment variable and we just need to match the name of the variables that we have added inside the EMV file keep in mind that you need to make sure that they match the same name and if you want you can also add obfuscate equal to True inside the three different envied field annotation but we will not use it for now because it's not really important for this project keep in mind that we will only use these environment variables during the development the overall approach will change once we go and put the app in production now let's go ahead and we are ready to generate the class so EMV dog. Dart so we just need to open the terminal and we just need to run Dart run build Runner build so that we can use the dev dependency to automatically generate the code for the EMV dog. Dart then it will take a few second but upon the successful generation of this code you will be able to access the emvg dart file and essentially now you can use this class to refer to the different environment variable so these are going to be available everywhere inside the application now we can close the generated code and we can copy the code that we have here in the EMV Dart because as we have mentioned before we need to repeat this process twice so we will need to create this as well inside the API project so we're going to go ahead we're going to create the library directory and we're going to add the EMV Dart file and now we can just paste all the code that we have added before then if we run Dart run build Runner build now it will not work for two different reasons so the EMV file that we have at the root level of our project is not going to be accessible here for the mved library so we will need to create another EMV file and we need to paste it inside the API directory and then also there's another reason why this will not work so it took me a bit to the this but essentially the reason it didn't work in term of code generation is because when I added the dependencies inside the prect doy file or the API project I did not run Dart Pub get and essentially we don't have this dependency available until we run that so just make sure to have the EMV file and to go to your pass yaml file and to run flatter pubg or actually in this case it's a Dart project so we need to run Dart Pub get now at this point we can use Dart run build Runner build again and this will automatically generate the emvg dart file which is going to be available inside the API now so we can use this environment variable whenever we want inside the different routes of our API now let's make one change before moving on to the next step so let me remove the superas service key from the root level EMV file because again we should not use it inside the client not to risk to leak the data of our database so now let's go ahead and obviously we do not modify directly the generated code but we go inside the EMV Dart file we remove the service roll key and then we need to run the build Runner again so that we can finally generate the code again and we are good to go with the environment variable everywhere inside the server and as well inside the client so at this point we're good to go here so we can close all the files and we can move on to the next step of the tutorial and at this point we can finally start working on the backand server for our flatter chat application so our first task here is to create an endpoint to retrieve all the messages stored in a specific chat room and we will do this within the chat room end point so we're going to create this new folder in the route section so thiso will include the chat room ID in square brackets and this mean that the chat room ID is passed as a parameter with each other the request that can be sent here so this will allow us to have it available in the request context so the endpoint will be structured in the following way so chat room for/ id for/ chatro id for/ messages and so this is where we need to send the request every time we want to retrieve a list of different messages for a specific chat room now let's go ahead and let's implement this so we need to go inside the index. dart file and we need to start to Define our on request function so in this case this is going to be a synchronous so the return type is going to be a response but we will wrap it with future R so essentially we specify that this is going to be an asynchronous task now we will go ahead and implement this function here and so we need to add a switch case statement to handle the request method so this particular in point will only accept get request meaning that we can only retrieve data here so if a user send data here or try to update or delete some data from the database using this Ino the request will not be processed so we will ensure that if the HTTP method is not get the user will just get a response and essentially will tell the user that that specific method is not allowed then if the request is a get request we're just going to call this uncore get method and here is where we're going to implement the logic to interact with a message repository and to take the data from our database and to return those data to the client so now before we develop this let's just go ahead and complete the switch case St M so we just need to add all the other potential HTTP methods and then we just specify that if it's not a get request essentially we just use HTTP status method not allowed as a status code for the response and in this way the client sending the request to the endpoint will know the reason why the response has not been received essentially now moving forward let's add the miss s method so we also need to use HTTP method do head and option so we just specify that these are also other alternative that are not allowed and then we can start to implement the get method however keep in mind that we will Mark some of this as a too because we need to develop the message repository first because that's the code that will be responsible for fetching the data from the database in term of messages and then we will come back here as soon as we have that repository and we will complete the implementation of this for now let's just create a try catch block and for now we will start by implementing the catch scenario so if we catch an error we're just going to return a response that will pass the error as a data so we're going to pass that as a body of the response and then we're also going to pass a status code equal to internal server error so it's going to be a status code equal to 500 then let's go go ahead and let's start to implement the try but essentially here we can just write some notes so essentially we will create a list of messages and we will return them inside the response so we need the message repository to do that so let's go inside the Library folder of our API and let's create the repositories so these are going to be the server side repository that will help us in our dart frog project to retrieve the data from the superas post Gress database so this one will retrieve the message data and actually it will also help us to write some data so whenever we need to create a new message we will use this repository to send the data to the database for now we will not implement the create message but we'll only focus on the fetch messages method so in the first one let's just use throw and implemented error and we will come back later on to implement this now keep in mind that the fetch messages will eventually return a list of maps so each map will contain all the data of a specific message and these are going to be only the messages for one specific chat room which is the one that we are passing as an input for this function now before we can implement this function we need to add the class Constructor for the message repository and so we need to connect to the database by using the super base client so we need to use the superas Library so just make sure to add this to the API project by running Dart Pub ad superas and remember we need the superbase library not the Super basore flatter since the superbase flatter library is for the client side development so we can use that inside our app but not inside our server so now let's go ahead and let's add the class Constructor and we are pretty much good to go here now don't worry about all the lines so these are just warning coming from the very good analysis so we can just remove this dep dependency we can run Dart Pub get again and we will remove all the different warnings so these are just there because we are not adding documentation to all the different public classes now let's go ahead and let's Implement and continue developing the fetch messages method so we will start by adding a TR catch block and then if we end up with an error we will just throw an exception but now let's go in the tri block and here we need to return the list of messages and we can retrieve this by writing a SQL query with our superbas client so we're going to Target the messages table and we're going to select all the columns from that specific table then let's specify that the output is going to be a postgress list so essentially this is a list with multiple Maps each map represent row inside our database now let's go ahead and let's also use the EQ method so with this we can filter the result based on the value of a specific column in this case we need to take the data from the messages table so in this table we have the chat room ID column and this is The UU ID that Define a unique ID for a chatro so we have this ID inside the end point we also have this ID here inside the fetch messages method so we will select that specific column and we will pass the chat room ID in order to filter the result so that we only retrieve the data for that specific chat room and pretty much now we can return the data just keep in mind that you need to add the superas library because that's how we can communicate from the server to the postgress database now if we want we can just keep it as it is however for the sake of clarity let's store the result inside the messages variable and then let's just return that so it's going to be easy to understand what we are returning from this method now we are pretty much good to go with the fetch messages so we can go back to our index. Dart file so the chat room messages and point and we can start to use the message repository here now in in order to access that we're going to use context. read message repository so we're going to look for an instance of the message repository inside the request context then if we find it we're going to save it inside the message repository variable and then we can use it to fetch the messages keep in mind this won't work yet because we don't have any instance of the message repository available yet now keep in mind that after we retrieve the messages we just send them back as a response to the client who sent the request so here we just use response dojon and we pass the messages inside the body so the implementation here is complete however it won't work because as I mentioned before we have not created yet an instance of the message repository and also after we create the instance we need to provide it to the request context through a middleware so our next step is to create the middleware within the routes for folder so with this we can grant access to the message repository instance to all the different endpoints that are in the routes folder so essentially this is some code that can get executed before the endpoint main logic and in this case we can just create a middleware and then we will pass two different uh middlewares here so we're going to add a request logger and this will help us to log each request in the terminal or the server logs and then we're going to have the second one which is going to be a provider that will pass an instance of the message repository to the Handler and in this way it will be available in all the requests in the routes folder so whenever you send a request in order to retrieve the messages now this will be available because we are using this middleware to provide the instance to the Handler and so to the request context but keep in mind that as a first thing we need to create the instance because we are passing an instance but in reality we don't have this message repository instance yet so in order to create that we will create a custom entry point for our server and this approach allow us to control the server initialization and it will allow us to execute some PR startup code so in this case it's going to be some code that we run before the server start and it will allow us to create an instance of our super based client and as well an instance of our message repository so we can just copy the code from this example and keep in mind that if we just keep the code as it is here in the example essentially our custom entry point is going to be exactly the same as the default dart frog entry point so there's no changes at all so far so in order to make the changes we're going to go ahead and Implement some logic so first of all we Define the late message repository variable and we're going to attribute a value to this inside this run function so what we do here is first we create an instance of the theb client so this is the superas client that allow us to communicate with the database and here is where we can use the environment variable that we created a few minutes ago so essentially we just need to pass the superbase URL and as well the superbase service roll key with the first one we know where to send a request so we know where is our database and with the second one we Grant access to our server to the database but again we can use this in a server because we fully control the server but we should not use it in the client then once we have the DB client instance we can pass it as an input for the message repository and now we have the message repository instance available and we can use it inside the middleware so so once we pass it to the middleware we can actually use it inside all of our different routes so now the routes that we have developed will work fine because we have a message repository inside the request context so we can use the message repository to retrieve the data okay so let's continue and actually let's remove the my own page that come by default with the flatter application and we're going to start by developing the chat room screen now keep in mind that the actual server data is not available yet in our project so in order to develop this screen we're going to use some sample data so it's going to be easier to get started with the UI obviously later on as soon as we connect the client with the server we're going to replace them so let's go ahead and let's start by removing the my own page widget and now here you can see the dev already some variables including two different user IDs and each of these going to be one of the two user that we're going to have inside the chat room screen as we develop the application then we have an instance of the chat room class and as you can see we have the ID which is a uu ID string then we have a list of participant with two instances of the user object and then we have a few more information including the last message that has been sent inside this chat room then we have the unread count and finally we have another variable at the bottom and this is a simple list of messages with two different messages that we're going to use on the chat room screen now we have all the data that we need to design the first draft of the UI so the next step is to create a new stateful widget inside the screens folder so let's create this new directory and let's create the new file as well so we're going to have the chatroom screen. Dart file then here inside the file we can create a new stateful widget let's call it chat room screen just like the name of the the file then as a first step we can go ahead and we can import it inside the main. dart then you will still have the error because we are passing the chat room as an input and we need to fix it by modifying the class Constructor or the stateful widget so we just need to specify that whenever we add the chat room screen to the widget tree we need to pass a chat room object as an input for this widget now just make sure that you have imported the mod models and you have added that inside the prect yam file now we're going to have a local import of the local package so we just need to specify which is the path of where we are storing this local package so just make sure to add that so that you have available all the models that we have created before and now you can use them inside the project so perfect now let's go ahead and let's implement the build method inside the state object or this new widget so here we need to create a scaffold and then we can start by preparing the app bar which is where we are going to display the username and the image of the user we are talking to so we're going to add this inside a column inside the title however before we do that we need to create two different variables inside the build method so we're going to have the current user and the other user so with the current user we simply take the list of participant from the chat room room and we take the first instance for which the user ID matches user ID number one and this is the global variable that we have in the main doart and just represent the user ID of the currently logged in user now we're going to change this as soon as we Implement authentication and at that point we are actually going to take the real loged in user but for now let's keep it like this then let's go ahead and let's also create the other user variable here we just store the other user that is available in the list of participant so the user ID will not match the current user user ID now let's actually rename these two different variables we can call the first one current participant and the second one other participant so pretty much we match the naming of the chat room property now let's go ahead and we can start to use this new information that we have Ava available in the build method inside the column for the title of the app bar so we're going to pass the text widget that's going to represent the username of the other participant and then we will just Define the style so we're going to use text theme equal to body small then right on top of the text widget we're going to add a circle Avatar and inside the circle Avatar we can display the image but first let's add that sized box to create some space between the two then we're going to actually struct as widget so that we will develop the circle Avatar in a separate stateless widget and this is because we're going to have these Avatar widget they we're going to reuse in different parts of the application now here we need to pass the Avatar URL as an input so we will match it with the property image URL and then we also can pass the radius so that we can customize the size of the Avatar so the image URL is going to be a string and the radius is going to be an integer now we're going to use this inside the build method to pass some properties inside the circle Avatar as you can see we just use the image URL and the network image widget to display the image then here we're going to Nest one circle avater inside another one so the external Circle Avatar so the outer one is going to be slightly larger in termal radius so that it's going to work as a border of the first one and per this new widget is ready so we can just take it and move it in a separate file so that we can keep this modular component separate from the chat room screen and in this way we're going to reduce it wherever we need it now let's go ahead and let's fix the error inside the chat room screen so we just need to import the newly created widget and finally if you want you can also remove the sced Box widget because the circle Avatar is slightly overflowing in the upper part and the title is pretty much good to go now before we conclude with the app bar we can add an icon button on the right hand side and we can just do that by passing the action and we're going to add the icon button inside the list in this case this is going to be a button that we can click to open another menu so we're going to use the more vertical icon then in order to create some space between the icon and the right hand side of the device we can just sized box then you can see the Avatar just moved on the left hand side so let's just ensure to put the title in the center part of the up bar and now we can move forward and we can start to work on the body of the chat room screen so we will start by adding a column and then here we will add to different widget a list view that will help us to display all the different messages in the chat room and then we will also add a row at the bottom where we will L the text form field now let's go ahead and let's customize the list view so we're going to have item count equal to the number of messages that are inside the messages list and again this is just the sample data that we have in the main doart for now then inside the item Builder we're just going to return a text widget and we're just going to take the content of the message and we're going to display it on the screen so as on now if we save we're going to have two different messages on the screen but first we need to wrap this with an expanded widget so you will take all the vertical space that it's available inside the column then keep in mind that we will be able to scroll if there are more messages than the space available obviously we will need to change the UI but first let's continue developing the bottom part of the column by adding a row and we're going to add the text form field now on the left hand side of the text form field we are going to have an icon button and by pressing on this the user will be able to select images to send as a message inside the chat room so we're going to have the icon equal to attach file for now then let's go ahead and let's add a text form field so we can add this and add an input decoration as of now you can see that it's not appearing yet on the screen and this is because there is an error that we need to fix but first let's also wrap everything with the safe area so that we are sure that there is enough space between the bottom part of our widget and the end of the device now after adding this we can continue and go back inside the row and we need to fix an error so essentially in order to display a text form field inside a row you will need to wrap the text form field with an expanded widget otherwise it will not appear and it will break the UI now as you can see before doing that I've added the fied property and as well the field color for this text form field but still we need to add the expanded widget otherwise it will not appear on the screen so let's go ahead let's add that and once we save and not toart you will see that this newly created text form field is going to appear here inside the UI now this does not match the desired UI yet so we need to customize the Border a bit and we're going to use outline input border then we're going to pass a border radius so that we set that all the car have a circular border radius and then we also need to remove the Border Side so essentially there's not going to be any line at the edges of the text form field okay so let's go ahead and let's complete this by adding some space on the left and the right sides so to do this let's just add a padding widget between the safe area and the column so this will allow us to set the padding for the UI and we will add some padding on the left on the right and and on the top but we will not add anything at the bottom for now as the safe area is already adding some space now you can see that whenever we open the keyboard there is no space between the text form field and the keyboard so whenever the keyboard expand we need to add some additional space and we will write some conditional logic to pass that additional space as a padding now in order to check whether to do that or not we're going to use the media query and we're going to use view in set so with this we can check which is the amount of space that is covered by the system UI and in this case the system UI is just going to be the keyboard in the bottom part so we use media query view in set of and then we check whether the bottom value is greater than zero so if it's greater than zero it will mean that we have opened the keyboard and then we need to add some bottom padding so we're going to add padding equal to 8 if View inser set do bottom is greater than zero and now you can see that whenever you open the keyboard there is going to be some space between the text form field and the keyboard itself okay now to complete this bottom part of the UI let's just add a too inside the onpress call back of the icon button because we will need to add some more logic in order to send the image message and then we're also going to pass a controller to the text form field so we will use this controller in order to save the data and also to clear the text form field after we send the message so let's go ahead and also let's create the controller inside the state object or the chat room screen and whenever you create a controller like this you also need to add dispose method so that whenever we remove the chat room screen from the widget tree we're also going to dispose of this controller and in this way we make sure that no resources are going to be wasted now let's go ahead and let's complete the implementation of the message bubble inside the list view so we're going to take the text widget and we're going to extract the message bubble widget so we're going to develop this separately from the chat room screen so we're going to pass a message as an input so let's also Define which message we want to pass so we take the list of messages and we select one at a specific index then we pass it here as an input and it's going to be required obviously we cannot create a message bubble if we don't have the message data available so let's just declare this variable for this stateless widget then let's fix the syntax here we don't have to take one from the list anymore because we only have one message available and then let's continue developing this so this is going to be looking a bit better so let's wrap everything with a container so that we can add spacing and a decoration but before we get there let's store the side size of the device in the size variable so we're going to use media query do size of so that we can use the width of the device because we want to set some constraint for the message bubble so essentially each message bubble will not take more than 2/3 OD horizontal space that it's available then let's add the Align widget to wrap the container so that if the current user is sending the message we're going to align the message bubble on the left hand side otherwise if the user is receiving the message we're going to align the message bubble on the right hand side of the device if you prefer you can just invert that then based on who is the user who sending the message we're also going to customize the color of the message bubble so we're going to add the Box decoration inside the container we're going to pass the color property then we're going to use the same conditional logic to Define which color to use so if the message has been sent by the current user we're going to use the primary color otherwise we're going to use the secondary color coming from the color scheme of the application then as we are creating a background color let's also make sure that the text that we're going to write on top of the container is visible so when we use primary as a background color we will use on primary as aex next color and we will do the same whenever we use the secondary color so in order to change the color we're just going to pass the style to the text widget and we're going to customize the body medium text team by passing the new text color then let's go ahead and let's complete to customize the message bubble so we're going to add a border radius we can use 16 or eight so just choose whichever you prefer and then we can add some padding and some margin so so we're going to add some padding in all the direction equal to eight so that the bubble is going to be slightly bigger and there's going to be some space between the text and the edges of the bubble and then let's just add some margin in the bottom part so we can use EDG in set dot only and we will add margin equal to four in the bottom perfect so for now the chat room message bubble is pretty much good to go so let's take this widget and let's move it to a different file so we're going to add the message bubble. Dart inside the widget folder so we're going to keep the code modular and we can ruse this if we need for now let's just make sure that here we are importing all the required dependencies and then we can save the newly created file and we can go back to the chat room screen so now back into the chat room screen we need to import the message bubble so let's go ahead and let's do that then we're going to add some more conditional logic to decide whether we want to show the image of the user or not so we will show the image of the user whenever the user send a message but we will not show it again if the next message is from the same user so we're going to write this conditional logic and we're going to store the result which is going to be a Boolean so true or false inside the show image variable and then we're going to use the value of the show image to decide whether to show the Avatar widget or not so let's go ahead and let's wrap the message bubble with a row so inside the row we will add the Avatar widget and we will display it on the left hand side of the message bubble If the message has been sent by the current user otherwise we will display it on the right hand side but actually after adding the row the alignment of the two different message bubble is not working anymore so let's add the main axis alignment and let's some conditional logic here to keep the same alignment as before so if the current user is sending the message we're going to align everything on the left hand side of the row otherwise we're going to align everything at the end of the row so on the right and side so perfect now the alignment is working again then let's go ahead and let's add the Avatar inside the list of children so we use show image and then we check whether the current user is the one sending the message and if those two conditions are met we're going to display the avatar on the left hand side of the message bubble and then let's add the Avatar as well on the right hand side so let's just copy and paste the code and we will need to change the conditional logic so we will check whether the image has to be shown and whether the user will send the message is not the current user now we just need to change a bit the margin of the message bubble so that there is some space between the Avatar and the actual message bubble and as you can see now everything is working fine so pretty much the UI of the chat room screen is kind of good to go but before we conclude with this part of the tutorial let's just make one more change inside the text form field so here we're missing the suffix icon so we're going to have another icon on the right hand side of the text form field and whenever you press this you can send a message so with the first one so the icon on the left hand side we are going to be able to choose an attachment for the message so we can choose an image that then we want to send together with the message then instead if you press on the suffix icon so on this other icon button we're going to send the message and then we're going to clear the text form field by using the controller so we'll come back to this later on as soon as we have the end point inside our API so so that we can actually send the message to the server for now the chat room screen is good to go and we can move on to the next step okay so the UI of the chat room screen is ready so now let's begin developing the API client and the client side message repository so these two different classes will allow us to retrieve the data from our backend server and to display them on the chat room screen so first let's go ahead and let's make sure that we have the HTTP Library added inside the root level ppec yaml file so we will use this library to send request to the server then let's go back to the services folder and let's take a look at the API client so I've added this new class and now we're going to take a look at it step by step so first of all let's take a look at the token provider this is just a new type and will simply represent a function that we can use to retrieve the Authentication token or the current logged in user and in practice this function will check whether the current user is authenticated and if so it will return the current authentication token of the user as a string because remember that whenever we will send a request to our backend server we need to pass the current authentication token of the user because the backend server will not allowed for unauthenticated request but anyway don't worry we will come back to this later on for now just keep in mind that we need to take the token provider and we need to pass this as an input whenever we create the API client now you can note that the API client has both a private Constructor which is used internally and a public Constructor that is used to initialize this class so we could potentially consolidate this into a single Constructor but for this example we can keep them separate as well so you can implement it whichever way you pref refer anyway whenever we will create an instance or this API client the only input that we will need to pass is the token provider because we will always create an HTTP client by default and the base URL will be set to Local Host obviously we will change the value in row 14 as soon as we deploy our backand server to the cloud then again we will need to define the three different variables that are needed for this class then let's go ahead and let's take a look at the fetch messages method that will take a chat room ID and it will eventually return a map as a response then with this specific method we just send a get request to the endpoint that we have created before inside our server so chat room ID chat room ID messages then in order to handle the request we're going to use this private method handle request so this is separate from our fetch message method because we're going to reuse it across multiple methods inside the API client this will take the request so the one that you see in row 34 so HTTP client. getet and we're going to send this request to the URI that we have defined above and then we need to attach some headers and we're going to do that inside the handle request method so as you can see here the first step that we have inside the tri catch block is to call the get request head method which is developed down below then what we will do with this is simply use the token provider which is the function that we pass as an input to the API client and we will use this function to retrieve the authentication token or the current user so if the token is not null we're going to pass it as the authorization header together with a request and we're going to use it as a beer token then we're going to check on the server side how we're going to validate the token but we're going to do this later on in the tutorial for now we just need to make sure that whenever we send a request we take the token and we pass it together with the request now let's go ahead and the next step of the Handler request is to receive the output from the server and to save it inside the response then the response will be a response object and we can take the body of the response to access the data that have been sent by the server this is going to be a string and we need to convert it into a map so we can just use Jon decode from the dart convert library and then we can check whether the status code is 200 which means that everything went well or not so if everything went well we will just return the body otherwise we will throw an exception then we can also check whether the request has Ed out meaning that it took to too long and we can just draw a custom exception for that otherwise let's assume that everything went well we return the body then we will take the body and we will save it inside the response variable and finally we will return that so essentially the fetch message method that we have in the API client will return the map that will represent the data that we have retrieved from the server but keep in mind that we will not use this data directly so we need to create a client side repository so that we can use the map that we have retrieved from the server and we can convert it into a list of messages so again it's going to be different from the message repository that we have in the server because the role of the repository in the server is to fetch the data from the database here instead we're just going to take the data that we have retrieved from our backend server via the API client and we will convert in a way so that we can use them directly inside the UI of our application so again here we add the API client and later we're going to add as well the websocket client as an input for the message repository then we're going to add two methods just like the other version of the message repository so we're going to add the create message that for now we'll throw an implemented error and then we're going to have the fetch messages so this is going to be asynchronous and it will eventually return a list of message objects so essentially for each map that we retrieve from the database we're going to have one instance of a message object here so let's add that let's import the models and then let's not forget to pass the chat room ID as an input because we will retrieve all the messages of only one specific chat room at a time then the implementation is the following so we use the API client we take the response and then we take the messages key value pair because remember that our server is going to return a map that will contain a key value pair so essentially the body is going to have messages as a key and then it will have a list of messages stored as a value for that specific key and actually it seems that there is an error here in the server because we are not adding the await keyword so we're not waiting for the fetch messages to be resolved so just make sure to add that in row 27 so that we can pass a list of different maps and not a future now let's go ahead let's take the list with all the different map so essentially each map is going to be a different message object and let's use the map method to iterate through each value in the list and to return an instance of a message object for each of them so simply we need to use the factory Constructor message fromjson finally we have the list available so we can just return it as an output for this method so perfect the message repository for now is ready so we can go ahead and we can start to use it inside the main do Dart because we need to create an instance of the API client and as well an instance of the message repository for now we're just going to create them as a global instance so that they are available everywhere inside the application but we're going to change the approach so we're not going to create them anymore like this inside the main Dart but we're going to have a more sophisticated dependency injection approach later on in the tutorial now for the sake of Simplicity let's just create an instance of the API client and let's pass a sample function for the token provider which will also have to change later on and then we're going to use the API client to create an instance of the message repository because we will need to pass it as an input for that and at this point we are good to go we have the message repository available and we can start to use it inside the chat room screen to retrieve the data okay so now we can finally start using the messages from the database and we can display them inside our application so let's go ahead and let's use the message repository inside the chat room screen so we're going to go inside the state object and we will create an initial list of messages so by default this is going to be an empty list so this is going to be our initial implementation however later on we will refactor this and we will use a bit more advanced State Management technique anyway let's go ahead and let's implement the load messages method and we're going to call this every time we run the init State function then here we're going to perform the following logic so we're going to use the message repository we call the fetch messages and we pass the chat room ID which is available because we pass the chat room object every time we create the chat room screen then we're going to wait for the future to be resolved and we're going to save the messages inside the list inside this method then we are going to sort this private list and we're going to order the messages in chronological order then we're going to take the newly sorted list and we're going to add all the messages inside the list of messages that we have available in the state object now keep in mind this logic will populate the list and then we can display them inside the screen then we don't need to make any further changes because the list view Builder is already set up to use the messages in fact as you can see here now we are using this variable from the state object and we're not using any more the data coming from the main do Dart however to avoid any mistake let's remove the sample data keep in mind that inside the chat room screen we will still need to import the main do data because we need to use the message repository that is being created in the main do data and as well the user ID which is still our coded and now we are actually using the data coming from our server however that's also why we are seeing this error essentially before we actually send a request to the server we need to start the server because we have not deployed the server on the cloud yet we just need to run it locally so let's open the terminal and let's run Dart frog Dev so that we can start the server in development mode now this will run on the local host and it will use port at80 so at this point we can hot restart and we will manage to connect with the server and retrieve the data and you can see that in the terminal in fact the logger middleware is confirming that we have successfully sent a request to the chat room in point and you can also see how the chat room ID in the terminal matches the one that we are passing to the chat room screen by using this instance or the chatro entity now let's go ahead and let's take a look at the UI because as you can see we can retrieve the data and now we are actually displaying those data on the chat room screen and now it's important to notice that the two messages displayed on the screen are not sent by the current user of the application and this is because I've added some sample data inside the project so essentially I created two different user us using the authentication Service that has been provided by suesa so now the user ID are different from the one that we have our coded inside the console now later on we're going to implement authentication but for now just create two different users as well so you're going to create these two different users here and then we will need to take at least one of the two different user ID and we will need to pass it as a user ID number one so that actually we have this list of users inside the chat room and the first one is going to be our currently logged in user although we have not implemented login yet and now you can see that again one message is on the left and one is on the right hand side then keep in mind that whenever you create a new user here a new user will be added as well inside the user table so if you go to the table editor and you take a look at the data inside this table in the database you will see two different entries and this will match the information that we have in inside the authentication Service and this happened just because whenever we created the table in our database we also set up the script to automatically insert a new user inside the user table based on what is inside the O user table created by default by superpa so again we have this function the handle new user and this just insert the data of the newly authenticated user inside our custom user table now let's go ahead and let's add more sample data for the other tables so that we can actually start to retrieve the data from the server now I've already added those data and that's why we can see the two messages inside the application but you need to do the same I need to add some data for the chat room for the messages for the chat room participant and so on and so forth but keep in mind that you will need to customize the sender user ID and the receiver user ID because these are the actual user ID that I have inside my project so whenever you will add some user into your project they're going to be different then as you can see I'm also adding an update statement at the bottom and this is because when we insert the first chat room there are no messages yet inside the database so we cannot set one of the message as last message of the chat room because the insert statement will not find that and it will break the primary for eny relationship so let's go ahead you can just copy the script which is available in the starter project and you can just paste it here inside the script editor then you can just go ahead and press the Run button and as soon as you do that you will see that the data are going to be appearing inside all the different tables so you can just go ahead and take a look at the messages so you can see the content column has been populated and as well the chat room so there's going to be one chat room and it's going to tell you which is the last message that has been sent then in the chat room participant you will see which are the user ID or the two different participant of that specific chat room and finally again if you want you can just modify the data even directly inside the database so for example if we change the content of one of the two different messages we can go back to the application and as soon as we out to reload you will see that the content of the message bubble will change so essentially we send another request to our server we retrieve the data from the database and we pass them through the server and then the server finally send the response to the client so make sure that your super based client is working fine and if there is some error probably you need to check your superbase URL and your super base service roll key so just make sure that in the environment file you have the correct values based on your project so again you need to have the correct superas URL and the correct superas service roll key and after that you should be able to retrieve the data from your database and to use them directly inside the application and at this point we can start focusing on implementing the web socket so we will start from the client side and so we need to use the websocket channel library in order to to set up and establish the connection between the client and the server so with this Library we will enable the two-way communication between the client and the server so as a first step we need to add the library as a dependency inside our project so let's go inside the root level prect DOL file and let's add websocket Channel at this moment 2.4.0 is the latest version of this library then let's go ahead and let's start to implement the websocket client class so with this class we will manage the websocket connection and this include also the logic to send and to receive the data from the server so let's go ahead and let's start by declaring this new class so we're going to call it websocket client and then the first thing that we need to do here is to declare the channel variable inside the class so this is going to be a type IO websocket Channel and essentially this is going to be the connection Channel between our back end and the client so this will be open from the moment we start the connection until we close the app or simply we invoke some logic to close the connection then keep in mind that whenever we initialize this class we will generate some logic and we will run it right after the initialization of the class so essentially we will create a few controller that will always listen from update from the server and then we'll emit some data whenever received from the server but we will do this later on for now let's just go ahead and implement the connect method which is the function that we can use to start the connection but keep in mind that we need to have this if statement so that we check whether the connection is already on when we call this connect method so that we don't try to open the connection again if it's already open then if that's not the the case we can continue and we can start the connection so in order to do that we just need to use IO websocket Channel and this will allow us to send an HTTP request to the server and we ask the server to upgrade this connection to a websocket connection so that it will be kept open throughout the session essentially then with this specific iow websocket channel. connect we can pass a URL and as well the header and with the headers essentially we will just pass the barer token which will then be verified on the server side so that we always check whether the user has the right to access the data that he's trying to access then we're also going to create this stream here so essentially we start to listen to all the data event that are sent from the server to the application which could be like a new message received or a new chat room has been created and so on and so forth then every time we receive one of this event we will perform some action but we will do this later on for now let's go ahead let's add onone and on error and here we're just going to add some print statement so whenever the connection is completed so we're closing the connection or whenever there is an error connected to this connection which could be let's say the user does not have internet connection anymore so the connection will close and it will generate an error now let's go ahead and let's keep the connect method as it is and let's create other two different methods one is going to be the send method with this one we can take some data and send them to the server so in this case whenever we want to create a new message we will create the message object and we will convert it into a string and we will send it as a payload to the server then the server will perform some action and we'll save the data now before we send a message we will apply the same logic as we did before so essentially we just check whether the current connection is open or not if the connection is open we send the message using channel. sync. add then finally let's create the disconnect method which we can use in order to close the connection from the application so we just use channel. sync. close so perfect this class is pretty much good to go for now so we can go ahead and we can start by creating an instance of the websocket client inside the main do Dart so again just like the API client we're going to create it here as a global variable but later on we're going to change the way we are creating this dependency and we are injecting it inside the widget tree now let's take this and let's start to use it inside the message repository so we're going to import it and then we're going to add it to the class Constructor after doing that we can use the websocket client to send some data to the server inside the create message method that we have available here inside the message repository so this is going to be a synchronous and it will take an instance of the message class so the newly created message as an input then what we do here we take the websocket client we use the send method and we just pass the new payload so the payload will have two component so the first one is the event name so in this case we are creating a message so the event name is going to be message. create and then we pass the actual message which we convert into a map then remember the payload should be a string so let's wrap it with double quotes and let's make sure that we add the bracket around message to Jason and at this point we have the payload and we can send it to the server and now we can start to use this new method so we will need to go to domain. Dart because there is an error to fix essentially the message repository now need the websocket client and then we can start to use it inside the screen so let's open the chat room screen and let's make a few changes inside the state object so as a first thing we're going to add new method which is a private method for the state object so the send message so we will call this by pressing on one of the two icon button that we have in the bottom part of the screen anyway with this send message we will take the value that is stored inside the message controller and we will use it to create a new instance of a message class now we need to change this a bit so the ID will not be part of this class because we will pass an empty ID and it will be generated ated on the database automatically then we will pass the sender user ID and the receiver user ID which are currently just coming from the hardcoded ID from the main. dart but we can retrieve them from the chat room variable then we need to fix the property name and we pass the text as content then we pass the date time and we can just send this so what we do actually before actually sending the message is to add the newly created message to the list of messages that we are displaying on the screen so that the UI is updated with no delay then we use the message repository and we send the message to the websocket finally we use the clear method to remove any text from the text form field by clearing the controller now if we press nothing happen because we need to take the send message method and we need to add it inside the onpress call back or the icon button so let's copy that and let's scroll down then inside the second icon button so the one that we are using for the suffix icon we can just call the method and there's no properties that we need to pass as an input however keep in mind that if we try to send the message now it will appear on the UI because we are adding it to the list of messages as you can see here but the message is not being being sent to the database and the reason is very straightforward so we have not set up our backend yet in practice we don't have a websocket Handler yet to listen to the messages they were sending from the client to the server and therefore whenever we send data we are not sending any data but actually we are not even starting the connection because we have not implemented that inside the client so we will need to call the connect method from the chat room screen but first let's go ahead and implement the web socket in the back end so we can continue using dart frog and we will add a new route in our server that will handle all the incoming messages for the websocket and it will as well handle all the messages that we will send from the server to the client before we do that we will need to implement this new dependency so let's add that and then we can use the websocket Handler which come from the dart frog web socket package and we will just follow the example here in order to implement it so we can copy this on request function and inside here we will handle the logic to receive the message and perform some action and as well the logic to send messages back to the client that are connected now let's go ahead let's open the terminal and let's shut down the current server which is running on the local osta and let's add the package so we're going to run Dart Pub ad dart frog websocket then let's just make sure that the package has been implemented and then let's go ahead and open the server again so let's run that frog Dev to make sure that the server is up and running then we're going to open the routes folder and we're going to change the index. dart to websocket doart so we're just going to use WS so this is going to be in the bottom level of the Roots folder and then we can also delete the test because we will not use them and now back in the websocket dart let's start to implement our websocket Handler so we're going to make all the changes as they are described here inside the documentation so we can just copy the on request and as you can see we have a Handler so this is going to use the websocket Handler from the library and then we will just react to the connection request so the server will receive an HTTP request from the client and it will eventually upgrade the connection to a websocket connection so it will be kept open throughout a longer session then here we do the following we take the current channel so the connection that we have between the server and the client we look at the stream we listen for incoming messages and then for each message that we receive we will just print the message inside the back console and eventually we also send a response and the response will just send the same message back to the client now let's go ahead and let's take a look at whether this work but obviously this won't work because on the client side we have not activated the connection yet so in order to activate the connection we will need to use the connect method that we have inside our websocket client and we will need to call it whenever we are using the chat CH room screen because so far we only have that and we're going to use the connect method inside the init state of this chat room screen so let's go ahead and let's make those changes so that we can actually see the connection in action between the server and the client and we can try to send the messages so let's create this start web socket method inside the chat room screen and we're just going to use the connect method that we have created before then we will need to Define which is the URL and as well the headers but keep in mind for now we're just going to add some our coded string for the headers so this is the URL to which we need to send a request so WS Local Host at80 WS so again we're not going to use HTTP because we're trying to establish the websocket connection and then the ending so WS is just the name that we have for the route then again we don't have have authentication yet so we will not pass the actual token of the current user because we don't have it but we will change this later on for now let's just go ahead let's use this newly created start websocket method inside the it State and then we can see whether everything is working so we will just need to outro start and then we will see in our the back console whether we are starting the connection now let's go ahead and let's take a look so as you can see we are printing connecting to the server which means that we are actually starting the connection at least this is what is getting print and then whenever we send a message you can see that the message is actually being print inside the logs of the server so whatever we are passing as a payload with our request is now appearing here and if you send another message you will see that the content that is getting printed here so the value of the content key is different from before and it matches the message that we have written so perfect it seems that this is working so we can successfully send the messages from the client to the backend server okay so currently we can take a message from the UI and we can send the data to the backend server so we simply pass the newly created message to the message repository and we call the create message function with this we create a payload and we then send the payload to our websocket client in practice we are using the send method in order to move the data from the client and send them to our backend server so with this we're going to add the data to the channel and then we're going to print this data inside this websocket Handler however we have not implemented any logic yet in order to take the data and to store them in the database so to create create a new row for a new message inside the database so what we need to do here is first we verify If the message receed from the client is a string if it's not a string it means that there is an error inside the payload so we're not going to continue executing the logic then if we receive the string we're going to use Jon decode in order to convert the string into a map because the message will have key value Pur then we're going to extract an event and the data and the value that we're going to find in the event key will Define which is the next step that we're going to perform so in this case the event value is going to be message. create so we're going to continue and executing the logic for that specific event and the data that we're going to use are going to be the one coming from the data key value per so in order to implement this we're going to use a switch case statement so essentially we take the event and we're going to check whether the value match the case that we have here in row 22 then keep in mind we're going to have multiple other option later on as we keep implementing and creating this application so if the value is message. create the first thing we do is to call the create message method from the message repository now this is the server side message repository and remember that we have access to this repository because we have created an instance of the message repository in the main Dart of the frog project and then using the middleware we are providing access to that specific instance to the request context so now we can call it using context read and we can use it here but remember we have not implemented yet the create message method from this repository so we need to implement it otherwise we're going to have this unimplemented error so what we do here is first we modify the return type and the input of this specific method so this will return a map and essentially this is going to be the message that we retrieve from the database after adding the message so when we add it we don't have the ID but when we retrieve it we have the final version of the message which include as well the message ID then we're going to implement this asynchronous method and we're going to have a TR catch block now in the tri block we will use our superp base client and we will Target the messages table to insert the new data so in practice this new action will add a new row to the table then we will use the single method to retrieve that specific Row from the database and this way once we insert the data we retrieve the row and then we can return it as a response so in this way we can have this response available inside our websocket Handler so now going back to the websocket Handler here we can start to use the create message method and then as soon as the future is resolved we retrieve the data which is the message that we have retrieved from Supa and we will use it to send a message from the server to the client so we do channel. sync. add and we can just pass the message inside the new data event that we're going to emit from the websocket so here we specify which is the type of event that we are sending and in this case it's going to be message. created because the message has been added to the database so it's been created over there then we just send message as data however you need to keep in mind that for now the message and the payload that we are creating for this event is going to be sent only to the user who created the message in the first place and this is because we have not implemented yet the logic to send the message to multiple channels connected to the websocket at the same time so we will need to apply some more logic and we will need to create a list of connected users so we will do this later on so we will come back to this so let's add a too for that so we will choose which are the client who need to receive it so essentially the clients who receive the message are going to be the users who are participant of the specific chat room in which the message has been created so far we don't have the logic so keep in mind it's important that later on we come back here and we add that for now let's keep it as it is and we can move on to the next step because now we can create a new message we can send it from the client to the server and we can save it inside the database and send it back to the client however we need to implement some logic inside the client to listen to the message messages that are coming from the server in fact here we created a too so we need to create the controllers to receive the data inside the websocket client so we're going to have multiple controllers here for the different type of event that we can receive from the server so the first one that we're going to create is the message controller so every time there is a new message sent from the server to the client so in practice every time there is a new message added to a chat room we will add the data that we receive inside this message controller then we will expose a stream and we will use the stream coming from this message controller into the message repository and then we can perform some action every time there are new data emitted by the stream so that we can display the new message inside the UI but now we need to initialize this controller and we're going to do this right after we create the instance of the websocket client so we're currently doing this in the main Dart and after we create the instance we call the initialized controllers and with this method we can just instantiate all the different controllers that we're going to have here so far we just create the message controller we use stream controller. broadcast and this will enable multiple listener to listen to this controller at the same time if that's needed now let's go ahead and let's go inside the connector method as you can see here we have the channel stream listener where we listen to all the events that are sent from the server to the client now when we receive an event we're going to apply some logic so first of all we decode it because it's a string and we're going to convert it into a map then we will access the key value purs that are stored inside this map and similarly to what we did on the server side we check what's the re received event and then we will act accordingly for instance if the event is message. created we access the data and then we add it to the message controller now keep in mind that we can have multiple if statement here so we can handle different event that can be received from the server for now we just have message. created as an event that can be emitted by the server and sent to the client so later on we will come back and more logic to handle other potential events that we will create now let's go ahead and let's ensure that the stream that we have created is exposed so we have a stream controller and we need to expose the stream so that the message repository can use it so it can access all the data that are received and then can use those data in order to display the messages in the UI so we're going to have these messages update and finally before we move ahead and we go make changes inside the message repository let's also close the message controller every time we call the disconnect method because as we disconnect we will not receive any more data and we don't need to keep the stream open now let's go in the message repository and let's create a stream subscription and this will help us to listen to all the data events that are going to be emitted by the stream that we just created inside the websocket client so let's go ahead and let's use this newly created message subscription in order to use it we're going to create two methods inside the client side message repository we can use the first one to subscribe to the message update and keep in mind that for now we will subscribe to all the message updates regardless of the chat room ID but we could implement this in a more sophisticated way so that we listen only to the updates coming from one specific chat rooms and we're going to do this later on for now let's just implement it as it is so this will take a function that will handle the data transformation that will apply to the payload that is received from the websocket so every time a message is received the function will process this data and in this case it will create an instance of a message class here we take the websocket client we use the message updates and we will listen to all the events that are going to be sent from that then we're going to store this inside the message subscription keep in mind that each data that we're passing here is going to be a payload from the websocket then let's go ahead and let's also add unsubscribe from message updates so we're going to use this to cancel the subscription whenever we don't need to listen to it anymore so essentially whenever the user move from the chat room screen to the chat listing screen because we will not listen to the updates anymore for this specific stream and at this point here we're kind of good to go for now so let's just go ahead inside the UI of our project so inside the chat room screen and let's start to implement the new logic here so first of all we're going to remove the set state that we have here so we don't add the message that we create directly into the list so we will only add the message after we receive it back from the websocket so we send a message we save it into the database thanks to the websocket and then at the point the websocket will send the message back to the client and once we receive it we're going to display it on the UI and to do so we need to start listening to the message updates so let's go inside the NIT state so essentially every time we add the chat room screen to the widget tree we're going to call the message repository subscribe to message update method and we're going to start listening to the stream of data that potentially can come from the websocket then we need to pass a function because we need to Define how we want to transform the data that we receive we are just going to access those data we're going to use them inside the message. from Jon Factory Constructor and then we check if the message chatro ID is equal to the current chatro ID we will just add that message to the list of messages that we are displaying on the UI keep in mind that we can remove this if statement later on when we're going to add the chat room ID directly to the subscribe to messages update then before we rebuild the UI by using the set State we're going to sort the list of messages to make sure that they are sorted in the right chronological order now we could try to send a message but it won't work and there are mainly two different reasons the first reason can be found by looking in the terminal so as you can see we are passing the wrong payload so what the web socket is expecting is different from what we are sending so far we're sending a string that once decoded contain one key value pair where the key is the event name and the value is the data now we're going to convert this because the websocket is expecting a different value so as you can see here our event need to have a event key and as well the data key so the event name will be the value of the event key while the actual message will be the value of the data key so let's use this new logic that is in row 22 so we will pass the event and the data as keys and then we will pass the message to Jason as a value for the data key then we take the payload which is a map and we encode it in this way it's going to be converted to a string and we're going to send it to the websocket so now it will match the expected data form that the websocket is getting then there is another issue and you need to make sure that the sender user ID and the receiver user ID which are currently hardcoded in the main do Dart you need to make sure that these are actually matching the users that you have in your database so you need to find two user ID coming from the database otherwise there is going to be an issue between the foreign key and the primary key because our websocket will try to add a new message inside the messages table however he will try to insert the value of the user ID of the sender and the receiver and if these do not exist in the users table there will be an error because these are connected to values in the user table so it must exist over there and it will be the same with chat rooms so the message that you add need to have a chat room ID that matches one of the different chat rooms that are available inside this specific table anyway once we are good with that we can go ahead art and we can try to send another message and at this point it will work fine so we will send the data to the server the websocket will take them and will send it to the database and finally it will send the data back to the client so that we can display the data in a new message in the UI and finally you can see that this is now working fine because the messages are being displayed inside the UI after we use the create message method and again this is all because we are listening to the updates coming from the websocket and you can also see that the events are being logged inside the terminal thanks to the request logger middleware then if we open our database and we go inside the messages table you will see the new messages appearing here so whatever we have typed inside the UI now it's recorded here with the Cur correct user IDs and with the correct chat room ID so so far we can send messages and we will receive the message back from the websocket however there are still a lot of features that are missing in the project so for example we will need to add authentication and as well we need to enable the websocket to send the messages to all the other users that are supposed to receive them and we're going to start to implement that in the next part of the tutorial
Info
Channel: Max on Flutter
Views: 15,157
Rating: undefined out of 5
Keywords: full-stack dart, full-stack flutter, flutter tutorial, flutter project, flutter dart frog, dart_frog flutter, flutter backend, flutter server, flutter database, flutter full-stack, flutter websocket, flutter real-time, flutter chat, chat app, chat backend, chat application flutter, real-time messaging flutter, messaging chat flutter
Id: by44X7SwYx0
Channel Id: undefined
Length: 119min 20sec (7160 seconds)
Published: Wed Nov 29 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.