How to Make a Clean Architecture Note App (MVVM / CRUD / Jetpack Compose) - Android Studio Tutorial

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hey guys and welcome back to a new clean architecture video yes i will do another one so those who actually saw my cryptocurrency app um already saw one of my clean architecture videos but because that video went through the roof i decided to make another one and if this video also gets that many views and also gets such a good watch time then maybe i do even more so it's fully up to you guys watch this video till the end build this up you will learn so much and then yeah i will do even more if you like it and if it gets good views so in how far will this video differ from the cryptocurrency update that i already made a tutorial about um so on the one hand here i want to show you how you can build such an app with a local database so the cryptocurrency app used a public api this video will use a local room database so those will just be some things that will be different here and i also get some feedback for the last project um some smaller improvements that i could make and these will also be things that i will be teaching you in this video i'm also just someone who never stops learning none of my videos are perfect so of course i will teach you everything in the best way i know but if there's something i can also improve on of course i will do that in the upcoming videos and you also don't need to watch the other video before this one um of course it helps but the other way around it also helps if you watch this one first and then the other one um so i will explain everything from scratch that regards clean architecture so you should know stuff about mvvm dependency injection um room databases so those will be things that i that that you should have heard of that you should have used before but everything around clean architecture will be explained from absolute scratch here so let's actually dive into this app and see what we will build in the next hours it will be a node app as you can see which has kind of a cool ui here we will build everything also the ui here in jetpack compose so you can see i added some sample nodes each node has a different color you can also assign these colors we can order these nodes here by extending this order menu with a nice little slide in animation right now they are ordered by date descendingly so the newest node will appear on top here if we say ascending then the oldest node is on top we can say title ascending so then it will order all these nodes by title alphabetically we can say descending and yeah you get it color doesn't really do anything here because we have one note of each color but if we would have multiple notes of the same color these would be grouped together so you could say for example for yourself these orange nodes would be nodes regarding school these um i don't know red reddish nodes um are nodes for whatever for shopping list i don't know feel free to be creative here however let's let's see how it works to create a note we can click on this floating action button and then we will see the screen we can choose one of these five colors these also have a nice little animation here a little fade color animation so let's choose this color for example we can choose a title hello youtube and we can enter some content this is a sample note for demonstration and if we then click save this note will be added here because right now we don't order by date descending we can see it but you can see now when we do we can see it and here's our sample node if we click on this node we can edit it and update it we can change the color for example if you click save and you can see now it's a purple node and we can also delete this node and then we'll see a snack bar we can undo this deletion so that it will be added again and if we wait until the snack bar disappears then the node will be fully gone so that is what you're going to learn that is a lot that is great for your portfolio that is great for your overall skill set that you actually need for every android project out there these are widely used industry standards so really make sure to watch this video till the end i know it is a long video but these are the videos you will actually benefit the most from so let's first actually start by understanding why we should use clean architecture and what that actually is so maybe you already know mvvm um and now you might wonder what what's the difference between me vm and clean architecture isn't mvvm already kind of a clean architecture um well i know what you mean and i also for a long time thought mvvm means clean architecture but clean architecture is actually a term that comes from uncle bob so very famous programmer also wrote many great books um and he actually came up with this approach of clean architecture so that is an approach that is specific um that was specifically created by him and it actually it's more of an extension of existing architectures so it makes use of solid principles it makes use of um an existing architectural design pattern like mvm for example and comes with some new approaches like use cases for example maybe you've heard of that which we will use in this video and you will learn about that so the whole purpose of clean architecture is that it makes your app scalable so usually if you work in the industry you won't just work on such simple node apps or to do apps instead you will work on really large scale apps and the the larger an app gets the more you actually need to think about how you structure it because a good architecture comes with several different aspects that it needs to fulfill on the one hand that is how easy is it to extend this architecture with new features for example then we have how easy is it to test our functions in this architecture so how easy is it to write test cases um how easy or how understandable it is so let's say a new team member joins your team how how long does this new team member need to actually understand what your project is about and also one point that kind of goes along with the scalability is um how well can you actually replace stuff so let's say you use an http library like retrofit then what happens if you actually want to use a different one so you decide okay now i don't want to use retrofit anymore for a project instead let's use the cater client then a good architecture should make the switch very easy for you so you should just have a very few files in which you actually need to change something to fully make this change from retrofit to the cater client in your whole project no matter how big your project is so all these points come together here and will be fulfilled by this clean architecture aspect one note right ahead um usually if you have a large scale app you implement that app using multiple modules in android i won't do that here on youtube that is too complex for for a single video i also said that in the previous clean architecture video but in this video i will specifically focus on um kind of emulating the the module structure that i would typically use in a in a real app and a production app on a big app um and kind of emulate this with our normal packages here in a single module and that is already for the theory here um if you are actually interested in these topics like clean architecture android kotlin um which i guess you are if you're watching this video then you will really like my newsletter my email newsletter which is fully free you can check the link down below and sign up for that and you will get regular android kotlin architecture advice right into your inbox so to actually start you need this initial project this initial project contains all the styles that i already set up so ah here for example the colors the theme so the primary color background all that stuff and the dependencies so this dagger hilt plug in then these plugins up here cotton cap and dagger healed and these dependencies so compose dependencies curiteen stagger health the room library for the database and the extension so we can use room with curtins you want to make sure that you get this initial project down below you will find a link to my github repository you can simply either download the zip file there or just clone the project in your android studio and then you can immediately start from there the first thing i will do here is actually i will set up the package structure because i think that makes the most sense to actually understand why we structure it that way and which stuff we will actually put onto which places so you see how clean architecture actually works because it's all about the structure let's close these two tabs here actually these two and let's see we have a root package and in that root package we have ui and main activity the ui package really only contains stuff relevant for a theme so let's close that instead what i will do is um to show you this new package structure approach that i didn't show you in the last video in the last video we just created a presentation data and domain layer so these are the three main layers that we have in clean architecture the presentation layer contains the ui so we present something to the user i think that makes sense the data layer contains everything relevant for data so for example api access database access shared preferences access all that stuff would be put into the data layer and the domain layer is kind of the the connecting layer so it contains our our actual business rules that means it contains business logic for example filtering a list it contains definitions for repositories we will get to that later and it will actually contain our model classes so our actual entities for example and in the last video i actually set up the package structure like that that we had data domain and presentation as as packages here in our root package and that is fine for for such a simple app of course and especially if you don't use multiple modules but as soon as you use multiple modules then you don't want to structure that way because when you use a multi-module architecture you actually want your modules to be kind of small because the smaller they are the the faster they build and multiple modules will speed up your build time if you do it right then they should also be replaceable and if you just have three main modules then these are not really replaceable if they are smaller then they are very well replaceable so we kind of don't want this approach to have these um main layers on our root level of our architecture of our structure instead what we will what we'll do is we will divide our application by features so what is a feature actually a feature of an app is in the end just a set of one or multiple screens so in in this note up here for example we only have one single feature and that is everything regarding that node functionality so displaying nodes adding nodes deleting nodes all that belongs to a single feature um if we for example wanted to extend this app and want to save these nodes on a backend server in the cloud then we would need some kind of kind of authentication so to log in then this authentication would be one feature so that would be the set of login screen register screen forget password screen maybe then we have a profile page in which we can edit our profile then this edit profile screen and profile screen would be a feature so the more of these things you have the more features you have so now the features actually make up your on the the structure on on the on the root level of your package structure and then inside of each single feature you have these different layers presentation domain and data so you actually divide the features by these layers and not your whole app by these layers i think that makes sense because that way it's actually a lot more replaceable you have smaller modules and it's also a little bit clearer because someone only needs to take a look at your route structure of your app and sees okay profile feature note feature authentication feature they immediately really know what your app is about and which packets which package contains what so let's actually dive into practice because i think then it gets a lot clearer what we what i mean here um so in our root package we will now set up our whole package structure those will be a lot of packages but i will explain as we do so as i said we only have one single feature here in our app which is the node feature so we create a new package called feature note the reason i call it feature underscore node is well it makes clear it's a feature and also if you have more packages on your route level then these feature packages will be grouped together because this will be sorted on alphabetically and because of that all the feature packages will just be grouped together that's just how alphabetical sort works and now in this specific feature module or package we can divide this into our single layers so presentation data and domain so let's start with the data package so we can create a data package and we can directly also create another package in that for our data underscore source so the data source will be the package in which we put everything regarding our room database because that is our data source maybe what's also important here is don't call this very library specific so you don't want to call this this package data and this package like room and put every room specific stuff in the room package no you don't want to do that because it's just your data source what if you actually want to replace your database with sql delight for example then it would be called room it shouldn't really know where the data comes from and it also shouldn't care about that you just have a data source and that's all you need to know for your app so apart from that let's also create another package in the data package which will be for the repository that is already it for a data let's create the domain package which as i said contains our business rules and business logic so let's first create uh the domain layer and in that i will create a model package which will contain our entities we will create another package for use cases now let's just call it use case what that is i will get to that when we get to the point um we'll have another package for repository we'll also get to that what the difference between this and this is when we get to this and we will have a util package in here that's it for the domain layer let's then create our presentation layer in which we will create a package for every single screen we have so we have two different screens in our app on the one hand we have the notes screen let's just call it notes and we will also have a screen called add edit node because the screen will be reused if we add a new node or if we um just added an existing node we also have a util package in the presentation layer so we can create that and also in both of these screen packages we will have a package that contains the components specific for this single screen so the composables in the end and then we can take our main activity and drag it in the presentation layer click refactor then we're good to go that's it for the feature packages there are there's actually one more package we need and that is for our dependency injection using degree hilt so package for our modules and i will put this in our package new package called di and if you actually have multiple features which you usually have if you use clean architecture then i would also create something called a core or commons package let's call it core here that will just contain everything that is kind of shared so let's say you have a ui component that you need throughout multiple screens in your in your app so not only in one feature then you would put this in the core package so you would create a presentation package in here as well and then you would have maybe components in here and here you could put in composables that you just need in your whole app but because we only have one feature this doesn't make real sense for for our app here so i will just delete it and that is really everything about our project structure um i hope this makes sense i know some of these things aren't clear yet if you don't know clean architecture um like the differences between like this repository this repository um and what what we put in these packages what's the use case and stuff like that but you will understand all this when we actually implement it so let's finally dive into implementation and the first thing i want to do is implement our room database so our data source for that we go in our data source package surprise surprise and actually not we will first go inside of our model package and create our node class so just a class that represents one node that contains the relevant data for that and that is in the and an entity and we put entities in the domain package in the model package so new kotlin class of file called node will be a data class uh yes let me add that to git and this will be an entity for room so we annotate this with entity so just a table in our room database in the end and let's let's think about what do we need for each node we first of all need a title we need content type string we need a timestamp so when we created that note so we can actually order by a date we want to save a color for that node which is here just an integer and we need a primary key so the id of a node which we need for every database we annotate this with primary key called id and that's a nullable integer that's set to null initially and actually a val like this i will also create a companion object for that note because in here i just want to have a list a hard coded list that contains the the colors a note can possibly have so val note colors is a list of and here we can just put in our colors that is red orange comes from our colors file that you also already have light green and of course you can choose your own colors here um oh i just like these ones um let's choose violet um a baby blue and we have red pink cool so with this list we can just control which colors we will actually um we are able to choose for our note and this will also then decide about the the the color bubbles with which we can actually change the colors for each node so first we're good let's go into our data source package next and create our node dao so a dao is a data access object in which we just define the functions with which we want to access the data in our room database so we want to have functions to insert nodes delete nodes to get a single node and just to get all nodes so let's right click new colon class of file node dao select interface annotate this with at dao and let's define the functions in here first of all we will have a function to get all nodes which will return a flow so kotlin curtains flow here off list of node so this will just return all nodes of our database for that we need to annotate this with add query because we now need an sql query that actually retrieves all these nodes from our database and that's a super simple query if we can just say select everything so an asterisk from our node table and then room will do the rest for us and generate the actual implementation for that behind the scenes then we also want to have a function that gives us a node by its id so when we click on a node to update it we need to load the the specific node with that id in our add edit node screen so we need a function for that that will be a suspend function get node by d id will be of type integer here and this gives us a nullable node so in case it doesn't exist it will be null and we can also attach a query here which will be select um everything from node where id is equal to the id we passed so we do this with such a colon and this is a suspend function here like all other functions in this file except for this one because here we just directly return the node we don't wrap this around the flow object or something like that i know that's a very common question why this one isn't suspend and this one is yeah because this one returns a flow then we we can do that then we need two more functions here on the one hand to insert a node so we can say insert and we want to pass some parameters here and that is a on conflict strategy we set it to replace so that means if we call this insert function with an id that already exists in our database then it will just update the existing entry so we don't need an additional function to update a node instead we can just insert a node with the same id and it will update the one that was already saved so suspend function insert node we pass a node here that's already it and we will have a function to delete a node this is pen function delete node pass the node and that's it that's already it for a dial um the next step is to create a class for our database also in our data source package called node database nodedatabase will be an abstract class we annotate it with a database and here we need two arguments on the one hand entities so we just define the tables we have in our database that is just one here which is node from our domain package no double column class and we want to define the version and just set it to one the node database will inherit from room database and we will have an abstract val for our node dial here of type no doubt and that's already it room will do the rest for us so now we have our database what's the next step the next step is to actually make use of the database and call the actual functions of our dao in our repository because what is the job of the repository the repository directly accesses our data sources so either database or api usually there can be multiple other ones but usually you have an api or a database and the repository directly accesses these the job of the repository is then to to take these data sources multiple data sources usually and determine which one to actually forward to corresponding use cases so for example if you get data from an api and you also have a caching layer and mechanism in your app then the repository needs to decide okay do i know do i now load the data from the cache or do i now load the data from the api so this decision logic and also checking if there were any errors in that api request or in reading from the database and then just forwarding this data to the use cases the use cases shouldn't know where the repository gets the data from they just want to get the data that's all user use cases care about so let's go ahead and do that in our repository package of the domain package we will create an interface for the repository called node repository the reason that's an interface is that's just good for testing because then we can easily create fake versions of this repository um for for test cases we often just like to have fakes because we don't want to use our real api in our real database to run the test cases on no the test cases should be quick so we want to have our own fake version of a repository that just simulates the same behavior as a repository so we can pass that fake repository to our use cases in that case so the use cases know what to use and the use cases then again don't care where the data comes from they don't care if it comes from an api or just from a local list that was implemented in a fake repository they just get the data and do something with it so in here we just have one function for each function we have in our dao on the one hand we have function get nodes which returns that flow of type list of node then we have a suspend function get node by d we pass the id and get the node we have a suspend function insert node where we pass the node as a parameter to be inserted into our database and we have a suspend function a delete node where we can pass a node to delete from our database that's it for the the definition of the repository these definitions not only for repositories also for other classes if you have these definitions in form of an interface these belong in the domain layer now the implementations of that so where we actually say okay these nodes come from a database this insertion should happen in the database that happens in the data layer so in this repository package we right click new cotton class note repository implementation we call it which will implement this uh not main repository not repository interface and this will actually now take a parameter here in the constructor and that will be our actual dow object so or no dow then we can press ctrl i in here ctrl a to select everything and enter to implement these functions first of all the get notes function here we just return dao dot get notes so because that's a very simple app we only have a database there won't be much logic in this repository we just call the functions from our dao but as soon as you also have an api in here then things get more complex and you actually have some data logic in here then for get node by id we just return dao and get node by id and pass the id insert node here we can say dow that insert node and finally dow the delete node and that's already it for this repository implementation so far so good those are our repositories or with our single repository the next step and the next layer we will implement is a use case so actually multiple use cases but we'll start with a single one the use cases contain our business logic so what we previously with plain mvvm did in the view model is now done in the use cases the advantage of use cases is that on the one hand they make our code very readable because a use case is in the end just a single thing our app does a single user action for example getting nodes for example adding a new node for example deleting a node so i think you get it just a single thing a user can do in our app it doesn't need to be linked to to a database function or an api call often it is but it doesn't need to be it could also be something like searching in a local list or so and these use cases are named exactly as what they do so for example add node use case and you don't need to have like 20 years of development experience to understand what is actually inside of that class if you just see the name and that's one big advantage of use cases they they quickly reveal what they actually contain by just looking at the class name and the other good thing about those is that they they make your code very reusable or they the use cases are very reusable because in the end our review models will call the use cases and if you would implement every api call and the validation and business logic related to that in a view model then as soon as you need that in a different view model the same logic you have duplicate code if you instead put that in a single use case class then you can reuse the use case class between different view models and you don't have that duplicate code so that makes your code just a lot more clean let's start with the first use case that we will have in our app and that will also be the most complex one which is the get notes use case it will be the most complex one because when we get notes we actually want to have the option to order notes so we want to be able to sort by title by color and by date both ascending and descending so we have six different ways of sorting a list of nodes and that's business logic that belongs in the use case for example so what we're going to do is we're going to go to use cases use case package create a new carton cluster file and this will be the get nodes use case that's how i called it in the previous video that's totally fine to call use cases like that some people said hey you don't need to call them with use case here you can also just call them get notes it's true you can do both just to do something different here from the last video i'll call this get notes but you're also totally fine calling you use cases with use case afterwards because then it's even clearer that this is a use case class but this is also kind of clear so i will call this get notes but you're right doing both ways and this will now get a reference to a repository our node repository and here make sure that you actually make this of the interface type and not of the implementation type because otherwise it's not replaceable and that's the whole purpose of using an interface and now every use case out there needs to have one function that kind of executes that use case so use cases only should only have one single function that is public and can be called from the outside they can have private functions multiple ones if you just want to have utility functions to to make your code more readable but they should only have one public function and some people call it execute i just don't call it um in any way i just make it an operator invoke function so we can call this use case like a function so operator function invoke that will override the invoke operator so we can call our class like a function in the end you will see how this works and this will return a flow of a list of nodes and in here we just put in our business logic to in this case get our nodes but we also want to assert our nodes so whoever calls this use case whichever review model that is should be able to somehow tell this use case hey this is the way of ordering i want please give me the list of notes so we need to pass a parameter here to actually decide about this this type of ordering and for that we will create two utility classes that make this on one hand readable and on the other hand just um yeah very clean so let's do that in our util package here for the domain layer right click new kotlin class of file and those will both be sealed classes on the one hand i will call this order type so the order type will either be ascending or descending we can create objects in here object ascending of type order type or we have a descending so with that order type we can just distinguish okay do we now want to sort ascending or descending and we can create another class in here called note order so this will now tell us okay do we actually order by title by color by date and then also do we order as sending or descending by these by these types so also sealed class and this will contain a class title so we could set this down to note order dot title so we order by title here we can pass our order type and that will just be of tab note order and here we actually also want to pass the order type in the constructor of node order because every node order type needs such an order type so if it's ascending or descending so we can say val order type of type order type let's duplicate that twice call this date and call this color so we have three different ways of ordering a list of notes and what we can now do is we can go to our get notes use case and we can pass this node order so node order node order of type node order i will move this in a new line and we can set the default to node order dot date and now we can say okay we want to order by a date descending by default so that is how we can now distinguish between different order types so what do we now return here so we need to return a flow of list of nodes that's exactly what we get from a repository so we can say return repository and get nodes and i mean right now there are no errors but of course that's not enough because we haven't applied our node order yet so what you want to say is i want to say map so we want to map the result list so whatever list we get from our repository we map this to a new list so we can give this a name of nodes and now depending on which order type that is we want to map this to the right list so to the right sorted list in the end so we can say when our node order that order type is ascending we do this and if that is descending we do this if it's ask something we can have another one expression when the note order this time is title actually we don't even need these blocks of code we can just duplicate this and have date and we can have color so when the node order is title then we want to map this nodes list to a nodes list that is ordered by title so we can say nodes sorted by so the sorted by function will sort it will sort as sounding and the sorted by descending function will sort descending so here we use sorted by and now we can say it.title so we will simply sort by title and actually let's also say the title of lowercase otherwise all the lowercase letters will come before the uppercase letters we don't really want to care about that so we map everything to lower case if we want to order by date we say notes sorted by it.timestamp and by color we say node notes that's sorted by it.color and then we can simply copy this paste it here and here we just need to swap out this sorted by with sorted by descending here here and here and well that's our use case that's business logic and it contains how it actually accesses our repository that's a very very typical use case um yeah and now this can be reused in a whole app so if you have maybe multiple screens in which you need to access a list of nodes with a given order then you can just reuse this use case class so you don't need to put this logic here in all of your view models that kind of need to to have this business logic and the other use cases here in our app will be a lot simpler so the next step here would be to think about which other use cases we actually need in our first screen in the in the note screen where we just just show the list of notes well of course now we have the the major use case for that to just get that list of nodes and filter or rather order it but we need one more um and that is actually deleting a node so let's quickly create that one that will be super simple let's go to our use case package create a new kotlin class called delete node use case or just delete node here to stay consistent and that will take our repository in the constructor our node repository and it will have suspend operate suspend operator function invoke it will take the node that we want to delete and it will just call the repository with delete node and that's already our use case it's very simple so now these are actually the two use cases we need for our nodes view model um but i actually want to do one thing here so we actually will have two more use cases in our app here which is for adding a node and for getting a single node so by its id and if this if your app actually grows then of course it will have more and more use cases and if you want to pass all these in your view models then that can quickly become quite a big viewmodel constructor so what i actually want to do is i want to wrap the use cases for a single feature into one class so we just need to inject that single class that contains all of our use cases for that feature into our viewmodel constructor and that just makes it much cleaner so let's see how this works we just go to use cases create a new kotlin class called nodes or just node use cases because those are the use cases for our node feature we select data class and in here we will just have a variable for every single use case we have on the one hand that is our get nodes use case and that is our delete node use case and later we can then extend this delete node and this will be the class that we will inject into our view model so let's actually do that next let's set up our dependency injection library dagger hilt which is very easy we just need to create an application class in our root package node app select class which is an application we annotate it with hilt android app and that's already for this class we need to register this application class in our manifest let's open this go in here say name note app then the last thing we need to do to be able to inject dependencies is to provide these in a so-called module so in the module we just put in all the dependencies we want to provide with a given lifetime of these dependencies so in our case we will just have singletons so in our di package right click new cotton class that will be an object called app module if you have many features um then i would usually just have one module per feature and yes it's actually up to you if you put all of that in in that di folder here in your root folder um i think it's also okay if you actually create the i folders in each feature i like to have these in one in in one package here but that's fully up to you in a multi-module project you would also have these dependencies in your app level module so let's click enter here have the module annotation and install in we want to install it in the singleton component and well what do we provide here first of all let's provide an instance of our room database so provides at singleton function provides node database this will require our application context to create that so we can simply pass the application here dagger hilt will automatically insert that and it will return a node database we can then simply return room dot database builder here we need to pass the context so our application the class of our database you want to create which is no database double colon class java and the database name which we can create in here in the node database class just in the companion object constaval database on the screen name and that will be nodesdb switchback has a dot nodedatabase.database name then we can call a dot build and this function is already finished then next we actually want to have a function that provides our repository so the node repository so add provides singleton function provide node repository and what do we need to create our node repository we need our dao and because we don't provide the dao itself instead just the database which i would recommend doing we instead want to use the database instance here of type node database because using that we can retrieve the corresponding dao that will return a node repository and here we just return the implementation of that so node repository implementation passing our db that no doubt another cool thing about dependency injection is now let's say you now want to test your your classes your use cases for example um then this function provides just a node repository so the interface um specifically our implementation here because that's of course what we want to use for our production app in the end so we only use the node repository that accesses our room database but as i already said in the test cases we usually want to have a fake version of that so all you really need to change for test cases is to have a separate module that instead of providing this node repository implementation just provides a fake node repository which is also which also implements this interface so that's really the only the only change we need to do and then this dependency injection will take care of everything else we don't need to change anything in the use cases and the repositories in our view models nowhere only in this module and finally one last thing we need to provide here and that is our node use cases so function provide node use case is just that wrapper class we just created and that will now need our repository instance and returns node use cases for get nodes it will return a get nodes use case passing the repository and for delete node it will pass and we will pass the delete node use case with our repository and that's it now we can inject that in our v models and actually access these two use cases so let's do that next let's create our nodes of your model we want to go down to nodes um right click and create a viewmodel class so the viewmodel classes are directly coupled to our actual ui so if you know mvp design pattern for example and the viewmodel would basically be the presenter the the job of the viewmodel in clean architecture is not business logic as it was in plain mvvm instead the job now is to actually make use of the use cases the use cases contain the business logic and the view model now needs to take the result of these use cases so for example our our ordered list that or the list of nodes that we get from the database and it kind of needs to put all of that in a state that will represent or that is relevant for our ui so the ui can observe on that state and just yeah have can easily display that in a readable way that's the job of the viewmodel so i'll call this classnodesviewmodel select file and i will use my live template here hill view model you need to tap this from hand probably if you don't have this and i will call this notes view model it will now take our node use cases here in the constructor which is the wrapper class we created and with this wrapper class we can access all the use cases we have for our notes feature so now the next step is we actually want to have one state object one state wrapper class that represents the current ui state of the node screen so let's now think about which kind of states which kind of variables we're interested in that are relevant for um yeah just showing the ui as we want to show it on the one hand we of course need to show the current radio button selection so just the current node order so we need the current node order in that state then we of course want to show a list of nodes so we need to save the list of nodes that we load from the database and we want to have a state that actually decides if this order section is visible or not so remember we could easily collapse and expand this so those are actually the three things we want to have in a state object and for that i will go into our notes package create a new data class called nodes state and it will contain just that first of all our list of nodes so list of node from our domain package and that will just be an empty list by default then we will have our node order that will decide about the radio buttons which is by default ordered by date descending so node order that date descending and what is left is actually val is order section visible so that's a boolean which is initially false so initially we don't show that section so now that we have the state that we kind of keep in our view model and save there so it also survives screen rotations there's one more thing i always think of when i'm implementing a viewmodel with clean architecture and that is i'm thinking about every single ui action there could possibly be from the user it's basically everything the user could possibly do in our note screen for example changing the order so the user could click any of these radio buttons to change the order then what else could it be it could be the user clicks on deleting a note so then we also want to fire off an event to the view model which we'll call the corresponding use case what else could it be it could be that the user actually clicks on this toggle toggle order selection icon which will just hide or show the order selection that's also something the user can do that's a user action that comes from the ui and one last action that might not be so obvious is that we actually can also restore nodes so when we deleted a node we'll show a snag bar and that snackbar will have an undo button if we click undo we will insert that node into our database again and that also is a user action and for these these different actions or events i will actually always create a wrapper class a shield class that contains these so we can very easily send these events from our composer bills to a review model and then just have one expression in the view model and depending on what kind of event that is we just do the corresponding action so let's see how this works we just want to go to our notes package create a new class see a class as i said and that will be called notes event select enter here and in here we will just have these four events i actually talked about on the one hand that is a data class to order the nodes so the order event that will take the new node order as a val actually here and that is a node's event so then we can just send this order event from our ui to the view model when we click click on another radio button and yeah the view model will then call the use case with that specific node order then another event we actually have here is to delete a node here we need to pass the node we want to delete type notes event then we will have an object to actually restore a note so when we click undo then we just want to re restore the most recently deleted note um that will be a node's event and one more and that is actually that we can toggle the order section so the visibility of that and now we can take the sealed class go to our nodes view model and create a function on event so this is the function we will trigger from our ui here we pass the corresponding event of type notes event and now we can very easily check when that event is an order event we can do this when that event is delete note from notes event actually if it is restore node and if it is toggle order section and now the job of the viewmodel is to either call some use cases from that um usually we do this but not always for example for this toggle order section all we really do is we take our note state and we just toggle this boolean and update the state's value so it's not always linked to a use case here so let's take this simple example and see how this works we just can create a state variable here i also have a left template for that you need to tap this by hand again i will call this state will be of type notes state and will be initialized with an empty node state by default then we can remove this and this will be the state that contains the values our ui will observe now we can go to this toggle order section here and we can say underscore state that value and we update this with stated value that copy so we take the same value so we copy it but now we have the option to change some values of that copy and we want to change his order section visible we just want to toggle it so stated value is order section visible and that just toggled let's go on with deleting and restoring a node because that's also quite easy we want to go in here to the later node we want to launch a curtain and view model scope and here we just call our delete node use case so node use cases dot delete node and now because that's an operator function we overrated the invoke operator we can call this like a function here even though that's actually a class um so for the node that we want to delete we just pass the node we pass to the event and for restoring a node we have two options we can either just um get an instance to the deleted node in our ui and directly insert it afterwards with like an add node use case and i kind of like the way i do here more to call it restore node because it tells exactly what it does and for that i actually just want to keep a reference here in the view model to the last deleted node so just private var um recently deleted node it's just a node a nullable one set to null initially and just when we delete a node here in our delete node event we say recently deleted node is equal to event.node so we just save the reference of that in that object in restore node we actually can't do that yet because we don't have our add node use case and we actually want to add this node back to our database so what we would need is we would actually need to create this so let's do that let's go to use cases and create another use case called add a node which will take a reference to a repository again a suspend operator function invoke and it gets the node we want to insert now you might think we can just do this repository insert node pass the node but no we actually only want to insert a node if we successfully validated the its values before so the title and the content if there is an issue with that for example if the title is empty we don't want to insert it and checking if it is actually empty is business logic doesn't belong in the viewmodel instead it belongs in this use case so we can just go ahead and see if the node dot title is blank well we want to do something here we want to tell the viewmodel hey the title is blank um i actually didn't insert the note here and there are different ways how we can do this we could make this function return something like an um a node validation error class or so so that we just return a specific error but what i also like is just creating an exception class for that and throwing that here so let's go to our note class and down here we can just create an invalid node exception which will take the message and just inherit from exception passing this message then we can go back to add node we can say this function throws this invalid node exception just for the documentation and that is actually and in here if that's blank we can say throw invalid node exception and we can say the title of the note can't be empty and we can do the same for node.content if that's blank of course you could validate more if it's like a minimum maximum length um but i'll leave it simple here just that you get how you you would do it um involve not exception and actually just the same just with the content and if these two if statements were false we'll actually just insert the node and our viewmodel can yeah just check in the try and catch block if an except exception was thrown or not so in here in restore node we actually don't need to do this because this will only be called when we click on undo and that means the node was already successfully inserted so we don't need to check this again but we need to check this if there was an exception when we actually add a new node so right here it's totally fine to do node use cases we don't have an add use case yet let's go to our node use cases say val add a node and that's an add node use case then in our app module say add node is an add node use case with a repository then here we can call this at node passing event dot actually not event.node we just want to add our recently deleted node and if that's null we just return at launch which shouldn't actually happen and we could also just to be sure said recently deleted note to null afterwards so even if we would call this multiple times afterwards from the ui for whatever reason then we wouldn't insert the the same note over and over again instead we can only insert it once and then we will kind of invalidate it and now there's one event missing here which is the order event which will just retrieve the list of notes with our given order the first thing we want to check here is if the order actually changed so when we click on the same radio button that is already checked we just don't want to do anything so we want to say if state that value dot node order double colon class if that's actually the same as event dot node order double colon class the state value note order note order the order type is equal to event.noteorder.ordertype then we just want to return so let's go through this what i actually mean and what this does we just check if the the order type the the node order is actually the same as the node order we wanted to change it to and also the order type so ascending or descending is the same as this is already in our state then we just don't want to do anything the reason why we compare the class here and we don't do this is that doing this we just compare the uh we'll just check for referential equality so if we deal with the same reference here which would never happen because the reference in our state is a different one than this because this was just created to change the order because we don't deal with data classes here in our where is it node order all these are just normal classes which don't override the equal function so to compare two classes we could override that here but we can also just check the class uh or compare the classes here if those were data classes that would work but then we couldn't pass the order type here we would need to rename it because those would need to be valves we would have a name conflict so all i'm saying is we we just want to check if the class is actually the same so if the the class of the first order type so title for example is the same as the class of the second order type that's all we're doing here and if that's the same and also the order type is the same here we can do that because it's just an object it's not a normal class then we just return if we didn't return we just want to call a function that just gets nodes with the given node order so let's create that function down here gets node and get nodes with node order and well what will this do it will first of all just take our node use cases that get nodes pass in that node order that will now return a flow that comes from our database the database or room will rather trigger this flow and emit something new whenever something changes in our database but we kind of want to map this to our compose state so we can say that on each on each image on each emission which will each be in a list of nodes we want to say state that value is stated value that copy where we set the nodes list to the new nodes list we also want to update the node order so node order will be the node order we passed here and then we also don't shouldn't forget to to launch this flow so launch in our review model scope curtin well this this seems correct it's actually not 100 correct because every time we call this get notes function we will actually get a new flow a new instance of that flow because we call that function every single time so what we actually want to do is whenever we recall this function we want to cancel the old cure routine that is already observing our database that's not so difficult we can just keep track of the job here private var get notes job for example is a nullable job and now by default and just before we get this um before we just get a new flow a new crew team job we can say get new jar get notes job.cancel and then we assign the new job to get node's job and then we can also scroll up um and just call this function in init again as well because we also just want to initially load some nodes with a default order so that would just be node order a dot a date and we want to order that descending uh descending like that and that's already for our notes view model so the next step now is implementing our notes screen so the screen where we display the list of notes for that we will need some composables on the one hand we need a radio button that just allows us to play some text next to a radio button we need the order section that contains all of our i think five radio buttons to order the list and we need the note composable so if we just take a look here this this is not composable this will be a little bit tricky here with this little clip um yeah you'll see how i solve this but that's basically what we will do next so first such a radio button then this whole section here then such a node item and then the whole screen so in our notes folder components we're going to create a new carton file called the default radio button and in here we will create a composable it's a little bit laggy right now composable default radio button and that needs some parameters on the one hand it needs the text that is placed next to it it needs a boolean whether it's checked or not it needs a function on check which is triggered when we actually check this button it needs the modifier for which we can pass the default one and that's already for the parameters all this will really contain is just a row and then a normal radio button in that row a spacer and a text so let's do that just normal row and we will say the modifier is just the modifier we passed and we set the vertical alignment to center center vertically then in here we will have the radio button we can pass um selected as it uh checked let's rename this to select it just to stay consistent and also this on check function to unselect then on click will just be equal to on select and for the colors of that radio button we will choose radio button defaults that colors and for some weird reason it always does that we only want colors the selected color will be material actually selected color material theme colors primary which is white here in micro in my case um in the unselected color material theme colors and that will be on background then as i said we'll have some space here so spacer with width of 8 and dp and then just some text that displays the text we passed so text and we can set the style of the text to material theme typography body one and that is it for the default radio button next create the order section i talked about with our five radio buttons in the components package we create a new file order section create a composable in here called order section we want to pass a modifier here we want to pass the node order so that will decide which radio buttons are actually checked and that will by default be node order date and that descending um actually not like this that is enough and when the order changes on order change we pass the new node order as a callback function to the parent composable and now for this order section we will just have a column of two rows and the first row will contain these three radio buttons and the second row will contain these two so now let's simply do that column here we pass our modifier for the modifier and then in here as i said we will have two rows on the one hand a row where we pass modifier fill max with and here we can then put our default radio buttons the text here will just be title and i won't use any string resources here usually in a production app i would do that but i just want to keep at least this thing rather simple for this tutorial so i'll just hard code strings here selected will be let's let's actually rearrange this this radio button should be selected if our node order is actually node order dot title because then we want to order by title and this radio button should be selected on select here we say on order change so when we click on this title radio button we want to trigger our callback function with the new node order which is node order.title and we will keep the current order type so node order dot order type then let's have some space here spacer with the width of 8 dp import tp and then we can just copy this actually now let's just copy this button paste it here and paste it again copy the spacer and paste it so for the second radio button that will be by a date so we replace this with date and this with date and this will be by color like this that's already it for our first row then we can have another spacer below our row so we have some vertical space um height of 16 dp and then we have our second row we can also set the modifier to fill max with then we can scroll up and copy these first two radio buttons paste them in here this will be for ascending sword here this is selected when the node order that order type is actually ascending and when we select this uh this is a little bit more complicated then it actually looks like because um we actually want to keep the current node order but we want to change the node order type and we don't want to make this node or this order type here mutable that's not a good practice so what i will actually do is to be able to just change this i will um hold ctrl click on node order to open this and i will actually create a copy function for node order if these would be data classes here we would have such a copy function so we could easily change the order type but this doesn't work because those are not data classes and if we would make these data classes as you can see then we would need to make this a vowel and if we make this a vowel then we have a name conflict here so that sadly doesn't work or or if we rename this order type which i don't want um so what i will do is i will just have a function here called copy and this will just allow us to pass the new order type so we will keep the node order but we change the order type and then return the new node order and this just returns when this so when the current node order is a title then we return a title node order with our order type that we passed and then we can duplicate that twice do the same for date and color and now we can go back to our section and actually make use of that by saying note order dot copy and here we want to change the order type to ascending then let's just copy this and paste it for this radio button this one is descending change this to descending and this to descending and that's already it for our borders section so next up we have our node item so let's create that in our components package in the node package called node item select file create a composable node item and this will take quite some parameters which on the one hand will be the node that we want to display it will be a modifier it's equal to the default modifier then we have the corner radius which is just the dp amount here and i'll default it to 10 dp so that will just be the the corner radius here um of our nodes here on the edges then we will have a cut corner radius or rather cut corner size let's call it like that that will be 30 dp and what that actually is is this size of of this corner that that is cut off here so this from from here to here is 30 dp so we can also pass that from the outside and when we actually click on the delete icon we want to trigger a callback function so how will we actually do this because this is something that i think is not possible with a simple modifier to achieve that that we cut off this the single corner i mean we can cut off corners um but i don't think with normal modifiers it's possible to do that just for one corner and then also how do we actually draw this this little corner that is cut off here and well this is a very good use for canvas so we are going to use a canvas here we are going to draw a round rectangle here so just four corners with round um corners and for this corner we will actually um apply a clip so if you watch my canvas course which i can only recommend you to do then you know what a clip is so we basically just draw a path around this rectangle here but at this point we cut off this edge and everything outside of this path this round corner here that would normally be there will be cut off and will not be drawn and then we will also draw another round rectangle here where half of it will also be cut off but the other half will display here so let's just jump into it and then everything will become clear let's first create a box because we will need to draw something on top of our canvas and for that a box makes a sense here we just apply the modifier and then in here as i said we will have a canvas for the canvas modifier we need to pass modifier match parent size and now you might wonder what's the difference between match parent size and fill mag size that's a small but a very important difference for this case because if we use fill mag size it won't work the thing with canvases is that they need a fixed size so they actually need a size that they know when when they actually initialized here instantiated or called it's actually a function here when they are called so we could do something like we set the width to 300 dp the height to 200 dp then that would work then we just define the space or the area for canvas to draw on but because we live in a world where we actually want to support multiple screen sizes we can hard code our sizes so we kind of need a relative size unit that still gives our canvas the size it it needs the thing with match parent size is this will actually give our canvas the size after the parent has measured its constraints so after this box knows how much width and how much height it occupies then this will return the size to the canvas if we use fill max size however max size then this will affect the size of the parent box because if the parent box let's say that one does not have a modifier with a size then this will actually also stretch the whole size of the parent box so that's not what we want we actually want to check how much space does the box occupy and the box knows that after the text actually took their space and then we will take the space for the canvas as well i hope that makes sense so in here we have our draw scope we can draw things on as i said if you really want to learn more about canvas and not not just such a simple thing then then really do me the favor and just check out um the course down below um you can just see what i do there and what you especially learn there much more advanced things much more customized ui so that will definitely be occurs you will benefit from long term and especially your ui will be um we'll thank you for that other than that let's jump into it so as i said we want to have this clip path so we want to define a path that goes around this rectangle but at this corner it's actually cut off and we can easily do that by using the clip path set this to new path that apply and we first draw a line two size that width minus cut corner size that's two pixels so that is the x coordinate and zero f the y coordinate so right now we just start at zero zero we draw a line to our size.width which would be here but we subtract our cut corner size so we're actually here and now we draw a line down here then down here here and then we close it so next line a line two actually size that width here and cut corner size two pixels then a line two size that width size that height so size is just the size of our canvas here in case you didn't know then line two zero 0f size.height and then we can just say close to close our path and then we can say that we want to clip a path where we pass our clip path and then in here everything we put in here and draw in there um will just be it will just make sure that the content is inside of our path so everything we draw outside of the path will just be cut off like this corner here for example so first of all let's draw the big round rectangle draw a round rect the color will just be the color of our note we need to convert that to compose color note that color the size will be the size of our canvas and the corner radius of that round rectangle will be corner radius um corner radius that two pixels i think yeah it needs that in pixels then let's copy this and draw one more round rectangle actually for this little one here where the half of it is actually cut off here well what is the color that's just a little bit darker color so what we actually want to do is we want to take this color and do some kind of blending operation that just makes it a bit darker so we can say this color is color color utils blend a rgb here we can define two colors so the first color will be blended which is our note color and the second color is the color we blend it with so we can say um color dot black and here we actually need to choose the the android graphics color we can also just pass zero x um zero zero zero zero zero zero that is just equivalent to black and now we define a ratio so how much you wanna blend it with let's just set it to twenty percent and we still get an error oh because i already wrapped this around a color object so with that it actually works here we also want to define a top left so the top left corner of that because it's not zero if it's zero we can leave it away like for this rectangle here it's not instead we can define an offset here which is size that width minus our cut corner size that two pixels and i will just move this to -100 on the y-axis um just that we don't get some roundings here um that would be very slightly visible but i just want to move this corner a little bit up here because we don't see it anyways then we don't have the rounding of this edge here of this corner and the size of that will be a size cut corner size that two pixels plus a hundred now and the height will be the same actually because it's a square also plus a hundred f and that's it for the canvas so i hope that some of it sounds if not then just ask in the comments or even better get my canvas course because there i can go much deeper in this stuff so now below this this canvas we want to actually have the two texts and our icon button for deleting the node so let's have a column here for the text set the modifier to fill max size then we will have some padding of 16 dp and i'll apply some more padding just to the end this time of 32 dp um i do this because then we actually make sure that the text doesn't overlap this this icon button which takes some arguments the text is noted title then the style is material theme typography h6 then we want to change the color of that to material theme colors on surface we want to change the max lines to one so we just have one line for the the title and we want to set the overflow to ellipses so that it just get cut off when it gets too long then let's have some space with a height of adp and let's have another text let's copy this one which will be for our note content we can say embody one here for this for the text style we can set the max launch to let's say 10 so we have um we showed 10 lines at max here and if it gets longer you can see it gets cut off and we can click on it to see the rest and then after this column we're going to have our icon button so i can button i actually have a live template for that i can button um the image vector will be icons default delete content description delete note on click we just set that to our on delete click function and we apply a modifier that we align this at the bottom end some modifier align bottom end and that's it for this note item a little bit bigger now um yeah so the next step is actually our notes screen so we just put everything together there so let's do that in our notes package new kotlin class of file called nodes screen composable nodes screen and that takes some parameters on the one hand our nav controller which we don't have yet and on the other hand our viewmodel instance so nodes view model equal to hill view model then in here let's have a reference to our state we can get from our view model we actually want to have a scaffold state because we are going to have a scaffold here to show snack bars remember scaffold state we need a curtin scope in here um remember curtin scope that will actually be needed to show the snack bar um and that's it for the variables i think so let's create our scaffold first it will take a floating action button so we can create that in here on click what will we do in this unclick function we will actually just navigate to our add note screen um we don't have that yet let's do that later and let's set the background color of this to color the primary i think i think that's white format that a bit and the content of that will be an icon icons.default.add content description will be add note then um is this our scaffold yes we want to set something to the scaffold state and that will be our scaffold state um oh no i'm wrong here we actually need to put this and we will actually not need anything more here so just a very simple scaffold with a floating action button and in here we can put the content of that scaffold so of our screen in the end that will just be a big column here we have a row then here we have our other section and the lazy column of nodes so column modifier is modifier fill max size and we apply some padding of 16 dp and we're going to have a row for the top section um we will have a modifier of fill max width will have horizontal arrangement of space between so we just push these um this text and this to the sides left and right side vertical arrangement alignment of center vertically and then we're going to have our text in here text will be your notes and the style is material theme typography h4 and then another icon button for the order section to toggle it so icon button image vector will be icons default sort um just sort as content description i guess when we click this button we want to take our view model not that one and want to call on event because that is now such an such a ui operation such an ui action we actually fire off from our ui to our view model so we can just save your model on event and which event is that that is just our toggle or the section event and our viewmodel will then change the state accordingly so we see the change in our ui now let's next implement this order section below this row and because here as you know we have this little animation that it slides in we can simply use animated visibility if it is visible is in the and state that is order section visible and then we can define how this animation should look like so whenever this actually toggles the animation will fire off again so we have an enter animation so when it slides in and here we can very easily just say we want to have a fade in effect and so plus we will also have a slide in vertical effect and for some reason it gives us an error i don't know why our experimental annotation here okay yeah just add this annotation and then you're good to go we can say exit is fade out i really like how they made these animations and slide out vertically and that's our animation so in here we now put the order section that we actually want to animate and whenever this state boolean toggles we're going to see this animation so order section we can set the modifier to modifier fill max with we want to set some vertical padding of let's say 16dp we want to set the node order so just to make sure that the correct radio buttons are checked to state that node order and we want to set on order change so when we change the order when we click on a radio button then we want to send the event to our view model so view model on event order and here we pass it so the new node order then below this animated visibility let's have a spacer with the height of 16 dp and below the spacer we will have our lazy column with a list of notes so let's just set the modifier to fill max size and in here we just have some items let's make sure to choose this overload with a list of items so we can pass our stated nodes and get each node here and for each node we just have another item so notice this let's not do it like this let's do it like this and the modifier will be modifier dot fill max with and we're gonna make it clickable so we click on a node we wanna navigate to the the add edit node screen right now we don't have any routes for that so we're going to implement that later and when we click on on delete we just save view model on event delete note and that takes the note going to delete which is just this note so we can pass this and after we deleted a note we're going to show snack bar with that undo option we can very easily do that with our scaffold state showing a snack bar needs a curtain because that just takes some time to show it and then it will be hidden again so asynchronous option shepard compose handles that with a crew routine so we can say scope that launch and here's scaffold state snack bar host state show snack bar the message will be not deleted the action label will be undo and what this function will do is it will return the result so if we actually clicked on undo or if we just dismissed the snack bar so we can say val result is equal to this and then check or just say if result is equal to snack bar result action performed that means we clicked on the snack bar if that happened we want to save you model on event restore note it's really that easy and let's just add some more space below a node item so we have space between nodes spacer height of 16dp and that is it for the node screen so quite some code here but now we can actually go on with the add edit node screen and view model so the next step would be to create one in our get note use case so singular where we get a single node by its id and the um and the add node use case we already have that ac so yeah let's do that right click new use case called get node use case i'll just get note gets an instance to our repository and we'll again have a suspend operator function invoke nothing new and here we pass the id of the node we actually want to retrieve so we get the node here we can input this and we just return repository get node by id and we pass the id that's already a use case we don't need to validate anything here so that we can now actually implement the the viewmodel for that so we can go into our adnet add added node package create a new package for the add added node view model hilt view model so just the default constructor here which will be called at added node vm model and that will just take our note use cases so wrap a class again and then let's see what do we need in here so let's take a look in our emulator and check what we actually need what kind of states do we need here so of course we need a state for the currently selected color here that just saves that we need to state for the current title here if we enter something we need to state for the current content and that's pretty much it for the screen unreal again just have an event flow in which we send events for example when we save the node to show a snag bar or so um so here i will actually not wrap this interesting into a single states object because we have um text fields and then that's not so optimal because whenever we type a character here then this text field recomposes and if we put this in a single state class that that combines multiple states if we enter one key here in this text field it will recompose the whole ui and that's so frequently so i will have separate states here the first one for the title um let's let's call it note title string and empty by default and when i think about it we actually do we also want to store something else like the hint yeah we actually do want to store that so we actually also need to stay because that's a custom text field we'll create here we need to state that indicates if the hint is actually visible or not so when we click it it's not visible and else it is that's behavior we need to code on our own so let's let's do that and actually create a wrapper class for that so i will call this nodetextfieldstate this will have a variable for the actual text which is an empty string it will have a variable for the hint so the hint text and it will have a variable is hint visible which is a boolean and true initially because then this will be of type node text field state here and we can create such a node text field state in here let's copy this paste it again for the i actually don't want to change this up there um come on um for the note content oops note content paste it here paste it here we need a state for the currently selected color so vm state node color that's an integer state and by default it is yeah just a random color that's how i solve it you can also just pick the first one i like to use a random color note dot note colors dot random that's a compose color and to get it as an integer we can say two a rgb then we can remove this we're going to need our event flow again so event flow is immutable shared flow of type ui event um don't we have that already oh okay i think yeah i actually talked about that before here in this course but we haven't even done that yet um yeah i was quite confused because i live streamed today and i used these ui events there um and now i'm recording this video okay never mind so what does this event flow for the thing is in jetpack compose if we use these normal compose states then these well they hold state like for example the selected color so when we rotate the screen the the same color should still be selected that's what state does but there are still some things that are more like events so one time events actually that don't represent state so imagine we want to show a snack bar then we will only want to show that snake bow once we don't want to show that again when we actually rotate the screen so it's not really state and that's what we need to use this shared flow for um there is no real native jetpack composed solution here for these events so we need to use the kind of the xml way of doing that works the same way um yeah so we can send you one-time events from our view model in this shared flow and then observe in our ui from that and depending on the event we can do something differently for example just yeah showing a snag bar so i will create this ui event class here that's a sealed class ui event and it will have two subclasses on the one hand it will have a data class show snack bar which will take the message for the snack bar of type ui event and it will have an object save node so when we click on that save button this one we save the node and after we saved it we want to um just navigate back and that's also kind of an event so type ui event and one thing actually i also want to do is we want to assign a default hint here because that is by default empty so for this note title we want to say enter title for the hint and for the content we can set the hint to enter some content cool so now we have our event flow in which we can send events we also need a public version of that which is just event flow as shared flow cool as in our other view model we will have an on event function which will take events this time these will be at edit note events so for this view model so let's go to our edit note package create a new class called add edit note event ah select data class and this will contain some quite some events basically for every single ui action the user can make we have an event here it's it's the same as in our notes screen already so we'll have an event when we entered a title so we basically for every single keystroke we we call this event and this should actually be a c class by the way see a class without that and we use that instead that looks better so we have a value here which is basically just the new title add edit node event then we do have the same for the content so we entered something in the content we actually also have events when we focus our title text field in our content text field because then we want to change our states in the end so we want to actually hide the hints and that is that that boolean for that is contained in our state so let's duplicate that and say focus title um let's call it change title focus and here we pass the focus state so if we're focusing it or not of this focus state type here from compose ui focus we get that from a modifier later on so let's copy this paste it here do the same for the content then what else do we need we need an event to actually change the color change color here we'll have the new color of type integer add added note event and one event will be that we save the notes so when we click on our floating action button like that so a little bit bigger event class here but very helpful because now we can say add edit node event here in our viewmodel and easily distinguish between these so here when we say no title dot value actually we of course need to check if it is entered title then we want to say no title at value just want to update that with a new title no title the value copy text is equal to event.value when that event is change title focus we want to say note title that value is no title value copy so when we when we actually focus our text field or title text field we want to hide our hint so we're going to set is hindi visible to event dot focus state is focused so when we actually not focus on the on the text field we want to show the hint we also want to make sure that the text is actually empty because we only want to show the hint if the text is empty and we're not focused in this field so and no title value text is blank so that is how this essentially works we can copy these two um blocks here come on um because for the content it's essentially the same so here we have entered content note content note content here we have change content focus note content note content note content that should be it for these four states next is when we click on one of these color circles to select a new color so when that is change color we want to say node color color that value is not color actually we don't need to copy anything here we can just say event.color and then here for the last event that is possible that is safe note actually not from ui event here from added node event when we click on save node on that floating action button what do we want to do we actually just want to fire off our add node use case so we actually want to say view model scope the launch so we need to launch a curtin and we're going to have a try and catch block because if you remember um we actually throw uh invalid node exceptions i call it when the title or description of the node is actually empty so here and try we can say node use cases add node we want to add a new node that we construct here the title of the node is node title value text content is note content value text time stamp is system counter melis color is node color.value that's just that value and we actually also want to pass an id here because it could potentially be that we opened an existing node and we then want to update the node when we click on save node and remember updating works the same way as inserting so we have this on conflict strategy replace that means when we insert a node here and this node already exists in the database with that given id it will just update it instead and that's how it works here so we kind of need to have a current node id that we cache here and if that's null we will just insert a new node so let's just scroll up and have that here that can just be a private var current node id of type integer and initially it's null and make it nullable here and then you can see we can pass that here so all we need to do is when we actually have such a node id so when we pass that as a navigation argument we get that in our viewmodel and we assign it and else it will be null so we pass null here that means we'll just create a new node then after we call this use case we can say event flow emit we want to emit ui event.save node so we can then react to that in our in our screen and simply navigate back and if we got an error say if the title or content was empty we want to say event flow mid and here we want to say show snack bar and the message of that snack bar will be e dot message and if that's null we can just say i don't know unknown error or better i think couldn't save note and yeah that's it already for this on event function how do we get the current node in the the current node id in here so essentially when we click on an existing node then we want to then we need the idea of that node in the screen and in this view mode how do we do that well we can just use a navigation argument that's very easy and the cool thing with navigation arguments and hilt is that we can inject such a safe state handle that is kind of a bundle that also contains these navigation arguments so we don't even need to pass that as a parameter to the view model to a function or something like that we can just access this with access it with this save state handle and here it also automatically injects this so we don't need to do anything else other than providing this here so in init we can say save state handle that get we want to get an integer which is the current node id the navigation argument will be called node id and if that's not equal to null then we can do something with that node id so we're going to check if this node id is not equal to -1 because that will be the default we use so if we just click on this floating action button here then we will pass -1 so then we don't have a node if that's not the case we want to launch a curtin in view model scope and then we want to say node use cases get node which we don't have yet let's go to node use cases and add that val get note as our get note use case singular go to app module add that here get note is equal to get note passing the repository and then we have that function here in our viewmodel so we can simply pass our node id to get that node if that's not equal to null so we can say that also then we get the node in here and then we can say kernel id is equal to it.id let's call it node so node.id we want to also update the value of our title to node title copy because now our value copy because now we of course loaded the node and we want to take the the title of our note from our database and put it in the in the actual text field in our state so we can say text is no dot title and the hidden is not visible then and let's do the same for the content of the node node content and node content and we want to update the color so node color that value is know that color and that's it for the view model now so we now have everything we really need here we expose all of our states here also this event flow so next the only thing that's missing is building the ui for the add edit node screen and the first thing for that screen will be that we create such a yeah kind of transparent text field here with a hint i will do that here in our components package of the add edit node package called trans parent hint text field select file make that a composable and that will take a lot of parameters actually of course it needs the text then it needs the hint then it needs a modifier set to the default modifier by default then it will take if the hint is visible or not let's let's also call it is hint visible is true by default and then we need something like an on value change function which gives us the new string so whenever we type something this function will fire off then we need a text style so we need to be able to pass a style for that text we actually display make sure to choose the compose text style set it to the default text style then we need a boolean whether that's single line or not initially let's set it to false i don't know yeah doesn't really matter [Music] and the last parameter we need is actually the on focus change function so that will give us this focus state that we already dealt with in the viewmodel and unit so quite some parameters for this text field but the body of this function will be very small in the end we will just have a box here we can apply the modifier for that box and here in that box we're just going to use a basic text field which is the the text field that you can style the most and let's see here we can basically just forward all of our parameters the value will be text on value change will be on value change modifier won't be here we have single line is single line text style is text style um what else invisibility hint that's not relevant here actually we do need a modifier because from there we get the focus state stuff so modifier is a modifier that fill max with and then on focus changed so here we have the focus state which is just it and we can say on focus change with it and that's it for the text field already we can now display our hint on top of that that's just a text that uses the hint the style will also just be our text style and let's set the color to color dark gray and that's it for this transparent hint text feel actually not we need to do do we need is invisible yeah we do need that because we only want to show this hint if the hint is actually visible i think that makes sense so far so good so now we can actually directly implement the whole screen in our edited note package add edit node screen select file composable add edit node screen then in the parameters here we need our nav controller to navigate back to pop the backstack when we actually click save we need the node color um it will get clear while we need this here um because we actually only pass the iud as a navigation argument it seems i mean it only makes sense to do that it seems but we actually will also pass the note color as an additional argument i will explain why we do that when we when it makes sense to explain that and we have our view model of type added node view model is equal to hill view model cool so in here let's first get our references to the states from our v model title state view model note title dot value content state is view model note content.value we want to have a scaffold state in here to show snack bars remember scaffold state and as i said we will actually animate the background so when we switch the color there is a color fade effect here i think that looks really cool and for that we use something called an animatable so val note background animatable is equal to remember animatable we choose the one here with the color the initial color one because we of course want to animate color and the initial color for that well what is that it's a color of our note color and now you you already see why we have this note color that we also pass because we need to show the initial color of the note that we actually selected if we didn't select the node so if we just click on [Music] this floating action button we select a random color by default which we get from the viewmodel but in case we actually select an existing node then it needs to have the same color as that existing node and that's why we actually need to pass the node color here as a navigation argument because if we don't do that then it will take a random color from our view model as the default color so even though we pick this orange node it could be that orange is selected but the background is actually like blue or so so that's why we need to pass that as navigation argument and here we actually want to check if the node color is not equal to -1 so we actually did click on an existing node then we want to choose node color and else we want to choose the color from our view model this one it's all about the initial color here and to animate this we need a protein scope so we say remember curtin scope again and then just as in our notes screen we want to have a scaffold with a floating action button which we can create here on click of that what will what will that do it will basically just fire off an event in our viewmodel to save a note so your model on event safe node actually this one here we want to set the background color to colors primary here [Music] let's do it like this yeah and the content of that floating action button will just be an icon not icons just icon icons default safe and say safe note cool then we want to assign the scaffold state here to our scaffold so scaffold state is scaffold state and then in the end we're just going to have a column here then a row of these these will just be boxes here we have a text field another text field and that's pretty much it so column um the modifier will be modifier filmax size the background color will now be our animatable how is it called node background animatable.value so that will just assign the current value of that animation to the background and we're going to apply some padding of 16dp and import that then let's have a row in here for our colors modifier is modifier dot fill max width and some padding of let's say adp here let's set actually the the horizontal arrangement to space between so these are evenly spread and in here we're just going to have one of these circles for each color we have so we can say note note colors for each color let's say val color int because these are composed colors so we can say color that to a rgb and then we are now designing such a bubble here using a box so you can see it's just a circle we have a border if it's selected and we have a shadow oops [Music] so we're going to use a box modifier is a modifier a dot size i will say 50dp here um if you want to support a lot of screen sizes you probably need to play around with that a bit or make that depending on the screen with i'll just hard code it here and for my emulator that will look fine then we want to set a shadow of 15dp and a circle shape we want to clip this to a circle shape then we want to have a border actually first the background of the color so this one here then we can say border the width of the border will be 3dp the color of the border will now be if view model note color value is equal to color int that means um the the color is actually selected then we want to set the border to the border color to colored black and if it's not selected we set it to color transparent and then as well just the shape set that to a circle shape so the border as well and then we can make it clickable so when we click on this um color bubble and then we actually want to animate our color to the new color so we say scope launch node background animatable animate 2 so that's the suspend function here and the target value will be well it will be our new color so color of tab coloring and we can specify an animation spec to to define the duration of that animation set that to tween and say duration melees to 500 so the animation will last 500 milliseconds which i think is a good amount here for a very smooth transition and then after that scope we can say view model on event and we actually want to say changed color because when we click on that we of course want to change the color to the color hint so far so good now we have these two these these boxes here for each color we don't need content here for these boxes yeah that's already a big part of our screen the rest will actually be very quick because the rest only consists of text fields so below um below this row here i think we want to have a spacer with height set to 16 dp and then we use our transparent hint text field um i hate it um what will the text be title state.text let's put that on new lines the hint will be titled state at hint on value change so whenever we type something we will say view model on event enter title with it so just passing the new title and on focus change is view model on event change title focus with it as the focus state then we also want to say is hint visible is equal to title state is hind visible we want to say single line we set this to true for the title we only have one line for that and i'll set the text style to material theme typography h5 that's it for the first text field let's copy this stuff here for the second one have another space of 160p and just replace the stuff with content state here here and here here we want to change it to enter content change content focus remove single line for the content we can enter as many lines as we want the text style will be body one for the content so not another headline and we also want to make sure that it fills the whole size so modify is modifier fill max height now one thing that's missing is actually observing our events if you remember we have this event shared flow in which we send events for example when we want to show a snack bar right right here or when we want to navigate away so let's do that scrolling up we can simply do that using a launched effect block setting the key to true so we only execute this once not again when we actually recompose or so and here we can just say view model event flow collect latest so we just get all the events here the latest events and when the event is show snack bar we can show snack bar here and when that is save note we can um actually not that one we need the ui event um save node here from the ui event oh come on like this when we're going to show snack bar we're going to say scaffold state snack various state show snack bar and the message will just be event.message and when we save a note we actually just want to call our navcontroller dot navigate up so we just go back to our notes screen okay that's it for edit note screen now the very last thing we need to do is to actually set up the navigation which is not that much so in here in presentation in util i want to define a screen class you will know that if you want my compose videos that will just contain our different screens and the corresponding routes now route is a string if that is i mean we have different options on the one hand we have our notes screen of type screen and we pass around for example note screen and then we do have the same for add edit note screen and we say add edit note screen and then for the navigation stuff we can go back to main activity let's have a surface we don't need this the surface will have a color material theme colors background and in here we are going to have enough controller first remember nav controller and then we're going to have our nav host in which we can define our single screens so nav controller we don't want to graph instead we want to start destination which will be um screen that node import screen screen.note screen that route and then in here we can define our different screens using such a composable function the route will be screen node screen that route for this one we just have our node screen that we put in here with our nav controller and i think we need an annotation here yep let's take this one and for the second one we will also have some arguments so composable route is screen that add edit node screen.route and here we actually also want to add something to that route because we we want to be able to pass arguments and both arguments are actually optional here so the node id and node color so we are going to do this just as with uh get parameters in the url we can say plus question mark note id let's move that in the next line node id is equal to node id and write this exactly as i do here then we have an and for the next parameter which is node color is node color just like this so that way these parameters are also just optional so we don't need to pass them if we just click on add note then we don't have any of these then we need to define these arguments using such a list here a list of nav arguments nav argument the name will be node id for the first one and then we have such a builder in which we can actually set the type of that argument which is just an integer type and the default value for that id will be -1 let's copy that for the note color name does note color also integer also one by default um yeah and then here in that block we want to call our add edit node screen passing the nav controller and for the node color we can actually just pass let's do that here in a variable well color is it so our backstack entry arguments using node color as the key um well let's i think we actually need to do get integer get question mark get int note color and if that is now we say that's minus one and then we assign color here oh man i think that should be it um one thing we actually need to do is we need to also annotate this main activity with android entry point so we can actually inject our review models with dagger hilt in this activity but i think i think i think i think that should be everything we need to make our app run i will just launch it now and we'll see how it goes so let's check our emulator here and i'll see you back when gradle has actually finished building okay gretel is actually done let's see it installs and okay at least nothing crashes can we toggle this we can so that works fine let's add a note click that oh i remember we forgot to add the the navigation let's quickly change that it's on our note screen um yeah i said i'll make that later and i already knew i will forget it and i did so on our floating action button when we click this we can say nav controller navigate to screen add import screen add edit node screen at a route we don't pass any parameters here because we want to create a new node and when we click on a specific node here in our clickable block in our lazy column we want to navigate to that node and this time we actually want to pass the the navigation argument so nav controller navigate and we're gonna navigate to screen add edit node screen dot route and here we say plus question mark note id is equal to note dot id and note color is equal to note color and that should be it so let's relaunch so let's see there we go it launches if we now click plus we actually get to the screen which looks good we can change the colors the animation is working fine let's create a yellow note greenish rather hello youtube um this is my note content new line new line um hopefully this works and if we click save there's our node here we actually need to change the color for that icon not too big of an issue if we click the node we get to the node it loads let's update this another color maybe click save the node is updated we can uh let's add another one actually to check the order just something with a in the title um yeah doesn't really matter this color and if we now change by title descending then this uh blue note should pop up at first that's what happens ascending this node ascending by date so the the earliest node is on top and by color yeah that's that's doesn't really make sense here cool let's check if we can delete notes that works we can undo that works as well and yeah this undo is really hard to read here um it's it's very hard to change that you can change it actually by providing a your own snack bar composable to the scaffold but that is uh yeah would be a little bit overkill here feel free to do that but that i consider that a waste of time so yeah if you really want to change this icon color which of course looks better then just go ahead and go to the note icon note item and then here this icon button i can and we can say tint actually is material theme colors on surface also i think that should already change it so let's see one final time but yeah that's a fully working app congratulations for actually following through this long i know this is this is probably my longest video i have on my whole channel so i would be very very very thankful if you could leave a comment here if you could leave a like um this is so much work to make these videos but i hope i can actually help someone with that because this is the stuff that really gets you jobs that really makes you a good developer it's not all that fancy data structure and algorithm of course it's uh for getting a job that's actually also important but not for the actual practice yeah so if you want to build apps videos like these help you and of course just practicing on your own um yeah so i would be really pleased if you could leave me a comment down below i would be really pleased if you could subscribe to my newsletter down below to receive more of such advice here and if you actually want to support me and actually also want to get more of this free content and especially also want to learn more advanced stuff then you also really want to check out my my paid courses down below it's the first link in this video's description and yeah just check them out even if you don't plan on buying one just see what you would get there and yeah i can promise you you will learn a lot if you don't learn a lot then you will always get your money back if you just leave me a message within 30 days so please share like comment and if you actually like this and want more of this you can also give me suggestions for more playlists ideas that it's not really a playlist more video ideas project ideas that i should make in future with clean architecture so i will see you back in the next video and i wish you an excellent day evening morning whenever you watch this bye bye
Info
Channel: Philipp Lackner
Views: 281,125
Rating: undefined out of 5
Keywords: android, tutorial, philip, philipp, filipp, filip, fillip, fillipp, phillipp, phillip, lackener, leckener, leckner, lackner, kotlin, mobile
Id: 8YPXv7xKh2w
Channel Id: undefined
Length: 143min 58sec (8638 seconds)
Published: Wed Sep 22 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.