How to Build an MVI Clean Code Weather App in Android Studio (Jetpack Compose)

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hey guys and welcome back to a new video and this will be an amazing video because we will build a weather app a clean code weather app to be specific because it's so hot right now here in germany i'm pretty much melting and it would be cool to know how hot it actually is so we're going to build a weather app let's see how that looks like uh yeah pretty much like this so that's the app you can see how warm it is here in your area i'm sure it's even hotter for some of you and this will automatically fetch your location so this will just be a yeah weather app for your location you could also easily extend this with a search for example for some locations you see the current time you see the current temperature like what kind of weather is with a cool icon you will see like a description of the weather some pressure wind speeds um humidity and you get a little forecast for the remaining hours or for all hours of your specific day but that is really something you could easily extend you can also also extend this for example with a forecast for the whole week this whole app will be based on an api that we will use that does not require any authentication so you don't need to register or so which is super cool especially for a video like this then we will use mostly clean architecture so we will have like presentation domain data packages we will have mappers we will have you compose of course for the ui and the only thing that differs from clean architecture is that i won't use use cases here in this tutorial format because i usually think they are way too overkill especially for an app like this usually i think here for this app we would actually just call repository functions in our use cases so i just want to leave away that layer and just yeah keep the other layers and still apply all the clean code principles especially when it comes to like um how you can fetch the location uh when it come with clean architecture and following all these abstractions and stuff like that that is what i want to show you with this video so definitely watch this till the end and now it's actually time to show you how we will actually get the data um here i am in google chrome and that is this the api will use open video it's called you can check just check this on open dash and here we can see some documentation which is really easy to use this is the only endpoint we will actually use if we copy this paste it here in new tab and press enter then you can see this is how the data actually looks like here in this case we would get like latitude longitude that is also what we need to provide so our current location and then we have this hourly block of code here or block of json which gives us different lists for different types of data so first of all each hour of the day and i think it even does that for multiple days and then you can see we get the temperature two meters so that means that is the measured temperature two meters above the ground level and that would be like for the first hour of the day second hour third hour and so on so we get a bunch of data here and we actually get more data than this if we extend this request with some more parameters which you can actually find here um in this documentation if we take a look to the hourly parameter definition those are all the parameters we could actually extend the query with to actually get more data for example we also want relative humidity two meters above the ground level so we could simply attach that separated by a comma we press enter and then we also get all these values so far for the api we will dive more to this in the video of course next up here drobo design i just got some inspiration from that it's it doesn't look exactly like this like i don't have this cloud i don't have the graph and stuff like that but i um the app looks very similar to this so i'll also put a link to this design in the switch description and yeah just to also give you some inspiration how you could extend this and something i also want you is this geocoding so that means that you have latitude and longitude and you actually find out the name of that location like it would also display the city you currently live in um you won't do that because i think you actually need an api key here for the google geocoding api and i think for that you need a billing account on the google cloud console so that would be a little bit too much setup for a tutorial so i want to keep this as simple as possible and as easy to follow as possible and then the last thing we actually need before actually diving into coding is that you need the initial source code here from my github repository and this time it's actually important that you get this really from github and not just copy over dependencies because there's some more initial setup that i have here nothing about how we structure our code and stuff like that but um let me just show you so you go to the branch here select initial and you can simply go to code either download it as a zip file or directly clone it here in android studio um let me show you that in android studio what is actually what belongs to this initial project of course the dependencies and that is nothing new for you if you watch my project-based videos we use dagger hill for dependency injection we use location services to track our own location and we use retrofit to interact with the api and of course view model compose to use view models but other than that we also yeah i also just added some colors here nothing special but this is an important file here this weather type which is a sealed class and i really don't want to show this in the video because the way we will actually decide about the description and the icon for specific type of weather our so-called weather codes if we scroll completely down the api will respond with just integer codes and each integer code corresponds to a specific type of weather so you can see 53 would be moderate drizzle 55 would be dense drizzle light freezing results so tons of weather types here you could have in your app and i don't want to write this in the video so you can just copy this file it will contain all the different weather types here as a sealed class so very convenient and we can then easily use this in the app to parse the weather type the api actually returns for a specific hour and something i also wanted to include is here in domain util we have a resource class which i use in all of my products just to easily distinguish between success and error cases you will see in this video how this works looks complicated it's super simple um but yeah you will see how this works if you download this class and if you watch at least one of my project videos then you will know this class and of course last but not least um what's important is that you have all the graphics like all the vector graphics for the different weather types which i included here in the drawable folder so you also want to have that like this cloud here like this rainy thunder for example which all look quite cool i think so yeah make sure you have these feel free to use these in your app and apart from that i would say we actually start right into it and i will also structure this in a way or like explain as if you would have no idea of um clean architecture clean code why we do things so i will explain a lot i want to assume that you know a lot about clean architecture about how you structure your projects so you yeah so that everybody can actually follow the first thing i usually do in such projects is that i set up the api because without api we don't get any data and without any data we don't have a working app so where does that api belong well in clean architecture we have three main layers of our app we have presentation which represents the ui of our app so viewmodels composables if using compose if you use xml that would be an activity a fragment or so then we have the domain layer which is the the innermost layer you can say and that has a purpose to really isolate the core behavior of your app so it contains the business logic the business rules and really should not know anything about other layers so it's about presentation or data so that means in domain you don't know if you use a room database if you use a retrofit api if you use cate or as a network client you really don't care about that in domain because it should be as isolated as possible and then we have the data layer which well interacts with data sources like preferences like the local db like an api as we have here and that is of course where we now also want to put our api our retrofit interface so in our package i will create that data package and in that we will have a remote package because our retrofit interface represents our remote data source and maybe as a little side fact here if you would have an app with multiple features a map screen for example where you can scroll through different cities and see the weather of these cities then i would not advise you to have this kind of package structure to just have data domain and presentation instead i would have feature packages and then in each feature package i would have this package structure so you just divide every feature by layer that is a lot more scalable but for this um app here it's totally fine to do it like this because we just have a single feature and then our rule package kind of serves as this feature package however let's dive into remote and we create a class called weather api select interface and let me attach to get and in here all we will really have is one single function and that will be the function that calls the api endpoint i just showed you in chrome we will be using kotlin coroutines here so that will be a suspend function and we call it get weather get weather for example or get weather data for example could also be a thing and that now takes two query parameters because what need what does the api actually need to know from us well that is of course our location because otherwise it doesn't know what kind of weather data it should return and we specify that with queer parameters like this the first one is the latitude which i will abbreviate with lat and we will of course also have the longitude like this now we need to specify what kind of request that will be so get post put or whatever that will be a get request we're just getting data here and we need to specify the endpoint which is a very long one here so i recommend to copy what i now paste from my github repository from the weather api interface so that's just yeah the route we actually access we make sure we hit the forecast endpoint and we specify some hard-coded parameters here that are always the same for every request like we want hourly weather we want um the temperature in our data set we want the weather code which just gives us information about what kind of weather that is so if it's cloudy sunny and stuff like that we have the humidity wind speed and the pressure level so with that we just make sure that we get all the data we want and not more and now that function of course needs to return something something that we get from the api as a response and that will be our weather dto so in clean architecture we often deal with these so-called dtos data transfer objects and these are just kotlin data classes that re represent the json response we get from the api nothing more i'll put this in remote as well create a weather dto here select data class and what will this contain here we really only put the information we need in the app and that is just the hourly field so if we take a look in the api response it's really only this field that we are interested in in the response so that is the response here and this is this is one field of the response if we scroll down we also get things like how long it took the api to generate this how hourly unit stuff like that we only want this hourly block so we put this in the app in here i'll call this weather data though because if we would call it hourly it wouldn't really represent what this represents so i will make this a weather data dto which we don't have yet and to make sure that marshy can actually purchase this so we will use maashi for json parsing here this actually normally would need to have the same name as the json field from our api which is called hourly but since that's not the case we need to annotate this with that field json from marshy here and specify the actual name which is hourly and then we now need to create our weather data dto which is basically all the stuff we have in this hourly block like we have more in our app because we request more parameters maybe we should quickly just show you how that would look like with our actual endpoint let's see let's take that and paste it here oops instead of this and [Music] that of course also now leads the latitude i'll just pick 53 here i think that should be fine um [Music] and 13 for the longitude and yeah there we go so that is the actual response we will get in the app if we collapse all these then you can see we only care about this hourly block here because that contains the weather data our humidity our time pressure weather code wind speed and temperature so that is what our weather data dto actually needs to contain so back in android studio i will just paste this dto here you can either get this from my github or just quickly write it off it's not a lot but um yeah pretty much we just have the time we yeah that is also called like that on the api with our temperature actually should be plural here because we get multiple ones temperatures and the js name for that is temperature two meters we have our weather codes we have our pressure levels wind speeds and humidities so then in weather api we want to make sure that we now return such a weather dto which contains our radar data dto so that should now look fine yeah now it knows that and we successfully created our api now what is next the next thing i want to do is i want to create our so-called domain models that is a core concept of clean architecture that we have models in our domain layer and if you remember what i mentioned at the beginning of this video the domain layer is our innermost layer and it should not know anything about implementation details so it should not know if we use retrofit if we use cater client or something similar and if we would now take our dto objects which contain our weather data and use them in our domain layer for example if we need to kind of do something with these with this data the api returned then that would be wrong because these dto objects contain implementation details they contain the detail that we actually use marshy since we use these annotations here so what we typically do with clean architecture is that we have so-called mappers on the data level and these mappers have the responsibility to actually take our dto objects and map them to objects that lie on the domain layer which kind of don't have these implementation details like these annotations here you could also imagine this with room entities there you would have this entity annotation so your domain layout shouldn't really know about this because the reason for this is if at some point you change the implementation details which in software development can very often happen so that you decide to use catocline instead of retrofit to use another kind of database instead of room then that would not only change your data layer but it would also change your domain layer and your domain layer should be as isolated as possible so that if something changes in your application the core foundation and the core logic will stay the same and is not affected by these changes so if something changes it should always only change one single layer of your app and also we typically have domain models that are just objects or classes that are much easier to deal with in code than with our dtos like for example here the dto contains the weather code in of type of integers and we don't always want to have this list of integers to actually check what kind of weather it is we want to have a more convenient way and that is why i actually implemented this weather type class so we can parse the type of the weather once when we map our dto object to the domain object and then we can very easily just check okay is the current weather maybe clear sky is it mainly clear so we have very readable name then for a specific weather type in our domain model that makes it much easier to actually deal with that without further ado let's actually do that and then you will understand what i mean if you're new to this in the weather package i will create another class called weather info so just a class that contains information about a weather and here this class should now contain the weather data per day basically so we want to have an easy way to say okay i want to access the weather data for um like for tuesday for the next tuesday and then we should get a list out of that that gives us all the different hours for tuesday containing the weather information and for that a map is pretty good i think so let me show you what i mean we have the weather data per day and that is a map of type integer to a list of weather data that is what we need to create so this map will basically map the current day index to the weather data list of that day so weather data would represent the temperature for that day or not for that day rather just the temperature for a specific hour of that day therefore we have a list to represent the all the temperatures for the day it would also represent the humidity the pressure and stuff like that basically what we get from our api so if we would kind of get um or access this map at index 0 or at key 0 rather then we would get weather data about today if we would access this map with the key one we would get weather data about tomorrow and so on so we'll basically have seven entries here in this weather data because that's how many days the api returns and we can very easily use this to then display the forecast for the next week and then we also want to have the current weather data which is also weather data nullable so this will just represent the weather data for today not only for today just for the current hour we're actually in so if we if we hit the api at 1 pm for example then this will be the weather data that contains the temperature for 1 pm the humidity for 1 pm so that is of course contained already in this map but it's a lot more convenient if we actually calculate this once here on our mapper level and then always have this current weather data to deal with so the next step is to create this weather data domain model object in our weather package as well by the way maybe i should mention that as well why i put that in the weather package in some videos i created a separate models package in domain where i put in all of my models and that's fine for most apps but i feel like if you separate your packages by context so by what's inside of that and not by type like by models um that's a little bit more scalable i feel like because if you just see the package type you know what's in there if you do it by context like we do it here with weather and you can give your project some kind of structure so if you would also have some data related weather dependencies in the data package you would also have a weather package there so it's kind of a consistent structure and i like it but there is no general right or wrong i would say let's just go in here and have weather data data class this will contain some fields about a specific weather data so specific data for a given hour on the one hand we have the time and i want this to be represented by a local date time object so there's one um yeah another reason why we have domain models to just have more convenient objects to deal with that because with a local daytime object we get information about the specific hour about the specific seconds without us needing to actually parse a string or so every time we need it like a time string and then we have the temperature celsius in form of a double we do have the pressure level in form of double we do have the wind speed double the double we have the humidity humidity also double and we have the weather type of type weather type and such an object is now of course a lot more easy to deal with in our code than if we have this object or actually this one here which contains lists of doubles a list of integers we would need to parse a lot so we just take this parsing and do it once in our mapper and then we can always very easily deal with the actual weather data object which contains our weather type c class our local data and just yet the specific values for a given hour so the next step is to actually create these mappers that will take our dto objects and map them to our domain level objects and we will put these in data because these will of course interact with our dto objects and therefore we need to be allowed to access these so they can't be under the main level so i'll create a package here in data called mappers and in that we will just have a file called weather mappers and in that file we will have extension functions to just map one type of object to anothers or dto to our domain level objects the first function i want to have here is actually a function that maps a weather data dto object to a weather data map so this map i'm talking about here is actually where is it this map here exactly um so we basically map the yeah the the number of the date to the list of weather data so for example if this key would be zero then that would refer to today and this list of weather data would then have 24 entries so one for each hour and each entry contains information about temperature pressure humidity all that stuff for that specific hour cool so we're going to return this map here a list of weather data and that of course now returns our domain level object that's the purpose of this mapper and this will actually be a little bit more complex this mapper here to kind of transform this weather data dto to this map but you'll see how this will work so we will actually start to actually map the time values we get from the weather data dto which is just a list of time strings so if we take a look here that would be such a string so we now loop over this list and map this so map index we actually need the index and we map this to a weather data object that's not so difficult yet since we can simply get all the different values using the index so we can say temperature would be what's called temperature is temperatures at the index of index we would have the weather code we have the wind speed whoops wind speed no didn't i include wind speed here um i did so why oh it's the variable name forget what i just said um the wind speed is of course equal to wind speeds at index um and then we will have the pressure and finally the humidity so now we have just references to the single values since all these lists have the same length we can simply use the same index for these and then we map this to weather data object the time will actually now be parsed with a local date time with that class we can say local daytime but parse and we simply pass the text so this the time string one of powers which is just time and a formatter that we need to specify to tell it in which format our time is which is just the standard iso format so we can say date time format iso date time and that will that'll then give us a lot more information about that time we're gonna have our temperature celsius we have our pressure wind speed humidity and finally the weather type which we actually need to retrieve from this weather type class here i have this from wmo function wmo is just the abbreviation for these types of weather codes that's how these are called and yeah that's pretty much also mapper class so we can just take this and call weathertype.fromwmo and we pass our weather code so now we essentially mapped our time values to weather data objects so we now have a list of weather data here we don't have that in form of a map yet that actually maps the um the date to the weather data lists how can we do that and that's actually where the tricky part starts because what we could of course do is we could say group by which actually returns such a map of any type and maps it to values of type list of weather data and we could say it dot time dot day of month for example then the arrow go away and then we would have a we would have a map where it maps the day of the month to this list of weather data so uh today is june 26th and that would mean that in this map if we access the key 26 then it would give us the list of weather data so all the different temperatures for today for every single hour however that makes it a little bit difficult later on to actually deal with that because we always need to find out what the current day is to kind of say okay i want i want the weather data for this day i want the weather data for in five days then you would need to say current day plus five to actually get that corresponding weather data and it would be much easier to actually have a map that just has seven keys starting from zero to a six and zero would just correspond to today one would correspond to tomorrow um two would correspond to the day after tomorrow and so on so that would be a lot easier to deal with because we don't need any information about the time later on however if we want it in that format it makes it more difficult to actually parse it to that since as soon as we actually have a map we can't easily map the um the keys of that map to its indie space because a map doesn't really have indices like a list does so we we can't simply say that map keys we simply map all keys and then say just two like it dot index or so that doesn't work and we also there is no map keys index function because the map does not have any says so how can we do that instead we can actually calculate the information we want using the index we have here so that index will range from 0 to a very large number which is pretty much 7 times 24 because we have 24 hours a day and we have seven days in that big list the api returns here so we have 24 um strings here per day you can see over that 26th and for each hour we have a string and then it goes on to the next day and so on so there are seven times 24 entries in there and with that information we can actually calculate what we need and what i will do is i will create a little data class here that assists us which i'll call indexed weather data which will kind of serve as a temporary class here that just that's just used to forward this index parameter so that will be the index and we will have the actual data that we also of course need to afford i think we should be able to make this a private one yeah because we only need this in this file here so then we will go here and not only return this weather data instead we want to cut this map this to an index to other data where the index is just the current index here and the data is what we just copied or rather cut then we want to go to group by and we group this by it dot index divided by 24 because we have 24 hours a day and we always round down so if we are at day number one in our list let's say we would we would have this entry here which is corresponds to the 26th and the third hour of that day and it would be the four actually the the third index in that list so zero one two three then we were taking this index which is three and divide it by 24 which is zero so it corresponds to the zero the day in this week you can say let's say we would be at day number two here the 27th actually starts this would be at index 24 so we would take this index 24 divided by 24 which is one so this now corresponds to day one and that is how this basically works so now we have a map of integers to index rather data but we actually want a map with integer keys to list of weather data so we don't care about this indexed weather data so what we still need to do is we need to map this again and this time we need to map the values which are indexed with the data we want to map these to normal weather data so my values and we map this to well it.value which represents to this list of indexed weather data and since we want to map the like the what is the items of that list here the indexed weather datas we need to say map again so i know that's quite confusing we just say it data which refers to where is it here it just refers to the weather data this indexed weather data holds so i know this was a kind of confusing function here with this end i would suggest you to just play around with that a bit if that is confusing for you so you could just say something like also and just print this or just print the keys here for example print the values so you see what this group by function actually does however the next mapper function we will have here is a bit easier we can just say weather dto so that will map a weather dto to a weather info object return a weather info object and what does such a weather info object contain well it contains our weather data per day which is exactly the map we are now able to calculate or now able to generate with our previous function and the current weather data so we kind of need to process the corresponding weather data for the current hour of the day first of all i want to say whether data map is equal to weather data that to weather data map and then to calculate this uh current weather data object we want to have a reference to the current time which is localdatetime.now and then we can say well current weather data is equal to weather data map which contains that at the index of zero so that corresponds to the current day and now we need to find the current hour in that current day so that if we access this at a specific key that would return a list of weather data now so we could say dot find so we in that list which contains all the different data objects for each hour we want to find the right hour so the hour that is the closest to our current hour which is contained in this now object so we can say val hour is equal to if now that minute is actually less than 30 in that case we want to kind of round down then we say now that hour and else we say now that hour plus one so if we are let's say at um 1 pm 20 minutes then the minute would be less than 30 so we would get the weather data for 1 pm if you would be at 1 pm 50 for example then the minute would be larger than 30 so we would actually take 2 pm as the time since that's closer to our current time and then we just want to find the object where weather data will let where it that time that hour is actually equal to that hour and then all we need to do is we need to return that weather info object whether data per day is weather data map and the current weather data is current weather data and again this map won't actually be that much used in this tutorial since we just get the weather data for the current day so we could also make this a lot more simple but since we get this data already from the api and i want to give you all the little homework to extend this with a forecast for the whole week i wanted to at least have a decent data structure to easily extend this app here with more days so what is the next thing we want to do here the next thing is to create the repository the repository's job is to just kind of combine the different data sources we have we only have one here which is the api but if you would for example have a local database cache then the repository's job would be to kind of get data from the api put it the local cache return data from the local cache and things like that i just like to have it as a design pattern for data related stuff the repository should always be abstracted out with an interface which we put in domain since then the viewmodel which we'd like to have can actually access that repository from the domain layer since it shouldn't directly interact with data let's do that here in a repository package and we create a weather repository interface and the reason why you should always abstract out your classes so when i say abstract out then i mean to create an interface or an abstract class for your classes the reason is just that it's a lot easier to later on swap out implementation details as i mentioned so if we later decide to use a different um different type of api library for example like swap out retrofit with cate or client and all we need really need to do is to change the implementation of the weather repository but not this interface and also not all classes that depend on this interface and that also makes things like testing a lot easier actually but that would be a little bit too much here for this course so here we will simply have a suspend function get weather data that will take the latitude and the longitude and then return resource of type weather info and here now this resource class comes into play which we will use to easily just forward the status or the result from the api to our next layer which is in this case the viewmodel so if the api returns a successful response we just simply return this value with the data the the api returned and if there was an error like we don't have internet connection we return this instead with a given error message that we can also then extract in the view model it's a pretty cool class so then we go to data create a repository package that's now where the implementation of this repository goes and we have a weather repository implementation which will implement this interface with a repository we want to have our get weather data function and we need access to our api it's a private val api is our web api and i want to inject the constructor here using dagger hill so i say inject constructor and then dagger field will automatically be able to create this this type of instance for our repository if it knows how to get this api which we'll do later and i'll also explain a little bit about daggerfield and this function is super simple all you want to do is run a return try catch and so if something goes wrong we will catch an exception i won't do any fancy error handling here dependent on different exceptions i will simply print the stack trace and then return and restart that error with e dot message and if that's now we're going to say an unknown error occurred and then in here we want to return a resource that success where the data is actually api get weather data that is led long as long and then we take this result and we can now map it so we can say to weather info and there we go and if something goes wrong here with this api call we will jump into the catch block return an error message and if not we will just return this resource success with the given data we got mapped to our weather info object so what is next next up we want to worry about location tracking because that is also data related it's also kind of a data source to get a location from your gps or so so what we will do is we will again have the abstraction which will be a location tracker interface let's create that here in domain in a location package and that's also what i mean with these context-based packages because if your package is called location and then you also have a location package in data then you know these are closely related and it's likely that in the domain location package you will have a bunch of abstractions and on the data location package you will have implementations you will see how this will work in here we will simply have a location tracker interface and well what do we want from a location tracker like from any kind of location tracker no matter how that's implemented well we want to have a function that just gives us the current location and that will be a suspend function since uh that can take a moment to actually retrieve the location and i want to use curoutunes here and that will be called get current location and return a location here another one actually since it could potentially not exist let's say we don't have location permissions oh we don't um we haven't uh the gps one enabled the setting for that and if you would want to super strictly stick to clean architecture here you would also create your own location data class that just represents latitude and longitude and return that here since that location class as you can see is part of the android framework we kind of yeah the domain layer shouldn't know too much about the android framework but here in this case since we're just building a pure android app where we don't want to reuse every anything from domain or so it's fine but let's say you would want to use a km project called a multi-platform then you want to use the reuser's location tracker interface between android and ios you couldn't do this because you would couple this to the android framework but for the sake of simplicity i'll leave it here just to prevent that there will be some angry comments let's go to data also create the location package here and in that we will now have our default location tracker which we will use in our app select class and make that implement our location tracker interface what kind of dependencies does that now need well on the one hand it needs the location client which actually also comes from the android framework here it's not fully okay to use this because it's an implementation detail here like a specific implementation of this interface and we can retrieve the location with a so-called fused location provider client a super simple word let's um provide that here private val location client is a fused location provider client that comes from a dependency we included and we also need the contacts actually to be able to check for permissions so we can simply inject our application instance here i also want to inject this these dependencies here in the constructor so i'll use this again and then we can actually override get current location cool so two things here on the one hand how do we get location and on the other hand how the heck do we do that with coroutines because the fused location provider client doesn't offer us a function that actually makes a curitin wait until we got the location let's first of all do the simple stuff and that is checking for permissions so when it comes to location there are two permissions we usually need on the one-hand course location and uh because it's fine location so that's just for the um for rough location that is for more detailed location and internet is actually also sometimes needed since it sometimes uses the network provider to get the location to make it faster so we want to check has access uh find location permission is equal to context compat dot check self permission here we pass our application for the context and the permission you want to check for is a manifest from android permission access find location and if that's equal to package manager permission granted we know we have that and now we do the same for access course location here we say access course location and that's it we also want to check if gps is enabled otherwise this also won't work so is gps enabled well let's actually not start with that let's first of all get a reference to the location manager which comes from the android framework so it's a system service we can say application um get system service context location service and we cast that as a location manager like this and then we can say well is gps enabled is location manager that is provider enabled we're going to check for the location manager network provider so that would be the provider that retrieves the location using the network or location manager is provider-enabled gps provider so if either of these is enabled we know we can retrieve the location so we can check if we don't have the course location permission or we don't have the find location permission or we don't have gps then we want to just return not because then we don't have a location and if you would want to handle these errors more specifically like to also tell the user what is wrong here you could wrap this location into such a resource object we have here and then also pass a specific error message i'll leave that out here and now if we have permission we will actually reach this point here and then we still need to retrieve the location of course in a suspending way how do we do that and the answer is we will use a so-called suspending curtin so we can say we return a suspend cancelable curotene which will give us a so-called continuation so we can kind of use this to convert a callback to a suspending current team let me first of all write this and then i will explain how this works so we can retrieve the location using our client get the last location and we can say apply you can see this returns a task so this does not give us the location yet since that's an asynchronous thing that takes a moment instead it gives us a task which we can then use to either listen for successful results for an error or maybe if it got cancelled so first of all we want to check if the task is already complete so maybe there is already a last location that is known then we don't even need to wait for that if that's the case we can use our continuation let's call it like that to be able to continue with something so we can just continue with the curing in which we execute this suspending function here and we can say we resume and we need to resume with a specific result which is our location so we just return with the result which we get from our task and else oh actually we need to do one more thing unless we also want to check in this case if the task was actually successful because being complete doesn't yet mean that it is successful it could also be it could also be a failed task so if it fails we want to resume with a null location and we still get an error here i don't know why um okay it's experimental let's add this annotation to our default location tracker and why is it feels like i'm using the wrong resume function here resume i just want this with a single location and here we pass the result yeah then it works and then we can actually get rid of this else block and just make sure we turn here in case we already resume because if we if you resume twice that it will actually crash but if it was not complete we actually want to wait until it is completed and that's where the async stuff actually starts so we can say we add an on success listener here to this task which gives us the location here in this callback and then we can say cont resume with it here once that was successful but in case there's an error we can say on error or on failure listener and we can say quant resume with a null in this case so then we don't have a location or if this actually was cancelled we want to say can't.cancel so we also make our current cancelable here in this case and that's pretty much how we can transform a callback like these callbacks to a co-routine so it's now very easy for us to actually just call this function and block coroutine until we get the location and then resume that routine i love to do that and that's already it now for our data layer i would say the next step would be to actually set up dagger hilt which will be used for dependency injection i won't dive too deep into the penalty injection i just released a very very detailed course about dagger healed which i will link somewhere up here so if you have no idea about that then i would highly advise to watch that course um i will just yeah it will explain a little bit but not every single detail so i kind of assume that you have worked with that before or watch that course so i want to start to actually create the dagger modules in which we just provide our dependencies to be able to be injected and i'll do that in a di package which i will set up in the root package so dependency injection and create an app module object in there so in here we just tell daggerfield this is how we can create our retrofit interface this is how we can create autolocation tracker and things like that needs to be annotated with module and install in so we want all these dependencies to be singletons so we say singleton component and then we can start with our retrofit interface annotate it with provides and singleton and we say provide weather api that will return a weather api instance and in here we now need to kind of create that and therefore tell daggerfield hey that's how we can create this instance so you want to say return retrofit.builder we want to specify a base url of course so the url of our api which is https um api open video dot com and then we want to add a converter factory to automatically parse json to our kotlin data classes so to the dto objects here i will use the marshy converter factory.create and then we can say that build.create like this and there we go we just created our api interface very easy with retrofit what is next we actually also need to provide the fused location provider client which we inject here so we go here oops provides and oops singleton provide fused location provider client that will need the application instance to be created so we can simply use that here and return a fuse location provider client return location services that is that get fused location provider client with our application instance very easy and then what we also need to provide is on the one hand our location tracker and our rather repository and i like to do that in separate modules since both of these are abstractions interfaces and with dagger hilt we can use a different kind of syntax to actually provide abstractions which just generates a little bit of a little bit less code so in di i want to have a location module will be an abstract class module installed in singleton component and the difference here in terms of syntax is that we use binds instead of provides still a singleton and we now make this an abstract function so we don't need to kind of write code to provide this instance and we say bind location tracker and we specifically want to buy we want to bind a specific instance which is a default location tracker but we want to actually provide that as the interface and here we can actually also add this annotation to location module just like that so whenever we inject a location tracker it will actually use an instance of default location tracker and that only works if we actually have an inject constructor here for that and that only works if we actually if dagger knows how to create these instances that one we actually provided with the provides function so that works and it's uh able by default to actually inject the application instance so we're gonna have the same type of module for our repository so i'll just copy paste the location one call it repository module and here we want to bind our weather repository so here we now specify the specific implementation we want to bind which is just the only one we have and here we specify the abstraction like this cool so i think that's it for the modules the next step with dagger hill is that we need to set up our application class which is super easy in our root package we create our weather app make that inherit from application and all we need to do is we need to annotate this with hilt android app so just that it knows where to get this application context from cool then we go to manifest actually add this application class here as a name weather app and that's it for hilt we are now able to inject dependencies what is done next the next step would be the view model what's now the purpose of the view model in clean architecture well the purpose is that it kind of interacts with the domain layer so in our case for example with our location tracker with our repository and it actually caused the functions of these instances and text result so what we get from the api for example or what kind of location we get and it maps that to state so it maps that to something that we want to then show in the ui so the ui in terms of composables or fragments or so should usually be as damp as possible so it should not do any fancy like state parsing or so at least in my opinion um that is kind of ui logic and you can do that in the ui level as well like on the in composables or so but that's not what i recommend but in my opinion we have your models in clean architecture to do that job for us so the ui really just needs to assign values from state to ui components which is then super dumb so it doesn't really know where these values come from so in presentation that is where the viewmodel belongs we will actually create first of all the state that will represent our screen state so we will have a weather stay that's how i will call it select data class and we will have the current weather info here of course which is nullable but if not by default which we still need to retrieve we will actually have an is loading boolean so we can show progress bar and we'll have an error string which is also not by default and then we can have a view model in presentation whether we model create that make it a hill view model so we can easily inject the parentheses in this view model and we say add inject constructor what do we need in this view model we need our repository in form of a weather repository and we need our location tracker like this make this inherit from viewmodel not that one viewmodel cool and the first thing i want to do is want to create an instance for our state so we will have a var state by mutable state off of type weather state import this twice pressing up plus enter and make this a private set so only the viewmodel can change the state and then what we'll have is we'll have a function load weather info and this function will basically call our api and it will get the get the location and combine these to a state that's the purpose of the viewmodel here in clean architecture just to call the interfaces from the domain layer or just some classes from the domain layer doesn't need to be interfaces and take the results and map them to our state that needs to happen in a curtain view model scope.launch first of all we make sure that we set the state to stated copy and we want to start to show the loading value and we want to reset the error so we need to use copy here to be able to change the state to a new value so we just copy it and change these values here so it's actually updated in the compose ui and then we can start to retrieve the location so location tracker get current location and if that location is not null so if we actually got a location we're going to use that here to actually make the request to our api using the location so we can say when the result is equal to repository getweatherdata location.latitude location.longitude and when that is a resource success now we can very easily check the results here using our resource class or resource.error if it's successful we're going to map our state again to state that copy in this case whether info will be the result we got from the api so result.data you can see that's a weather info object is loading will be false in this case and the error will also be null if however we got an error we want to say state is stated copy the weather info will be now is loading will be false and the error will be the error we got from the repository so result that message in this case cool and that's already everything we need to do to actually handle the success case like when we get a location if we don't get a location we want to go down here to this um closing curly bracket and say run so if that is null we're going to execute this run block here we're going to say state is stated copy is loading is false and the error message is just something we specify here couldn't retrieve location make sure to grant permission and enable gps something like that so that we just know what the error was and that's already it for the view model that's all we really need to do here for this simple app then the next and the last step or the next and the last part for this tutorial is the ui so now we're going to build our composables that we show on the screen that will happen in presentation as well we will actually check our app how it looks like and first of all think how we will arrange this it's a very simple ui in the end and there won't be any special things to consider here in the end we will have a composable for this weather card here that's how i will call it so this um deep blue thing then we will have a simple text here that we will align at the end that just displays that it's today and yeah the time we will have we will put that in the column as the first element we will then put this image as a second element which we'll get from the state we'll use the temperature we'll use the description of the weather type and here we have another row where we just show this little information about pressure humidity and wind speed and then we will have another composable which i will call the weather forecast so for the given day and there yeah that's also just a column with this today text and then we have a lazy row in which we will have these single um yeah our the composables i forgot how i called it we will see but yeah just a composable for each of these hours so let's go back and start with our weather cart so weather cart make that a file composable call it weather cart and let's think about what we need to know to create this weather cart on the one hand we need to know our state to be able to show the weather information we need the background color so we can keep it flexible from compose ui graphics and we want to be able to pass a modifier which i always recommend to do that you can pass a modifier to the outermost composable for your composable you're creating then we can create our card here where we can say okay we have a background color which will be our background color we will have a corner radius or how is it called shape we make that a rounded corner shape 10 dp border radius or corner radius and we say modifier is our modifier we passed and we apply some padding of 16dp cool and in that card we will have a column we'll say i'm going to fill the whole width with that column and also apply some padding here pretty clear i guess while we do this so that we just have some spacing here and some spacing here and we actually only want to show the weather card if the current weather data of our state is actually not null so we could actually say state weather info currentweatherdata.let and say yeah data or so and then move that into here otherwise we wouldn't show this card at all and in this column we will then make use of this weather data so what comes next next we will actually start with this today text then we'll have our image and our temperature and so on so first of all a simple text where the text will be equal to today and here we now need to actually have the time of today basically parsed as a normal time string so we could say data dot time dot format and we can simply specify a pattern here date time formatter of pattern and the pattern will be 2 capital h's and a m so we will just have this 24 hour format here and minutes then i want to make sure that we align this till to the end um alignment.and so just that it sticks come on um to the to the end of here of the column so there's a right side can i retry this my android phone always automatically disconnects i don't know why it does that however we also want to assign a color here um i'll just i will just hard code these colors here of this app to what i like them to be i won't use any material theme here also to not make this too long so that's already it for the text next will be a little spacer with a height of 160p and below that we will have our image for the weather type so simple image the painter will be a painter resource so we want to pick an image from our drawable resources and the id of that we will actually get that from the weather type so let's go like this we say actually data dot time data dot weather type dot icon resource that is the corresponding icon that comes from our weather type class which we have here so for each weather type we actually have an icon resource you can see where i just assigned a specific image for specific weather type so we can very easily deal with that on the ui layer i won't assign any content description here because i'm lazy and we want to give this a width so we actually have an image which i will pick 200 dp here for and in general you also want to center this horizontally so we should assign that parameter here to the column horizontal alignment will be centered horizontally unless we specify something like this let's have another spacer with a height of 16dp again now what comes next after the the image next comes the temperature so we will have a big text where we'll say the text is um data.temperature celsius and then degrees celsius font size will be set to 50sp and the color color.white then another spacer now comes the weather description so we take a look here that will be this overcast or whatever type it is maybe sunny or maybe drizzle or so um so that one we can also get from the weather type here so for each weather type we have a description which we want to display so here's overcast for example so we have a text and the text will be data weather type weather description the font size will be 20sp here and the color will also be color white then i want to have a little bit more space than usual let's say 32 dp and next as the last thing for our weather cart will be this section here with these icons and the corresponding values whenever you have uh repeating elements in your ui like this i would make them composable in this case i will call them like um how did i do it whether data display so we just display data of whether you can say so let's make that composable in presentation weather data display select file make it a composable weather data display what do we need to know here well we need to know what kind of value we have so the value will be an integer here we need to know the unit so you can see we have hpa percent h so that will be a string we want to be able to pass an icon which is an image vector a modifier from compose we want to be able to pass a text style to be able to change the color here for example set it to the default text style by default and going to be able to pass an icon tint in form of a color is equal to what's the default one let's just say white and the content will be super simple we'll just have a row with two elements we make sure we assign our modifier and we assign a vertical alignment to center it vertically nothing special and in that row we will then just have our icon and our our text so i can image vector will be image vector dot vector resource the the id will be um actually not we can create this somewhere else we just pass the image directory here no content description we say the tint is our icon tint and we say the modifier is modifier.size 25dp so all the images have the same size there we go next up we want to have a little bit of space horizontal space let's say 40p not a lot and then we have our text for our unit this text will be our value so first of all this 30 here for example the unit would be percent and we just concatenate these value followed by unit and the style is our text style and that's already it for this weather data display which we can now use in our weather cart so here in a row where we will say modifier is modifier fill max with and we will say horizontal arrangement will be space around to just kind of yeah distribute them evenly here so there's a little bit of space between them which looks good i think and then in here we will have three composables of type weather data display the value for the first one will be data dot pressure and we need to convert these two integers i think let's say that around to end the unit will be hpa here and the icon will be image vector of a vector resource are at drawable i need to import the right r here this one from our weather app drawable dot ic pressure the tint should be color white and we want to assign a text style but we also give this text a white color so something like that and then we can take this copy it two more times this one here will be the humidity unit will be percent this will be ic [Music] drop like water drop and here we will use the wind speed the unit will be kmh we say i see wind yeah and that is it for the weather cart we could also already try that out if that looks good if we go to main activity in here and we get a reference to our viewmodel privateval viewmodel will be a weatherview model by viewmodels we need to also annotate the activity with android entry point which we need to be able to inject dependencies in an activity using hilt such as our viewmodel and something we need is we need to request the permissions before we can launch the elbow before before we can try it out so we can say private val permission launcher so we will use the activity results api for that let's actually make this a latent variable that will be an activity result launcher of type array of tab string so we can launch this with an array of the permissions you're going to request then in here and on create we can say permission launcher is equal to activity no equal to register for activity results we need to specify a contract here which is activity result contracts request multiple permissions that's what we want to do and here as a callback we get a map for each permission and a boolean whether we accepted that or not so that's a callback after we accepted that um we actually don't need that because we checked that in our location tracker but what we want to do here is after we requested permission we want to say view model not that one viewmodel dot load with our info because then we have that permission or we don't have it if we declined but then we're ready to actually try to fetch the location and yeah tell us if that was successful or not and then we're going to immediately launch that permission launcher using the array of permissions you want to request which is on the one hand manifest permission access find location and access course location then here in our app we will just have a big column which will make a filterable screen so fill max size and also assign a background color of our dark blue which we have in our color file and then in this column as the first element we will use our weather cart the state we will get that from the viewmodel and the background color will also be no it will not be dark blue it will be deep blue which is even yeah the darker one and i think that should already be enough to try this out and at least see the weather information in that weather card so i would say we have my phone disconnected again i would say we try this out while i connect to my phone again and see if that works there we go build was successful here we will see our permission request let's say while using the app then right now we don't see a progress bar but we see our weather card so that looks very nice we see the temperature we see it's pretty cloudy exactly how it should look like of course that's not done yet we also need to have our forecast for the given day but that already looks nice we also need the progress bar and we need to show the errors but that's not too much work so back here in android studio we want to go to um a presentation and create another composable which i will call weather forecast composable weather forecast there we go and all that needs is our state with a state and a modifier like that and just as a quick reminder let me launch the finished app again when i need to reconnect i have no idea do you also have that that your wireless debugging always automatically disconnects like this is now probably like the 10th disconnect during this recording session which is super annoying however now we will build this section here so just today text and this lazy row we will just display all the times for that given day so back in here we will of course need a column and actually also only display that if we have weather info for the day so if we have is it a current weather data no because current weather data is only for one hour of the given day we want weather data per day and we want to say get at zero so that means we refer to today if we would say get one we would refer to tomorrow so we would show the weather data for tomorrow and so on so we do have that for a whole week that's why we have this cool map and we say that lat and take the data here and then put the column in this then in this column you will have a modifier modifier dot fill max width and some horizontal padding of 16 dp import tp in this column we will start off with a simple text for today the font size the font size will be 20sp and the color will be color.white then we will of course have a little bit of spacing let's say that is a 16dp again and below the spacer we will have our lazyrow so lazyrow and the content will be this since our data here is just a list of weather data we can directly say items data and get all the specific weather data objects we have here in this list and now we need another composable which basically represents such a single entry here for that weather data that is just a column with the time the icon and the temperature and i will call that hourly weather display so we go to presentation again create that hourly weather display select file make it a composable like that that will take a weather data object which we have in our list it will take a modifier and it will take a text color which i will default to white cool and yeah all in all we just have a column in here we assign the modifier to this column we want to make sure that we center the stuff horizontally and we want to make sure that the vertical arrangement is space uh what did i choose space between like that so we just make sure all the stuff is centered here horizontally and we have yeah equal space between these items because we will actually give these items a fixed height um because not all of these icons are yeah have the same height so we want to make sure that all the whole hourly composables are actually the same height so we need to give them a fixed height you can see the the sun icon is actually a lot um higher than the cloud icon and else we would have different heights for these items which would look very ugly and then for the first text we want to actually have the form at a time and i will actually use remember here so formatted time we could actually also use that for the other one where i just put it in the code directly um so we'll have remember [Music] make sure to also pass the weather data as a key here so it will actually re-fetch that format that form at a time when the weather data changes and in here we will say weather data time format and we pass our date time formatter which we can say of pattern and we use the same pattern as we had before so the advantage of using remember here is that this will only be re-fetched when the weather data actually changes and not every single time the hourly weather display is just updated then in here in the column we can show this as a text so text is formatted time the color is light gray here and that's it for this text we will then have our image which will be the same type of image we would also have used for the main image so we get that from the weather type so the painter will be a painter resource weather data weather type icon resource quantum description null again and we want to give this a size actually just a width of let's say 40 dp and then we want to have a final text to display the temperature so we say weather data temperature celsius and then like that the color here will be white again and i want to set the font weight to bold so that will look very similar actually not too white we want to set it to text color so we can change that from the outside but by default it's white and then yeah we just have a column with three composables in that and it will then look not again it will then look like this here like these items i won't reconnect now so let's switch back to our weather forecast and in here for each item we now want to show this hourly weather display with the corresponding weather data yeah which is pretty much just this weather data here and for the modifier we say modifier height 100 dp that's how i hardcoded to and we also want to say some padding actually horizontal padding 16dp and then it'll look good the next thing will be or the last thing actually will be in our main activity that we make use of this weather forecast in our column here below the weather card let's just have some spacer 16 dp we then have our weather forecast with our weather state and do we need to attach a modifier not sure let's check here no oh we actually don't even use this let's just pass this here but it won't change anything um yeah i just still want to fill the maximum width here we could also pass this from the outside it doesn't matter too much um let's just go to main activity that's now it for the whole weather data to display that but you of course also want to be able to display the loading indicator and error messages so i will handle this the following way we will have a box that surrounds all this modifier fill max size we will put the column in that box and below here we will check if view model state is loading we want to show um i want to show a circular progress indicator which we actually want to center let's just center it here in the modifier align center and we also want to check if the viewmodel state error is not null in that case we get the error message here and we want to show that we can just check it the way we do here with an additional statement since these two are mutually exclusive so loading will never be true and error will never be known not now at the same time hope that makes sense so we can do it like this um in here we'll just display a simple text text will be error the color will be color red and the text align will be center and let's also make sure to center it in the box like that i think that should be it i would say we start this and see how this looks like there we go we see our loading indicator looks like it's working and i will quickly uninstall the app and then reinstall it to decline the permissions to see if the error message the error handling is actually done right so we click down below then yeah we see couldn't retrieve location make sure to grant permission and enable gps if we then reopen this um like here weather app while using the app now we accept that we see the progress indicator again you can change the color of course that looks a little bit ugly right now but the app is working yay so i really hope you learned something in this video about clean coding about layered architecture about clean architecture and if you actually want to learn about the real clean architecture in a bigger project in a much bigger project with use cases with proper separation of current constantly using multiple gradle modules in android then i have a perfect course for that so if you are interested in that then would be really cool if you check the description and check the course um so what we did here really only covers the bare surface of android architecture and i can of course dive a lot a lot deeper into that in a real course that course is like 10 hours so you will learn a lot more about clean code about architecture about how to make applications scale that'll be definitely interesting for you if you found this video interesting and that's of course also a perfect way to just help me maintain the channel with all these free videos of course so do check it out first link in the description and apart from that i would love to actually know what kind of apps you want me to build in future they're not too complex like um i won't do an uh 10 hour course for youtube that's just too much for video but these yeah two hour courses i think are great here and there um so if you want to see some specific type of app let me know down below thanks for watching have an amazing day and rest of the week see you in back in the next video bye you
Info
Channel: Philipp Lackner
Views: 102,304
Rating: undefined out of 5
Keywords: android, tutorial, philip, philipp, filipp, filip, fillip, fillipp, phillipp, phillip, lackener, leckener, leckner, lackner, kotlin, mobile, clean code, clean architecture, clean, mvi, mvvm, native, open meteo, api, location, temperature, weather app, android studio, domain, presentation, data, retrofit, moshi, compose, jetpack compose
Id: eAbKK7JNxCE
Channel Id: undefined
Length: 88min 51sec (5331 seconds)
Published: Sun Jul 03 2022
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.