How to Make a Clean Architecture Dictionary App (WITH CACHING!) - Android Studio Tutorial

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hey guys and welcome back to new video  in this video i will show you how you can   make a dictionary app while sticking  to the clean architecture guidelines   so many people actually asked me to make another  of these videos about making an app with clean   architecture and especially to make an app with  clean architecture and caching so that is what we   what we will do today and what you will  learn today so it's a very simple app   i will show you that in a moment um that basically  has the option to to get dictionary entries so   just info regarding a specific word from a remote  api and then it will save that in a local database   to cache it so we can also access these words that  we accessed in the past um even if even if we're   offline so if we don't have internet connection  so let's actually take a look here how this looks   like in the end what we will build here you can  see it's very simple it's just a search field   here but if we actually enter something in that  search field let's say we search for android in   the dictionary then we actually get a dictionary  entry here so we get the word obviously how it's   pronounced and just some info about that word now  if we enter another word like morning for example   then we get information about morning and  if there is if there are multiple entries   in the dictionary for a specific word like  probably for bank then we actually get multiple   multiple entries of this bank because a bank  could mean something else depending on the context   so yeah it will actually consider that another  cool thing as i said if we simply enable   airplane mode here so we don't have internet  connection and we close our app and reopen it here   then you will notice if we search for something  else like apple that we will get a toast here   a snack bar that we couldn't reach the server  because we don't have internet connection however   if we search for something that we already search  for like android we actually get the information   because it's contained in our local cache and  the same will happen for bank so if we search   for that we will get the bank information and  we also get the snack bar because yeah we still   don't have internet connection to refresh that so  let's next actually take a look at the api that we   will use for this that is the pre-dictionary api  so you don't need any form of authentication for   that you can just use it this is the website so  dictionary api.dev you can try around there you   can also enter some words here you can see a  search for warning and if we press enter here   then we simply get a json response and in the end  we will just do this but in our app so you can see   that will be the actual url that we will then  use in our app api.dictionaryapi.dev and then   with the corresponding url afterwards so all you  really need to do before jumping into this video   is to check out my github repository down below  because there you will find the dependencies that   we will use for this project so you can you can  just pull this from my github or you can download   it as a zip and open in your android studio  or you just copy over the dependencies into   your build.gradle file which i will quickly go  through here so you actually know what we're   dealing with and the dependencies that i  actually use for what we will use these   so you can see the compose dependencies which is  just the view model dependency so yeah we will   obviously stick to mvvm and clean architecture  here and for that we we need view models   curatings i think that's obvious for network calls  and for actually accessing our database in an   asynchronous fashion uh lifecycle scope stagger  hilt we will use that for dependency injection   retrofit for the actual api calls room for caching  our actual dictionary entries and kotlin extension   and curating support for room so that we can  actually use creatines in combination with   room so without further ado let's actually jump  right into the project and start with setting up   our project structure because i i usually just  like to start with that when i do these videos   because it gives you a good understanding of of  the general structure and project will build and   which layers we have and which features we have  and why it's actually structured that way so first   of all if you're new to clean architecture it's  basically something we yeah a type of architecture   we build upon an existing architecture like mvvm  so usually in mvvm we have our ui layer so in   android that's the activities fragments or if we  use jetpack compose like we do here then that's   our composables then we have our view models that  contain our business logic and our data layer   usually in form of some repositories that actually  make the api calls or calls to our local database   here in clean architecture that's actually quite  similar but we have an additional layer which   are the so-called use cases these are located in  the so-called domain layer so in the domain layer   it's it's actually the shared layer between data  and ui so it will basically contain the business   rules and the business logic of our app and when  we actually get to that point of creating these   use cases then it will it will get clearer why  we have these and why that's actually useful   um when i actually create a  clean architecture project   i usually structure that by features  and these features i structure by layer   so what that means is a single feature in your  app would for example usually just be a set   of screens that somehow belong together so if  you have a social media app then that could be   everything regarding profiles so one feature could  be the profile feature that contains the display   profile screen it contains the edit profile screen  maybe some kind of log out screen i don't know   something that's related to profiles then you  could also have some kind of post feature which is   just a set of screens that deals with posts like  a post list feature or post list screen rather   or a post detail screen stuff like that and i  have these features as the root of our project   so what we will do is in this app because  it's so simple we just have a single feature   but i just want to show you right away how you  should do it in a real app so we will create   a new package here in our package called feature  dictionary for example let's create that and then   in this feature we can now create our different  layers that we have so that is presentation   domain and data presentation is basically  ui so it contains our composables here   domain contains as i said the use cases and  our actual entities that are shared between ui   and data and the data package will then contain  our repository implementations it will contain our   our room database classes and our retrofit api  interface so let's actually start to create our   presentation layer here i will leave all of these  empty for now just to have a general structure   we will then have another package in  the feature dictionary which is called   domain and inside of that we will have a  use case package for use cases we will have   a repository package in the domain package and  that will actually only contain the definitions   of our repository but i will get to this  later we will have another package for our   models so a model package that's already for  the packages under the main layer the next   layer would be the data layer so in the feature  dictionary new package data dot remote for example   so we will have a data package and inside of that  data package we have two types of data sources in   our project on the one hand the remote data source  which is our api and on the other hand the local   data source which is our room database so we also  want to have a local and in that local package we   will have another package for our entities that  we actually save in our database so every single   table and actually in the remote package we will  also have a similar package which we call dto   for data transfer objects so that is basically  the kotlin implementation of the json response   that we get from the api and then we will have  some kind of mapper functions that takes this json   response and maps it to a class in our actual  domain layer which we can then share with the   presentation layer which contains the view models  so that's it for our three main layers another   package we need in the feature dictionary  is a package for di dependency injection   which we use dagger hilt here for so yeah later  you will understand how that will look like   and i think one more and that is actually in our  root package so outside of this feature dictionary   in the word package we want to create another  package called core so in clean architecture   the core package is basically the one that that  contains everything that's shared between multiple   features so right now we only have one feature  and it would be totally reasonable to put the   stuff from the core package instead of the feature  dictionary package however as your project grows   you will have more features and there will be  classes you actually need in multiple features so   you actually put these classes in the core package  and here in this particular case we will have   a usual package in here because later on we  will have a utility class that we will actually   need throughout the whole project if we would  have multiple features so far for our project   structural let's actually start to code and  i want to start with implementing our api   we will do that in our remote package here  right click new cotton class of file and i   will call that dictionary api that will be  an interface yep i want to add that to git   and yeah i won't dive deep into retrofit here  i assume you know that if you're watching this   rather advanced video this will essentially  just have a single function that we now define   um how we actually want to access our api so  ridge route and what that basically takes as   parameters and returns so that will essentially  be a get request because we want to get some data   and now we have to define the url here which is  api slash v2 slash entries and trees slash en   word in curly braces because that will be the  placeholder for the word that we actually enter   by the way if you want to have that dictionary in  your native language you can simply swap out this   en with your language code of course the api  needs to support that i tried it with german   with de and that worked so then you can  look up for your own words in your language   i will leave it english here and that will  essentially be a suspend function get word info   and we will have a path parameter which is word  here and we'll just call it word which is a   string obviously so whatever we pass you for the  parameter will be inserted here in the url when   the request is actually done now this function  of course needs to return something here   it will return a json response that is parsed here   to one of our dto classes that we don't have yet  so let's actually first declare these classes   before we can actually define the return type  here for that we need to go to our api website   and take such a word here let's actually i don't  know if this is the best word to demonstrate this   maybe we search for bank i don't know if  this is even better um but we just want   to copy everything that we get as a response  here we want to go back to android studio and   right click on dto new kotlin data class  file from json that is a plugin that i   have here if you don't have that you can  click ctrl alt and s to open your settings   you can go to plugins and then simply search  for that kotlin to adjacent to kotlin class here   install that and then this option will appear for  you you probably need to restart android studio   it will basically just take the json response that  we got from the api and we'll parse that into our   kotlin classes so the class name we want to parse  that into is word info dto so we press enter and   it will now generate quite some classes here and  we actually need to restructure that a little bit   here you can see five classes essentially and  this is the the word info dto that makes up the   the root response basically you can see that  it's nothing else than just a list of items   so we can totally remove this because we can also  directly return that list of items in retrofit   and we want to call this word info dto item  just word info dto so we can click on that   shift f6 to rename this and just remove item  here press enter and there we go if we open this   up you can see we have meanings phonetics  and the rest is just strings that is fine   let's actually also rename these meanings  and phonetics here to something like dto   also shift f6 by the way uh yeah just clicking  ok here doing the same for phonetic shift f6   and appending dto clicking ok  and one last time for definition   here you can also by the way see these antonyms  because the response wasn't optimal so there   actually weren't any antonyms so it couldn't  determine which type this had so just used any   here which we wouldn't like instead here because  i looked up a word that had antonyms we can just   use string so i know that will work when we parse  that and i think the rest is actually fine let's   make sure that we rename this pressing shift f6  again definition dto clicking ok again and i think   the rest is actually good here okay so now we have  these classes let's jump back to our dictionary   api and actually return that because it returns  a list of word info dto it returns a list because   as you've seen if we enter bank for example  then there are multiple words that correspond   to a single um query basically so yeah we we could  return multiple of these word info dtos now what's   next so in clean architecture we typically have  so-called mappers when we have these dto classes   what a mapper does is it basically takes one  object and maps it to another if we have these   dto classes we would like to have these mappers  to map such a dto to a model of our domain layer   that we will actually then show in the ui so that  should only contain the data that we actually want   to show in the ui and because we have quite some  dto classes here we need to create such a mapper   function for each of these so let's start with the  definition dto and we can just put this in here   in the class body and i'll just call this  to definition so it will just return a   definition without dto because that's how  we will call it we will call the class in   our domain layer so let's actually create that  in domain model called definition will be a data   class and for this particular case it will i  think just contain the same entries as here   so we can copy these paste these and i think the  example is actually nullable here let's also make   that nullable here in the dto class i just see  that on my code that i have prepared because   it seems like some words don't have examples and  then it might crash if it tries to parse that   however let's simply return a definition now  here from our model package and we can just   say we turn a new definition and we just pass  our values as these are this seems a little bit   um unnecessary here because we just create an  object that looks exactly the same as we have here   but it's still good to stick to that in terms  of consistency usually these model classes just   contain less information only the information  we really need because sometimes these api   dtos and apr responses contain so many data fields  you don't always want to have these in your ui   then we'll have an example here and we will  have synonyms so far so good let's jump to   the next layer which is the meaning dto so  let's also create that in our model package   just called meaning here also a data class  and this will contain a list of definitions   now we use the list of digit definitions  that we have in our model package   and it will contain the part of speech which  is just a string so then in our definition   actually meaning dto we can write another  mapper function to meaning returns a meaning and we can say we turn meaning the definitions  is definitions and the power of speech is part of   speech and here we get an error um yeah absolutely  because we actually expect definitions here   being of the type of the definition we have in  the domain layer however these definitions are of   dto so this is now the first time we can make use  of our mapper functions by simply using that map   it dot two definitions so we just go through  the list and map everything every single item to   such a definition let's do that  one last time for our word info dto   which is um here in our model package as well so  right click a new kotlin class called word info   data class and let's take a look in our root  info dto here um exactly actually here we   are only interested in this single phonetics  so we don't need this list of phonetic dtos   which contains audio text so that's something  we now don't need in the model of the domain   layer so we can simply leave that away and not  pollute our ui with that so here we can simply   copy everything from word info dto go to word  info paste this and remove the phonetics value   and in word info dto we can say okay we want to  have a function to word info returns word info and we can say return word info meanings is equal to meanings and origin  is equal to origin ponetic is phonetic   and word is word and i'm not sure right  now if we take a look here in word info   yeah you can see that's a mistake i didn't want  to use the meaning dto and said just the meaning   from our model package in the domain package  and we can remove the imports here because now   we actually then need to go back to word info  dto and also map these meanings so we say that   map it to meaning and that's now everything  for this mapping stuff here at least for   the dtos so now we basically have our uis  our api set up of course we still need to   create that later but that's not what we will  do now instead we will next set up our database   and for that i will actually start with the entity  so the table that describes which fields we want   to save on the table that will be put in this  entity package here in our local data source   package right click new class and that will be  called word info entity will be a data class   we annotate this with entity and well in the  end we save very similar things in this database   as we get from our api because we of course  want to be able to display everything   as usual as if we would get that from the  api so we have the word we have the phonetic we have the origin which is also string and we  have the meanings which is a list of meaning   and finally because we're dealing with database  entities in an sql database we also need an id   for every single entry which we annotate with  primary key so we just make sure that we can   uniquely identify each item and then here in the  body we will also have a mapper function which is   a lot less work here for this database entity  so function two word info so that we can also   easily convert that to a normal word info of  our domain layer so word info and that returns   word info meanings is meanings the word is word  origin origin and actually phonetic is phonetic   so far so good that's it for the entity here  for our database the next thing we need to do   for our database is to actually set up our dao  our data access object in which we will define   the functions how we access our database so here  our local package we right click new kotlin class   and that will be called word info dow and that's  actually not a class here that is an interface   that we annotate with at dao so we will have three  functions here in this dial on the one hand that   will be of course a function to insert something  in the database so we have a suspend function   insert word info let's actually insert  multiple words here so infos would be a list   of word info entity actually because  we're dealing with our database here   and we uh annotate this with add insert we  can pass an on conflict strategy of onconflict   strategy that replace so if we try to insert an  element that has an id that is already in our   database we will simply replace it then what else  do we need we need a function to actually delete   all word infos in our database because that is  what we will do when we try to write something   into our cache we will just clear our database  and insert our new words basically to update our   our existing words so we just want to have a query  where we can pass a list of words and we will just   delete that list of words i'm not meaning that we  delete all words just that list that we pass so   query to have a custom query here delete from word  info entity so we want to delete from this table   where the word is in our words list  that we pass as a parameter here   so that will be a suspend function delete word  infos and we pass our words as a list of string   so we just pass a list of strings  for example bank android and morning   and what this query will do is it will just delete  all word info entities related to these words   and we will have one last query here which is  used to actually get a word to read it from   our database in case we want to load that from  the cache so query select everything from word   info entity where the word is alike and i will  explain what that means here after i wrote it just type this off here so we say where  the word is like this so in sql these um   what would be in kotlin the or operator  in sql that is used to concatenate some   words or some strings and these percentage symbols  here just mean that's a placeholder that could be   everything so we're just searching for our word  in our database basically that's really what this   query will all do in return so suspend  function get word infos for a specific word   and it will return a list of word info entities  so far so good that is it for our word info   dao the next step and the last not the last  step for our database but the next step for our   database is to set up the actual database class  in our local package as well new kotlin class   called word that's called word info database  select a class here that will be an abstract class   we annotate this with at database to  make sure this is our room database   the entities will just be a single  entity here which is our word info entity   double colon class and we need to  specify a version which is just   one here so every time you update your  database you need to increase that version   and we need to make sure that this actually  inherits from room database and all we really need   in here is an abstract valve for our dao which  is a word info dao and room will actually create   the rest for us okay so there's actually one last  thing we need to do for our database maybe you've   noticed that already if we take a look in our  word info entity there is an issue with this class   because right now we just try to save a list of  meanings in our database but room has no idea how   we can actually do that because we can't naturally  save lists in a room database and especially not   lists of a complex data tape like meaning so how  can we do that well usually if you want to save   a list in an sql database that is a sign that you  should put the objects that are actually in that   list in a separate table here because that's what  relational databases are actually perfect for but   in this case just for simplicity and for this  for the sake of this video i don't want to do   this because that really makes the stuff complex  if we would have a separate table for meanings   and then a separate table for definitions and  stuff like that so what i will instead do is   i will directly save the list of meanings in a  word info entity by simply parsing pursing it to   a type that room understands and that is simply  a string so we will take this list convert it to   adjacent convert it to yeah adjacent string in the  end and save it here in our database and for that   we can create something called a type converter  in room so that if there is a type and room that   yeah that's not understandable for it then we  can define how we can actually transform that   to a type that room actually understands and  for this json parsing process i will actually   do something that might not be obvious right  away why i do this but i will explain it after i   i did it so actually in our data package  here i will create another util package   and in that util package i will create a  new interface that's called json parser   this interface all this will really contain are  two functions to get an object from a json string   and to parse an object to a json string it's  a function that will be a generic function   rom json which will actually take the json string  and it will contain the type we actually want   to parse it to so that will be a type this  one from java here and it will then return   an object of our type t here and we will have  a function that is actually used to convert   something to a json string here we instead  pass our object of type t and also the type   and it will then return a corresponding string  let's also make this nullable in case something   goes wrong there so let's actually try to  understand why why i make that interface here   why don't i just create the type converter and use  our json instance in there to manually parse these   strings from it to json well it could happen that  at some point in your project you decide that you   want to use a different library to paris jason's  strings so right now i will use json here because   i'm just used to that but there are so many json  parsing libraries there is marshy there is cotton   serialization and stuff like that jackson i think  and if you want to switch that then you need to   change every single spot in your code where you  use that dependency so where you use json where   you use moshi i don't know whatever however if  you have such an interface and everywhere in   your code you just use that interface then that's  not necessary because then only what you need to   change is the implementation of that interface  in which you basically tell your code hey this   is how you use machine to convert something from  into json this is how you use json to do that and   stuff like that so in this youtube package we  will have a class which is our adjacent parser   and that will now implement that interface  json parser because every single json parser   definitely has these two functions and here  we now have that concrete implementation   of doing that with json so we actually need a json  instance for that private val json of type jason   and using that instance we can now simply say  return json.from json we pass our json string   we pass our type there we go and here for two json  we say return json to json and we just pass our   object and our type and now when we create our tab  converters let's do that then it will get clearer   where we did all this in our local package a new  cotton class called convertus select class here   and that converters class will now take such  a json such adjacent parser and here we don't   specify is that a json parser is that a margin  parser because we don't care this class really   only needs to pass something from it to json  it doesn't care which library is used for that   and if we would hard code this here to be a json  parser and at some point in our code we decide to   use a marshy person instead then at every single  point in our code where we use that json parser   we would need to go and change that to a marshy  parser so that's just a lot of changes you need   to do if your project grows if we do it like  this we don't need to do that now we just need   to define two functions here basically how we can  convert something from a meaning list to a json   and how we can take a json string and convert  it back to meanings list so from meanings   list or rather from meanings json i think um where  we get the json and we return a list of meanings   so now we just define that for  a room so we're going to return   jsonparser from json the type  is an arraylist of type meaning and it will now take two parameters on  the one-handed json string which is just   json and the type this is a bit more complex  because uh yeah that's just what we have to do   here to define the type of a list because our  list has a generic argument here and it's not   easy to to specify the type here so what we  need to do is we need to say object of type   type token the type token will be of type a  wait list of meaning then parentheses curly   braces and then after that we say type so that's  basically how we handle that and if that for   whatever reason returns null we can simply return  an empty list so let's annotate that as well with   type converter to mark this as a type converter  function and then we will have another function   to actually take a list of meanings and  convert that to json so two meanings   json actually a function it will  take our meanings list this time and we'll return a string here we  return json parasite to json this time   the object will be meanings and we need to  specify a type here we can again take the   same type token paste it here and if  that is actually now we just want to   return an empty list in adjacent format  so this will also be annotated with type converter and we also want to annotate  this class with at provider type converter   the reason why we do this is that we want to  provide our own instance of these tab converters   because by default tab converters can't have  constructor arguments like we use here but we   need that json parser here so we need to provide  our own converter which we will later do so just   for room that it actually knows that it shouldn't  create that on its own now back to our word info   database here we need to pass these type  converters using type converters i'm just saying   converters double colon class so far so good  that was difficult here for the database stuff   but that is pretty much it for the data layer now  actually not really for the data layer but for our   two data sources our api and database the actual  important stuff that most of you want to see here   is yet to come and that is the caching part  which also obviously belongs in the data layer   so let's see how that works first of all i want  to paste the class here in our core util package   which is called resource i use that in every  project those who actually watch my project   playlist know this class it's just a utility  class that is used to distinguish between   loading success and error states of whatever it's  it looks super complex it's actually not um so   we can just use it when we get an api response  to just forward the results to the viewmodel so   the viewmodel actually knows was that a successful  unsuccessful response are we still loading just to   update the state so what will we actually do now  the next step will be to create our repository   and we will actually have the repository  definition in our domain package here   so the interface of that repository belongs  here in that repository package let's do that   called word info repository interface and that  repository will just need a single function   and that is to get a word info so function get  word info we pass a word and the return type   is a bit more complex it's actually a flow from  cotton x curtains of type resource of type a list   of type word info our domain word info yeah so  we will actually use a flow here to emit multiple   values over a period of time so we can first emit  our loading status then we can get our our actual   word interest from the cache we can then make  the request when we get the response we can emit   another list of word infos that we actually got  from the api and so on and so forth i will explain   that more when we get to the implementation now  and that belongs in our data package so under data new package called repository we actually create a cotton class which is  word info repository implementation and that   will take two dependencies on the one hand our  api which is our word actually no dictionary api and it will contain our dao  which is our word info dao   and it will implement the  word info repository interface   we can press ctrl i to implement that single  function and now this is where the caching logic   belongs because that is what the repositories  is used for and what is the job of a repository   it's used to take all the different data sources  you have in our case that's api and database   and then it will take that and decide which data  should be forwarded to the viewmodel into the ui   when it comes to caching you should usually stick  to the single source of truth principle what that   means is that all the data that we ever get and  display in our ui comes from the database so   we will never get data directly from the api and  directly display it in the ui instead what we will   do is we get the data from the api and insert it  into the database to then show it in our ui from   the database that just makes things a lot easier  because we know all the data will always ever only   come from the from the database so that's just  what's called single source of truth which will   probably hear quite often if you're dealing with  caching so let's actually start to implement this we can start by just saying this returns a flow we  use the flow builder here and in here we can now   simply say emit when we want to emit a specific  value so we can say it made resource success amid   resource loading and then the viewmodel will know  about that so first of all when we initiate that   request we want to say re-emit resource.loading  because that's what usually should happen when we   start request that we start to display a progress  bar now the next step is that we actually get the   first data from our database so we can directly  show word information if we have that in our cache   so we can say val word info actually wrote  infos and that is equal to dao we get word infos   and here we need to pass the word we want  to get the information for which is the word   we get from the parameters here and we simply  map that to our word info from the model class so   that's also what's the job of the repository just  to map data objects to our domain level objects   now these are the cash word inverse so if we  have some of these we'd like to now emit these   to our ui so we can directly display these  even if we're still loading and waiting for   the update from the api so right afterwards we can  say emit we're still loading from our from our api   but this time you can see we can actually  attach some kind of data here to that loading   so we can say the data is now word infos and  that will now notify the view model hey there   is actually word in first to display in our ui  now and the next step is to actually make the   api request and initiate that we need to do that  in a try and catch to just make sure we catch   exceptions on the one hand we  want to catch http exceptions   if we get an invalid response and on the  other hand we want to catch io exceptions   in case something with the parsing runs wrong  or the server is not reachable oh we don't have   internet connection so let's start here  in the try block what do we want to do   we want to get the remote word infos from our api  so we say api get word info and we pass our word   so far so good so now if we're actually at  this point in our code and the request was   successful so it didn't go into one of these catch  blocks we can now actually delete the word infos   that we got from the api we  can delete this in our database   and update these with the new word infers  that we got from the api so we can say dao   delete word inferiors and that takes a list of  string so we can say remote word infos since   that's a list of word info dto another list of  string we need to simply map that to it that word and right afterwards we want to insert  the new word interest into our database   which is remote wordinfos.map and it dot two word info so we simply map that to  oh it's actually no it's it's a word info entity   so we don't want to map this to our domain object  instead we would like to have another mapper   function here so we can just say info entity  entity we want to do that in the dto class so   remote dto word info dto and just have another  function here two word info entity and i'm   actually not sure if we need this seems like we  don't so let's actually just make this the entity   mapper and return an entity here return  entity here and that's already it so we   just need to change the names because that way  we can simply convert it to word info entities   and save it in our database correspondingly if  we get errors here then we simply want to emit   a corresponding error resource so emit  resource dot error and we can attach a message   i will just hard code this here typically you  would like to use some string resources but for   time reasons i'm not going to do this let's  format that a bit have a message equal to this   so here we can just say oops something rant  wrong and we can still attach some data because   also in an error case we get data from our  database potentially so we can say the data is   word infos and we can copy this in mid block   paste it here oio exceptions i typically just  default the message to um couldn't reach server check your internet connection and we can  also attach these word info so we still   display something even though  we couldn't reach the server   so far so good so now we made the network request  but there's still something that needs to come   afterwards because what we did here is just that  we got the words from the api and replaced them   in our database but we didn't really emit them to  our ui so what we want to do here is we want to   say val a new word infos is equal to dao get word  infos for a word and we want to map these two word   info objects so we just read the corresponding  words from our database again and now we can   simply emit these and that's in that case it's a  resource.success and we passed new word infos and   that's our caching logic here in the end so quick  recap we first emit loading before we start our   database request and we display the progress bar  we then read the current words from our database   so the cached ones we emit these with this loading  status again then we initiate the api call when   we get the result we simply replace the items  in our database with what we got from the api   if we have errors we simply emit these and if we  don't have any errors we simply emit the success   resource here with the words that we got from  our database so that's not really it for our   data layer the next step is much easier to  understand that's actually in our domain layer   implementing the use case so we will have a  single use case to actually just get a word info   a use case in the end is just a single  thing the user can do in your app   that's usually connected to some kind of  repository function but doesn't need to be   in this case it is and in this case it's a  very simple use case so let's go to domain   use case create a new class called get word  info and the the good thing about use cases is   these names that we give them make it really  clear what's contained in this class you you don't   need to be albert einstein to to understand  what kind of code will be in this use case here   we will just need our repository  repository of type word info repository   and here we will have an operator function invoke  so we override the invoke operator that's just a   fancy thing i like to do because  that way we can call the use case   as if it was a function even though it's actually  a class you will see how that will look like we   need to pass the word here for that use case  and it will just return the same thing our   our repository returns so flow from kotlin  curtains of resource of a list of word info i want to make sure so validation logic now goes  in here so we want to make sure if the word is   actually blank so if the user did not enter  a word if we cleaned up our query basically   we just want to return an empty flow so then we  don't want to make an api call in all other cases   there is not that much to validate  here we just want to return repository   get word info and pass our word and boom  that's our use case it's really that simple   now what comes next the next thing will be to  write our view model a word interview model   which will basically just call this use case  the view models will always call you use cases   and then forward the result to your ui so it  will take the result from the use cases map it   to corresponding compose state and provide that  for the ui your models belong to the presentation   layer so let's put it in here new cotton class  called word info view model and select file   i have a fancy shortcut here  hiltviewmodel you need to tab this by hand   and that will be called word info viewmodel   then we will inject the penalties here using  daggerfield in this case it's just our use case   so val get word info of type get word info we  will we will have the daggerfield up later when   we created the viewmodel so then we will be able  to inject that here then what i like to do is to   create a state class here for each screen that  just displays that just um yeah kind of is a   wrapper class for the state variables that  are relevant for the ui on that screen so in   the presentation layer here as well we'd like to  create a new carton class called word info state data class and that will here just  contain two variables or objects on   the one hand it will contain our word  info items which is a list of word info   default to an empty list and we have a boolean if  we're currently loading something and that's false   by default so now we can use this wrapper class  in our view model to provide some state for ui   first of all which states do we need on the one  hand we need a state for the actual search field   i like to decouple that from the state's  wrapper class because that changes so frequently   and if we always update the state's wrapper  class we would uh yeah basically update our   whole ui every time we have a keystroke on the on  the edit text so that's why i simply like to have   a separate state i also have a shortcut for  that you probably also need to type that by hand   which is called search query is a string  and initially it's an empty string   so we just expose the non or the immutable  state here of compose to the ui so the ui   shouldn't be able to modify this that's  why this is private and this is public and we'll have another one of these for the word  info state or i'll just call it state because   that's the main state of this view model infostate  and initialized with an empty word info state   just like this and one more thing we actually  have is a so called event flow the event flow   will be used to send one-time events to the  ui like showing a snack buff for an error for   example so for that we can simply create our own  ui event class i like to make that a sealed class like this and in here we can  have different types of ui events   we will just have a single one to show snack bar   we will have a message here for that snack  bar and that will just be of type ui event then we can create such a such a flow that will  be a share flow to send these events private   valve event flow is immutable shared flow of type  ui events and we have a public version of that   event flow as shared flow just like that then we  need a function on search and we actually type   something into our text field so every single time  we type a character we will trigger this function and it will make a corresponding query to our api  and database to get the results from the cache   however i'd like to have some kind  of delay after we type the character   so we wait like half a second before we  actually make the request because yeah   usually you you need some time to type a word and  we don't need to make a request for every single   part of a word basically so that's why i  will introduce a so-called search job here   which is a curtin job now initially so with that  curtin we will basically make use of that to   cancel that when we actually type another  character so you'll see how that works   i don't want to explain that right now because  it makes more sense when we type that first   so first of all we of course want to say search  query value is query we just want to update the   search data in our ui then we want to say search  shop.cancel so we cancel the current search   when we type the new character and then we say  search job is equal to viewmodelscope.launch   so we launch a new job inside of that search  job in which we now execute that request so we   first want to delay that for 500 milliseconds and  here i'm hard coding this you should rather make   make it a constant for time reasons i'm not doing  that here so 500 seconds we the actual delay until   we start to make the request if within these  500 milliseconds we type another character   this function will be called again and it will  cancel the old running job and wait another 500   milliseconds until there will be 500 milliseconds  in which we did not type a new character   then we will execute the actual search so now in  here we will now call our use case get word info   that's a class but since we overrided the the  operator function we can simply call it like   um like that like a function we want to pass  the query as the word and that now returns a   flow so we can call on each to listen to every  single emission which is now a resource of a   list of word infos so we can easily now check  what kind of result we got here so if that was   successful if that was an  error from our resource here   or if that is loading now the job of the  viewmodel is to just deal with these cases and map   the result to our state accordingly so we  can say state that value stated value.copy   and here in the success case we just want to swap  out the word info items with result.data which is   also type word info items or an empty list if  we don't have that and it's loading is simply   false and that's actually what we want to do in  pretty much all of these cases because also when   we get an error we have results we maybe have  results from our database that we want to show   so just like this is loading is false and  additionally we want to show a snack bar so   we take our event flow and emit that we want to  emit a show snack bar event and we can attach a   message that probably just says result.message and  if that's null we say unknown error so you could   also spend a lot more time with this with this  error handling here i don't want to do that here   however for the last part here i can paste this  again and this is basically yeah here we want   to say true because when we're loading then  we obviously want to set this loading to true   but we also might have some data to actually  display but that's pretty much it for this   on each here right now nothing would happen  because we also would need to launch this flow   which we will do after on each so here we can say  launch in and we pass this so that just refers   to the current view model scope here okay and  that is actually it for our view model now what's   missing is our dagger hill setup dependency  injection and our ui which is really not that   complex so let's actually start with dagger  hilt so here in our root package we'll create   an application class which is needed for dagger  hild called dictionary app which is super simple   we'll inherit from application and we annotate  this with hilt android app then the next step   is to actually register that application class in  our manifest so let's open that go inside of the   application tag and specify name a dictionary app  when we're already here let's also add internet   permission users permission internet and we only  need one more thing for dagger hill and that is a   module in which we provide the dependencies that  we actually want to be able to inject so let's   go to di and create a new carton class of file  that will be an object called word info module   i also will go through this a little quicker  and assume you know dagger hilt from my other   videos because this course is more about clean  architecture so i'll annotate this with a module   and install in we install this in singleton  component so all of these dependencies will be   singletons and now we need to think about which  dependencies we actually need to inject well   we can actually start with the dependencies we  need in our review model and that's just the use   case so let's start to provide that provides and  singleton function provide get word info use case and that returns a get info get word info use case  here so we can say return get word info and oh   here we need the repository so let's simply  provide that here word info repository pass   it for the use case so the next thing  we need to provide is the repository so the next function is provides  singleton provide word info repository   and that returns a word info repository where we  simply return word info repository implementation   this time and oh we need our api and our  dao so let's simply pass these values here   word info dao and our api is our dictionary api so api and dao so these two are the next  things we need to provide provides singleton   provides actually let's provide our database so provide word info database   for that we need our application contacts  so we can just pass our application which   dagger hill will automatically pass here  and that will return a word info database we can say return room   database builder the context will be app the class  will be word info database that class.java and the   name will be word db maybe i don't know choose  whatever you like and we can call that build   let's also change that up here so not injecting  our dow instead our db word info database   and then we can say the beta dao here so that  way we're just a little bit more flexible   if we have the database and if we're able to  inject that instead of saying that dao here and one last thing is retrofit so provides singleton function provides dictionary api that does not take any parameters i think so  dictionary api return retrofit that builder   dot base url we actually don't have that yet so  let's go to dictionary api in our data package and in here i will have a companion object   with our base url and that will  be https api dot dictionary   api.def so then we go back to word info module  specify the base url is dictionary api base url add converter factory and that will  be json converter factory dot create   and we can call create or build first  yeah build first and then dot create   passing our dictionary api class at java one  thing we forgot and that we also need to provide   is actually our type converter because as i  said we manually need to provide that for room   so here in that provide database function  we actually want to add a type converter and   yeah in here we can simply pass json  type converse a json parser actually   with a json instance so typically i would  also have my own provides functions for   these here and pass these as values but  since we don't do anything else with that   i'll just create it in here and now if you  decide to swap that out with moshi all you   really need to do is to pass a moji parser here  or wherever you basically inject your dependencies   so i think this is it for this module so  the really last thing we want to do here   is to implement our ui and that's luckily not too  complex i will put all of that in main activity   actually we want to annotate  this with android entry point   and yeah as we don't have any navigation here or  multiple screens and stuff like that i'll just   put everything in here it's not too much code if  you want to learn how you can build multi-screen   uis then check my other videos this here is not  about ui first of all let's get a reference to our   review model so valve model is equal to hill view  model it's actually of type word info view model then we want to get a reference to our state that  we get from the view model and we will have a   scaffold state so scaffold will be needed to show  snack bars easily and compose remember scaffold   state okay so far so good let's start with a  launch effect block passing true for the key   this will be needed to actually listen  to these events from the event flow we   actually have in our view model so  in here we can save your model oops remodel dot event flow collect latest we can  collect all of these events in that launch   defect block so when that event is actually  show snack bar then we want to show snack bar   it's really that easy so we can say scaffold  state dot snack bar host state show snack bar   and the message is event.message so that's  how we can easily show snack bars in compose   as in form of an event that we get from the  view models so after this launch effect block   our ui will now start so let's start by  wrapping everything into a big scaffold   um actually we don't want to pass a modifier we  we can just pass the scaffold state here and then   in that we will actually put our ui first of  all let's have a box where we pass a modifier of a modifier from compose   that background we fill that background  with material theme colors background we can also put that into its own line  and then in that box we will have a column   or that column we can say modifier fill max size and we want to add some padding of  16dp import tp here pressing out plus enter and   okay what will we put inside of this column  in the end if we take a look here in our app   then the column will contain this text field  here and then it will contain a little bit of   space and the the lazy list for all of these  words here so just text field space lazy list   so we can start text fields and i will keep  this very very simple in terms of styling   let's format that a bit the value will be  view model search query that value and on   value change is your model double colon on search  modifier can be set to modifier fill max width   and we want to make sure to pass a placeholder  that just says and that just displays a text   rather that says search just like this  that's already it for this text field   and we'll have a spacer with a little bit of  height 16dp and below the spacer as i said we   will have our lazy column that is basically  just the list of items we want to display   so here we also make sure to fill the whole size  modifier filmex size and then in here we will have items block for every single item in  our state so state word info items.size   we get the current index here and then we  can get the corresponding word info with that   so val word info is equal to state word info items   at the index of i if this index is greater than  zero so for every single item except for the first   one i'd like to add a little bit of extra space  so height adp this will basically just make sure   that after word is over we have a little bit  of space here then after that if condition we   will have a composable that we will create  a word info item or we can just pass the   word info for that or let's let's call it  word info as well here we need to create that   and after that i can check again and make sure if  i is actually less than state word info items that   size minus one so all that really checks for is  if we if we're not at the last item so basically   for every single item except for the last one  we just want to add a divider so that that way   we have these little lines between items but  basically not here for the very last one so let's   quickly create this word info item and then we're  already done i mean already took quite a while   here but i am sure you've learned a lot of stuff  let's go to presentation a new word info item um will be a file and we can create  a composable here called word info   item and that will take the word info  and form of a word info item actually   just in form of word info so now we're  basically creating the composable for   such a word so word phonetic description type of  word example stuff like that just a bunch of texts   so we will start with a column we will pass a  modifier here which i also want to pass here compose modifier is equal to the default one and inside of that column we first want  to have a text for the actual word so   the text is word info that word we can set the  font size to something like 24 sp import sp   we can set the font weight to  bold and we can set the color to   color that black that's it for the title  the next would be for the phonetic so   now let's set the text to word info that phonetic  and we can set the font weight to light for this   so that way we get this yeah  it's just a little bit lighter   and looks good in my opinion we can add a little  bit of space here of 16 dp height also import dp   we then want to have another text for the  actual origin so we're in for that origin   we don't need to change anything else here for  this origin and then all we really want to do for   the rest is we want to loop over the meanings  so word info meanings for each it's actually   a lot less work than it looks like in the ui  here we get the meaning let's first start with   the meaning dot part of speech and also  make that in bold so font weight is bold   then we want to display all  definitions of that meaning so meaning   that definitions for each index actually  because here we also need that index to display some composables correspondingly  based on that so now we will basically just   display here basically everything that comes after  noun here we want to iterate through this list and   yeah basically numerize this so one two three four  that's why we have that index and just display the   corresponding definition together with the example  so we will have a text that says index plus one   let's actually call it i i plus 1 a dot and then  we simply use oops definition that definition   so that's it for the definition already we  add a little bit of spacer let's say height adp actually modifier that height then we have some extra text that is for  the example so definition dot example   and that's nullable so let's actually make  a null check here definition example.let's   we get the example here and  then we can put this in there   so yeah just let's replace that with  example and let's actually also say example is example just like this then after that let's  have a little bit of spacer again atp height ah come on all right this one it's not  including the modifier modifier height hdp   we can copy this line and just have that one more  time after this for each block this time with 160p   and that's it for the word info item so going back  to main activity we simply need to import this   and i think i think this should be it i   we will see maybe i missed something which  is very likely in such a rather long video but i think we are ready to actually  click run and wait for this to launch   okay so the very likely thing happened we  actually have an error here in our project   which is i think easy to fix  and require tab converter   for blah blah was expected so our room database  basically doesn't recognize our type converter   let's take a look what that is in our info module  and yep that is a silly mistake i actually did um   we don't pass our json powers here we of course  want to pass our tab converter so converters   of type i'm actually not of type just the  converters with our jsonparser json instance   so with that change let's actually try to run  this again hopefully we don't get another error installing and this time it does not crash which  is good um one thing i actually now realize i   forgot is the progress bar but i don't think  it matters here just have an if condition if   state is loading show progress bar it's really  easy let's try if we can actually get a word   from our api let's search for windows for  example and we couldn't reach server yeah   which is expected because i'm still in  airplane mode let's disable that then and once we connect here probably already let's  update this and yeah we do get the word window   from our api let's also search for other words  bank as i said to check if we get multiples   yep we get multiple bank words we can  search for basically anything android   ios i don't know if we find  that oh it even finds ios   uh yeah so that's pretty cool and let's test the  caching if we close that app go to airplane mode and we open the dictionary again and we now enter ios then we actually load that  from our cache so that is working perfectly   fine we can do that with android basically  with everything we've requested in the past   and bang obviously so these words and that's just  working perfectly fine the snack bus are appearing   and yeah just for the for the progress but let's  quickly do that main activity here in our box   we want to make sure that if the state is  loading we want to show a circular progress   indicator it's that easy we can relaunch this and  then we will see that we can actually also see   the progress bar in our app so here if we now search for car maybe uh yeah it's  not aligned properly we would need to center it   but the process where it appears to  align it we can say modifier is modifier   line and say center i'm not going to relaunch  that now because i think you you get how that   works so yeah congrats for actually finishing  this rather long video here in case you enjoyed   watching this video and you liked what you learned  and you actually learned a lot then you will also   learn even more by subscribing to my free email  newsletter in this video's description so simply   enter your email it's totally free and you'll  get regular advice on android kotlin architecture   on a weekly basis for free it's basically like a  blog also i would be very happy about a like and   a comment down below just telling me how you like  this video if there's something i can improve on   because these types of videos are a lot of work  and yeah it would be cool if that somehow pushes   the youtube algorithm a little bit so even more  people can benefit from these videos and learn   how they can actually properly do android  development thanks a lot for watching   and i'll see you back in the next  video have an excellent day bye bye
Info
Channel: Philipp Lackner
Views: 10,673
Rating: undefined out of 5
Keywords: android, tutorial, philip, philipp, filipp, filip, fillip, fillipp, phillipp, phillip, lackener, leckener, leckner, lackner, kotlin, mobile
Id: Mr8YKDh3li4
Channel Id: undefined
Length: 77min 11sec (4631 seconds)
Published: Tue Nov 16 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.