Supabase & Riverpod Mini course | Build a Realtime Photo Sharing App

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
[Music] Hello friends Jermaine here and welcome to this mini course we're going to be building a real-time photo sharing app running on Super Bass of our back end with Plata as our client and riverpod as our state management solution super bases on open source Firebase alternative which has support for most of the features that come with Firebase such as authentication Cloud functions real-time subscriptions Etc and in terms of the database solution Super Bass runs on postgresql let's take a look at what we'll be building over here we've got two devices running I'm currently logged in my Android device let's go ahead and add a memory by pressing the floating action button at the bottom right uh give it a title and I'll select an image at which point we see the file name of the image and then we'll submit upon submitting we see that the image is added as an entry which automatically shows up on the iOS simulator via real-time update I can like this entry which also updates on the simulator and then of course we can make edits by pressing the icon in the middle and then having paragliding for fun let's log in via the simulator and now that we're logged in let's also like this image and then let's add an entry and let's select this image of the puppy then we can like this image and then I also like it from the Android device I can delete this entry which also updates the simulator into logout we'll press the icon at the top right which displays the login at the bottom all right let's get started it would be useful to describe how we're modeling our data in our database we have three tables that we'll be creating the memories table represents our entries each entry would have a title an image ID as well as a foreign key constraint pointing to the profile which created this particular memory and if we look at our profiles each profile is added automatically whenever a new user is created and the reason why we need to do that is because our author uses is not directly accessible from our app so then our public profile ID points to the ID in our all the users table and then the username will be extracted from our raw user metadata which is a Json serialized string Under auth.uses and then each column would have created a timestamp for all of them the likes table contains details of each memory that we like and who liked it there's a foreign key constraints on the memory ID field which points to the ID of the memory that was liked and also has a profile ID here pointing to the user like in the memory all right so in a new tab we're going to go to database.new which will bring us to this create a new project screen we'll set a name for our project and then for now we just generate a password I'll select the region and then click create it will take a couple of minutes to set up the project once our project is initialized let's go ahead and create our Tables by clicking on table editor and then we'll click to create a new table this will be our profiles table the ID will be of type uuid and then we'll edit the foreign key relation which will point to our auth schema and in particular the users table and the column will be users ID and then under the action we will Cascade so that whenever we delete a record from our users table or also remove this record in our profile let's go ahead and save our creator.column will be a timestamp and then I'll click the settings icon and untick this one we'll add a new column for our username which will be of type text and then I'll untick this one as well which is because we want these values to be defined all right let's save and there we have our table created with our columns let's create our second one this will be our memories table and we want to enable real time I'll leave the ID field as such and then just ensure that this is ticked for Crater that we don't want it to be nullable and then we'll add three more columns which will be for our title our image ID and then the profile ID our title will be of type text and it's not nullable same as our image ID profile ID will have a foreign key relation points into our profiles table and in particular the ID and then when the row is removed we want to Cascade let's save and before we save we'll untick that and then let's save all right then lastly we want to create our likes table we'll also enable real-time we'll untick this one and we'll add two new columns these columns will be our memory ID and our profile ID memory ID will be an 8 by integer and take this one and we also have a foreign key relation pointing to the ID of memories and then under action we'll click on Cascade let's save that and then our profile ID will be a universally unique identifier and then untick this one and Save okay so now that we've got our tables what we need to do is to define the relevant policies for each of our tables so that they're accessible from the client we'll come under authentication and then we'll click on policies and then for each of these tables we see no policies are created yet let's add a new policy for our likes table we'll select from a template and then we'll select enable read access to everyone and then use this template leave everything as is and then click review and then over here we see the SQL statement that will be written to create this policy and then we'll save and then we'll add a new policy and for this one we want to create from scratch and the policy name will be enabling all actions for users based on their profile ID and the operation will be all Target rows I'll select authenticated and then the expression here we want to check that the authenticated users ID is equal to the value in our profile ID field so also the uid is from Super Bass which contains details of the currently authenticated user and therefore we need to match that against the profile ID that will be sent across and then let me copy that and paste the same in here we can review and then save and we pretty much need to do the same for these two for profiles however our second policy will also be based off a template and we want to enable delete access for users based on their user ID so we use this template and then the target roles will be authenticated and we want to check it against the ID column we'll review and then we'll save since our auth users table is private and we are using profiles as a means of accessing user data we essentially need to create a trigger which will populate our profiles table when our user is added so what we'll do is on the database we'll click on functions and then we'll create a new function the name of our function on the new user schema will be public the return type will be a trigger and then in the definition here we want the following logic what we're doing here is with each new creative user we are extracting the ID and then the username which is passed in as raw metadata and we're using that to populate the ID and username Columns of our profiles table and then we need to make sure to return new and end our statement and check show advanced settings and then the type of security we want a security definer and then we'll confirm and then once that's added we'll come to our triggers and then we'll create a new trigger the name will be on earth user added and then the table we're watching is our both users table the events in particular we care about is the insert operation the trigger type will fire after the operation is completed and then our orientation will file for each processed row then we'll scroll down afterwards and we'll choose our function to trigger which is our Honda new user function and let's confirm and then we see it here lastly we need to configure our storage bucket for storing the images we upload will come under storage and then we'll create a new bucket we'll call it memories and then we'll make it public and then create our bucket we need to configure the policies for this bucket we'll add a new policy and we'll choose from a template and then we want to give users access to their own top level folder named ostia user ID we'll go ahead and use this template and the allowed operations are select insert and delete we'll click to review and then we'll save and now our storage bucket is set with the correct policies so onto the code base over here we've got the setup for our memories flutter app which has the relevant packages installed including Super Bass flutter and flutter River pod among other packages this setup can be accessed in the startup branch on the GitHub repository in our main.dots file we are initializing Super Bass and also initializing riverpod by wrapping our my app consumer widget in a provider scope before we run this up let's retrieve our URL and key from Super Bass in our dashboard we'll come to our project settings and then under API we'll copy over the project URL and then we'll copy over our public key I'll save that and then let's run this by pressing F5 we've got the relevant screens set up already where this is our main home screen and then when we click this we come to our login screen which also has the option to create a new account should we create a new account then we're brought to this verification screen and then once the account is verified we're back to this home screen at which point will be logged in the first journey we're going to look at is the Journey of creating a new account and this is where our onboarding feature comes in so over here under onboarding we'll create a new file we'll call this file onboarding repository which sits under the API directory we'll start by creating a class called onboarding repository this class will have a client field which holds the reference to Super basis client object let's import Super Bass flutter and then this will be followed by two methods the sign up method which requires the following Fields our email password username and then in here we're going to return the results of clients.auth DOT sign up which accepts our email and password and to pass in the username we need to define the data named argument which is a map passing username master key and the value will be our username that we pass in we'll also Define a verify code method which accepts the email address and the verification code sent to the user's email and over here we'll invoke client.auth.verifyotp we'll pass in the email then the token will be the verification code and then the type will be OTP type DOT sign up and to make this available to our widgets we need to expose this via provider so to do that we'll Define this function called onboard repository which returns an instance of our onboarding repository and since we're using Code generation with report I'll add The riverpod annotation here and be sure to import ripperport annotation and be sure to add an underscore here since we're not going to be using the rough object that we passed in we need to Define this part file once we've got that let's run the command to generate our code which is flatter pub run build Runner build you should see this file added over here and then in order to use this provider we'll first need to export it and then we'll come to our auth folder under view click on login page which is this screen over here now on our login page we've got the button to create our account which is this widget being rendered here at the moment it directs to verification but then instead we want to check that our current form is valid and if so we create an account and if our current form is not valid we turn on auto validation and then our create account method is defined here we'll start with a try catch Block in this try catch block start by enabling the submitting flag which will disable the buttons and the form Fields while the submission is going on and then seeing that we are in a consumer State we can access our onboarding repository provider and then invoke our sign up method the email address is from our email controller which contains the value for the email address form field I'll do the same with password and then username let the sign up succeeds then we'll check if our current screen is still mounted before we do a context dot push redirecting to our verification screen if this fails then we display the error to the user by showing a snack bar message and then lastly we want to reset the submit state to false before we pass in our username we also need to ensure that the information that we've entered such as our email password and username is also passed onto 2D verification screen so before we enter the username in the form here all we need to do is to come to our verification page widget which is under onboarding view verification page and you can see here that this page requires the email to be sent across and we also need the password and username details since those details will be required to enable us to resend the code to the user's email we're going to scroll up here and then we'll define a class this class will have the following Constructor which requires the email password and username fields and then in our verification page we're going to require this whereby params is an instance of verification page params and in order to work with this we'll come to our routes dot dots file which is on the call we need to refactor this logic and here we can retrieve the params object by creating a variable called params and then extracting our verification params object from State DOT extra and then we'll have this logic to ensure that our verification page params object is defined if so then we can pass it here like that and to make sure that we've passed the verification page params across we'll return to our login page and over here when we do context.push we need to define the extra named argument which will accept an instance of verification page params with our email password and username let's save this then let's restart the app come to login we'll pasting the email and then we'll create a password and we'll set a username let's create an account and there we go and now that the verification code is sent we can come to our email and then opening our email inbox we're giving this following message follow this link to confirm your user with a hyperlink which attempts to access locals 3000 with an access token given our use case we expect a verification code instead of a URL fortunately we're able to change that so if we come to our dashboard come to authentication we see that our user we signed up with has been added but then we want to come under email templates and then over here and confirming our sign up this is what it's sent so far with the confirmation URL we're using the confirmation URL token however what we want is the dot token placeholder which will give us the six digit numeric email one-time password so I will select all of this and then replace it with this HTML containing the top can and then let's save this once that is saved we will return to our authentication and also just to confirm that our trigger to populate our profiles table worked we can come to our table editor click on profiles and then we see our entry added here let's return to authentication and let's delete this user which should automatically delete the row from our profiles because we set it to Cascade and then we'll come back and let's create an account again if we check our inbox now we've got this verification code which is 280 382 however we've not implemented the verification yet so let's look at that we'll return to our verification page and then over here instead of the email placeholder the email from our widget or params and get rid of this const okay so now we see this email here and then on submit we want to invoke the verify method let's look at our verify method we'll have our try catch block then we'll invoke the verify code method passing across the email and then the verification code from our text editing controller which is passed into our text form field over here upon successful verification or display a successfully signed up message and then redirect to our home screen and then if this fails set is submitting to false and then we'll display this alert message and here we'll capture our error and then do tostring and let's move this const over here so should we attempt to verify then we're redirected here to the home screen read the message that's successfully signed up let's look at the failure scenario I'll return to the authentication section and then I'll remove this user then I'll stop this app and remove this up let's run this again let's go to login and then enter the temporary email address add a password then create our account so if we put in a wrong verification code and submit yeah then we get this message here and then we've got the functionality to resend our verification code so let's Implement that I'll have the same try catch block with our submit States and then we want to invoke the sign up method again with the email password and username and then if we're successful we'll have a similar outcome over here without this context.go and if there is a problem and we need to be sure to reset is submitting to false at the very end so let's try and resend the code okay so we got this message called resent and if we attempt to resign the code again we get this auth message which is because the code is resent every one minute so let me go to my email and then enter the code and then let's submit made a mistake let's try again and there we go we've successfully signed up before we move on let's refactor how we're displaying the snack bar messages because at the moment it's a very repetitive having to do all of that under the core directory uh Creator utils dot dots file and I'll make sure I export it over here and then in our utils what we're going to do is we'll create an extension on build context and be sure to import flutter material our extension method will be called show alert which will return a scaffold feature controller which is what this method returns we're going to copy all of this into here return it like so and then context will be this since it refers to the instance of our build context and then it takes this message passes it in here okay so now that we've got our extension method we can simply do context dot show alert and then we'll show this message like so and then we'll do the same over here and then we'll do that here as well and in here and also in our login page right here and then let's make sure to import the core dots file which exports our details next we need the UI on this home screen to reflect a user that is logged in which involves hiding this log into the out button and displaying The Floating Action button on the bottom right as well as the log out button at the top right and before we do that we need access to the authentication state which Super Bass gives us access to we're going to implement our auth repository so under auth I'll create a new file and here we'll create our auth repository class we'll also pull in the relevant Imports in our auth repository class we will retrieve a handle to Super Bass client and also Define a getter called alt state which points to this on Earth State change stream that we can listen to and then lastly we'll Define our logout method where we invoke where we invoke client auth sign out and then to use this auth repository we need to Define our provider and let's define our part file and we'll run our code generation which gives us this file here our export this file since our auth state change is a stream we can listen on this alt State using a stream provider and we'll go ahead and do that by creating a new directory under auth this will be our providers directory and then we'll call it auth user who have our relevant Imports and then we'll Define our stream provider like so let's add our parts file and run our code generator and then in here we'll Define our auth stream read from our auth repository provider and then we're listening on our auth state then we'll use our weight for to retrieve our auth state pass along our stream and then we'll yield the auth state session user so what that means is that when we log in our auth state receives the logged in user that we can retrieve from the session object and then to utilize the stream provider we'll scroll to our memory directory under View memory page name here we can retrieve the user doing a watch now all the user provider and then we need to be sure to export that from our auth dot dot file like so you can import that and then we want the stream as data and then we'll extract the value so this means that whenever our off State changes riverpod will notify this widget and this widget will update accordingly so over here in our actions you want to check if the user is defined so it's not now and if that's the case then we'll display this icon button and let's remove this bit this icon button will invoke the logout method on our auth repository provider when pressed so let's save this which gives us that and for our position widget containing the login button we want to have this in a conditional as well so if the user isn't defined then we want to display the button or else we don't show it and also before the closing scaffold we want to display The Floating Action button if our user is defined so we'll have a ternary if user is now then we just have now or else we'll have our Floating Action button and then the child will display the add icon to confirm that our Logic for the user Works click this button to log out Ridge brings us back to the default State let's Implement our login functionality so when we click on login to the app should be able to enter our email and password with the user that we just created and submit we'll come to our auth repository and then we'll add a new method here called login which takes in an email and password where we invoke client auth signing with password let's save this come to our login page and then over here in our login method similarly we'll have a try catch instead of submit States and then invoke the login method in our auth repository provider with the email and password credentials and once we're logged in we will pop the current screen which should which will bring us back here or else we've got an error and will display the error message in our catch block in order to invoke this login method we will come over here to our submit widget in here roll out the similar logic to our create account flow and then we will invoke the login method so should we try to submit then we get this error and then once we enter the correct credentials and submit and now we're logged in and we can log out by clicking that that's pretty much it as far as the login goes and now we can move on to our memory items before we implement the UI for each of our memory items it's worth having some data in our database I'll come back to our dashboard under our authenticated users I will copy this user ID I'll come to our storage and our memories buckets and then I'll create a new folder the name of the folder will be the user ID and then I'll select this folder and upload an image once our image is uploaded I'll copy the name of this image and then let's come to our table editor our memories table and let's insert our first row we'll give it a title image ID would be the name of the image that we placed in the storage folder and then the profile ID will select we'll select this user and I'll save so now that we've got our first entry let's return to the code to pull in the information we just added to our database under the memory feature I'll create a new file called memory repository we'll create our memory repository and create our client variable pointing to Super based clients let's import Super Bass then we'll create this method called get memories which will pull the data from our database in order to do that we need to do client Dot from which allows us to perform a table operation on our memories table and then we're going to do a DOT select and in here we set the columns we want which is our ID title created our image ID since we have a foreign key constraint on the profile ID we can reference the profiles table like so and then we'll pull in the ID and then the username and then we're gonna order the results based on the created value and then we need to cast the select operation to this type so let's save that and then over here we'll Define our provider and then we'll import Riverport annotation add our path file and then run our code generator once code generation is completed we'll go ahead and create our Notifier which will use this repository to load the information from our database under memory I'll create a new file under provide us and then we'll call this memory Notifier or import our relevant dependencies and then we'll create our memory Notifier which extends this identifier which will be code generated India will have to override the build function and then we're going to read from our memory repository provider and invoke the get memories method so let's save this and before we run our code generator we need to export our repository over here and also export our Notifier and then we import the memory.darts file we save this and then we run our code generator okay that looks good and now that we have our async Notifier we need to create a widget and we'll do that in our memory feature under widgets we create a new file and we'll call this memory list View we'll create a stateless consumer widget and then we'll sort out our Imports and then in the build method we will read from our Notifier provider and then over here we're going to do memory notify.1 when it's in the loading stage we want to display the circular progress indicator when there's an error will display the error text and then once we're successful we can render our masonry grid view this is from the flutter Target grid view package we can import it there we go we're going to set the following options for the cross access counts and the spacing and the number of items is based on the length of items from our database and then we'll Define our item Builder function for now we'll return a list tile widget we will set the title the title from our database let's save that we will export the widget here and then in our memory page we will have a positioned dot fail widget with our memoryless view and be sure to import the relevant file let's save this and then we see that our entry is here to save us from working directly with map objects let's create a data class that we can use to encapsulate the data coming from our database if we come to our memory repository here I added then callback without Json information let's print out our Json and then let's return it like so I'll do a whole restart and then look in our debug console so this is what we get this means that we can create our model class which will hold this value and the memory and create a new file under models and then call it memory we'll Define our memory class with our Factory Constructor and then we'll have the following Fields learn for profile ID and username since it's within the profiles map we need to Define these two functions to effectively detail how we retrieve these values and then we'll Define these functions up here like so and then before we run code generation we need to Define our from Json which looks like that and now we can run our code generation there we go we got these files over here let's refactor our repository to return that so in here we will return list of type memory and then in here we'll map over our data for each map item we would produce a memory data object and let's export our memory data class in here and then sort out our Imports save that and then we'll come to our memory Notifier and then let's run our code generator again let's come to our memory list View and then over here we can do the title at this point I'll stop and let's start the app again okay let's build out the look and feel of our memory items I'll create a new widget called memory item View and let's create a stateless consumer widget we will require the memory information and then we'll render a card with the following for the child we'll have a stack widget let's render out our image we need the path to the storage URL which Super Bass exposes you can come back to our memory repository and then over here we are the following which is a getter to storage URL provided by client and then in here you can retrieve the storage URL like so and then have it here and after our storage URL we need to look in the object public directory and then the name of our storage object is memories and then we'll follow this by the top level folder name which is the profile ID followed by our image ID which is the file name so let's save this and then we'll export this file and then we'll return to our memory list view in this list tile which it will replace with our memory item view which takes in data and let's save that which gives us our image here we'll return to our item view where you can display the username at the bottom right with a position widget we will have a start container and the child will be a text widget so let's say this and we can display the title at the top left another positioned widget this will take a container child with this text widget which displays the title let's save this let's log into the app once you've logged in let's implement the form for adding a new memory item so this form will display when we click this Floating Action button we'll create a new extension method under our core details this extension method will be responsible for displaying the model bottom sheet from the material Library let's save this and then we'll create a new file under widget we'll call this memory item form we'll define a consumer state for widget we'll have the following State fields in here we'll have our form widget with our column and the title of our form let's save this and let's return to our memory page and then down over here we want to do context.show bottom sheet and then for our child we'll have a padding widget which renders our memory item form and let's export our memory item form and once we click on that we have our form let's continue to add the rest of our fields we need our title field which is a text form field and it's got the following validation at the moment is just required let's save and next we need to add some spacing followed by our file upload field which was in the starting files for this project takes the following options let's export our file uploader then import our library we need to create this field in our state for the file and then for file we'll import dot IO and save that so now we should be able to select an image once I click on that and then pick an image we see the file name for that image which is retrieved from this file object and then let's have our submit and then we'll repeat this and have an outlined button that says delete and we'll add a little bit of spacing and when we hit submit we want to ensure that the form is valid before submitting so if we save this and we hit submit we need to enter a title and once we enter a title then we should be able to freely submit it we'll come to our memory repository and then we'll implement the relevant methods that will allow us to add update and delete memory items the first method takes the following information the title and then the file object and we'll import that IO the second method will be the update memory method which will take in the memory ID and the updated title then lastly for this will be the delete memory method which will take in a memory object and will perform our daily operation when adding a memory we'll be performing two operations we'll be uploading the image to our storage bucket and then secondly we'll be inserting our record into the memories table let's retrieve the profile ID from our session by doing client auth retrieving the current session then doing user.id and then we'll extract the image ID which is based on the file name to ensure that we have the profile ID we'll have a check if it's null with run exception or also add our entry to the database by doing client.form and then our memories table and then we'll invoke the insert method we'll set our title image ID profile ID columns with their relevant values which enter insert statements let's remove this question mark because it's record quiet once our memory item is added we can proceed to uploading the image by doing client storage or from and our storage bucket is called Memories and then we'll invoke the upload method our path is based on the top level folder which is based on our profile ID followed by the name of our file which will be the image ID and then here we'll provide the file object that we passed through let's save let's return to our memory item form and then over here will invoke the add memory method let's define it over here we'll have a try catch block and then let's ensure we have our file object or else we end over here and that will trigger the add memory method pass in the title and then our file and then in our catch block we can do contacts dot show alert and let's import the core Library however before we display the alerts we need to collapse this bottom sheet or else this alert message will not display and also when we are successful in adding a memory we also need to collapse the bottom sheet so let's create this method over here called pop View and then if it's mounted we'll do a context.pop and let's import go router we can use that method over here and over here so that should we submit this form I hit submit it's collapsed and it should be added in our database let's check and then I see that it's added over here if we come to our table editor and look at our memories we see our second entry over here all right so we've confirmed that our entry was added to the database however we cannot see that here unless we restart the app at which point we now see that here it will be useful to implement a subscription which will notify the UI of the new entry that we've added to our database so that the UI will update automatically without us having to restart the app fortunately Super Bass supports subscriptions to implement that we'll do the following we'll return to our memory repository and then we'll Define a getter called memory channel will invoke the channel method on our Super Bass client and then we'll give it a name call it public memories after our table name I'll save that and then we'll return to our memory Notifier and then in here we'll Define a method which will initiate our memory channel in this method we will retrieve the reference to our memory Channel and then we'll listen for changes to our postgres database by invoking the on method the type is an enum and we want real-time lesson types dot postgres changes and then our filter accepts a channel filter we specify the events and we'll use any event the schema is public and the table is our memories and then in here essay callback that will be invoked whenever events take place for now let's print out the payload that is returned containing the event information and then let's not forget to invoke the Subscribe method and then lastly we will invoke this method before we retrieve our memories save this and then do a hot restart let's add a new memory when we look in our debug console we see our event is emitted this contains information about the schema in the table that we specified in our Channel filter what we care about is the event type over here so this is an insert operation and then new contains the information that was inserted we can use this information however to simplify things we can repeat this bit by doing the following since we are in an async Notifier we can update our state by doing State equals and then we can do an async away here and we'll do a syncvalue.guard and then we'll invoke our get memories method like so we need to change that to another score because ref is pointing to the wrong object let's save that and let's do a hot restart and then add a memory okay then let's look at the edit flow for memory items will return to our memory item View and then we need to retrieve our logged in user by doing the following and let's import the auth file and then once you've got the details of our currently logged in user let's have an if check if the user is not equal to null and also if the currently logged in user's ID matches profile ID if I currently logged in user then we want to display the edit button so this will be of centered field button looking like that and then once we press this button we want to do context.show button sheet and then in here we'll render the form however since this is an edit flow we will modify this memory item form this will take in details of your memory if it's defined then where in the edit flow or else we are creating a new memory in our consumer State we will override our init State function and in here we'll have to populate the title control with the value from our memory object so let's make this a late text editing controller and then we'll cut this bit and then in here we will initialize our title control and pass in the value from our memory object so now let's return to our memory item View and then in here let's pass in the memory object let's do a hot restart and let's see and then for the image we won't show the form we'll show the image that was uploaded by returning to this form over here we need to set the correct title here so let's launch this and then over here we will do the following if our memory object isn't defined then we'll have new memory or else will display edit memory which we see dot here secondly instead of displaying this file upload field we have a condition if our image ID is not equal to null then we will render it out and then the URL would be similar to our memory item view so this one over here in fact what we can do is have this as a util function and use the asset provider what I mean is we will return to our utils.dot and what we do here is we find a function this function will take in the following named arguments will retrieve the storage URL from our memory repository provider and let's import and then we'll do a return and build our URL save this and then let's run our code generation okay once our code generation is completed we will return to our memory item form and then in here we'll do a ref.read import our image URL provider which takes in our user ID and then the file name will be that and let's not miss the else block for this file upload field widget so let's save this and now we've got that let's implement the submit behavior when editing our memories and now submit we'll have an elsif block and in here if our data is defined then we are performing an update so we'll invoke this update memory method which we need to Define over here we'll Define our method we start by checking that our data memory object is defined we'll have our try catch as usual and then over here we'll invoke the update memory method we defined earlier this takes in our ID and then the updated title next what we need to do is to complete the implementation of our update memory method we will grab our current logged in profile we'll invoke updates and it's the title you want to update and then we want to update where it matches our ID and also where the profile ID matches our currently logged in user ID so should we edit this memory and then hit submit then that should update our database so we see the update here however we're not seeing that update here I'll do a hot restart and let's update this again submit that there we go so it's actually working we just needed to do a whole restart let's try that again okay there we go it works so then let's implement the delete on our memory repository we will retrieve the profile ID and then we'll retrieve a reference to our image ID we need to do two things to delete the image we'll invoke the remove method which takes a list so we'll reference the folder which is our profile ID for mobile image ID and then we will copy this and paste it here differences update becomes delete and we'll get rid of that and then ID will be the data ID that's it for our delete Behavior we'll need to Define this in our form and it looks pretty much the same to this one so I'll copy this and then let's paste it here and then we'll invoke delete memory while passing in widget.data and then we'll invoke delete memory okay let's test it so I hit delete and then there we go also let's copy this bit for the submit Button as well and then let's look in our database to confirm that the memory entry is removed as well as our storage image has been deleted let's also refactor our memory item view to use our image URL provider that we defined get rid of that which means we can remove this line before we move on to the implementation of our likes counter let's make a quick refactor in our memory Notifier over here we want to update the state based on our event type of our payload specifically if the event type matches any of these values then we want to run the operation to update our state so I'll set that and do our restart now into our likes Contour implementation what we'll do is we'll listen to our likes table for changes and the way to do that is we'll come to our memory repository we will have a stream on our likes table since we don't need to worry about any foreign key constraints on the likes table we can just use dot stream we'll also Implement two methods first method will be add like which is taking the ID of the memory that's been liked and then in here we will start by retrieving the current user profile and then with the insert where we pass in the memory and profile ID and then for the remove implementation we will duplicate this function rename this and then in here will invoke the delete method and we want to add a filter where the memory ID matches the memory ID we've passed in and also where the profile ID matches the current user ID so let's save that from here we need to implement a stream provider that listen on this stream which we're then used to update our UI let's create a new provider we'll call it likes count we will Define our annotated stream provider function let's run our code generator okay we'll retrieve a reference to our light stream and then we'll use on our wait for and we want to yield over likes where the memory ID matches our ID from the current memory item and then we'll return length let's save that all right to use the stream provider we're going to create our widget for it on the memory widgets we create a new file call it likes counter we'll create a stateless consumer widget and let's update our exports this likes count I will receive our memory object and then over here we'll retrieve our likes count from our stream and then to retrieve the actual likes we do likes counts or as data dot value in here we'll change this to a sized box with a filled button and a style and for a child will have a row this row will have our favorite icon followed by flexible and then the text containing the number of likes then let's add a style to this so to use this now let's export it and then in our memory item View we will have been positioned with our likes counter let's say that we need to set waste for this one let's Center the items in our row which gives us that and what we want to do is to retrieve our user and be sure to import auth.dart and then over here on press you want to check that our users now or if our likes count is in loading State then we want to set this as null or else in here we want to do ref.read and then add like so then if we test this out by clicking one of these our like has been added to database and we've got this update here also when we check our likes table we see that our entry has been added and then when we remove this the UI should update to zero in order to implement the remove operation we need to know whether the user has liked this memory item already so that we do not invoke the add light method again we invoke the remove like method we're gonna have to modify our likes count stream instead of emitting integers let's create a new model and we'll call this likes info will annotate our likes him for class and then we'll have a factory Constructor with the following Fields let's save this and run our code generation and let's export our model then we'll return to our likes count and this will be now emitting a likes info object and then for our yield let's cut this and then we'll have likes info and then for our account we'll paste this then in order to define whether the user has liked this particular memory let's support current user and then over here we'll do like stop any for each like you want to check this condition and also we want to check that the profile ID matches our current user ID so now we're guessing the number of likes on this particular memory item as well as a Boolean indicating whether the currently logged in user has liked this memory item or not let's run the code generator and let's come to our likes counter we'll change this to info and then for our likes we do info account or else we'll set 0 as the default here we can do that and then we can check if half light equals true that means they've liked this memory item already then we'll do remove like passing in the data ID or else we'll add or like and let's restart okay so when we click on that we like it and then when we click on it again it's removed let's modify the look and feel of this counter once we like the memory item we want to change the color to Red to indicate to the currently logged in user but they liked this memory item will come to our icon widget for the color we want to check if info has liked equals true then you want the red accent or else we'll have white which therefore looks like that and when we click that again it's back to Y and should work with all of these like so if we like this again and we log out we see that it's back to white again because the user is not logged in I have another condition here like so if it's more than zero we'll show white or else will have a slightly transparent white so let's save that which looks like that and should we log in then it's right that's like a couple more items and then let's log in with the other account and then we can also how they like to these two and when we log out we get that our implementation is now complete here's what the full flow looks like when we go to login I'll create a new account foreign will enter an incorrect verification code and we get that message and when we hit resend code we need to wait for 45 more seconds because Super Bass enables the resend Behavior after every minute and then after a minute let's resend okay and then we got code recent then I'll paste the recent verification code and then we hit submit there we go we're logged in let's add an image and let's add our new memory let's add some likes let's edit our memory and then let's delete okay that is good and then we can log out all right and this brings us to the end of the mini course be sure to like this video and also subscribe to the channel if you haven't already I make tutorials teaching developers to build full stock applications with data and flutter if you've got any feedback any comments below do let me know and I'll see you in the next one thank you
Info
Channel: Creative Bracket • Dart and Flutter Tutorials
Views: 6,658
Rating: undefined out of 5
Keywords: dartlang, supabase, riverpod, flutter course, state management, firebase alternative, cloud platform
Id: 510QYduXBXc
Channel Id: undefined
Length: 62min 23sec (3743 seconds)
Published: Tue Apr 25 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.