MVI Compose Multiplatform Contacts List App With Photos (iOS & Android) - KMM Tutorial

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hey guys and welcome back to a very long new video in this video we will build a full composed multi-platform context list app so we can now finally build a full app with just one single code base in kotlin and Jetpack compose which will work on both IOS and Android so if you've not thinking to learn such a technology to build apps for both IOS and Android for a while then please do yourself a favor and watch this till the end I know this is a very long video but these are really the videos that will help you the most of course it also helps to watch videos about these single pieces but then you still don't know how to put all these together in a functional app how to structure the architecture of that app and just have an impression of how to approach such a bigger app so if you want to learn this technology compose multi-platform and kmm and you don't watch distillion then I really can't help you I mean I could make this a paid course this easily took a full Work Week full of preparation and recording and editing but I decided to make it a free video on YouTube also just as a thank you to you to just give something back because all of you have been so loyal to my channel for such a long time the channel has been consistently growing over the past years so I also want to have videos on my channel for those of you who might not be in a situation where you can easily um afford one of my more advanced paid courses and maybe a question some of you will now have is do you need a Mac to follow this because initially when we wanted to build kmm apps so with kotlin multi-platform mobile then we just needed that Mac because we would we needed to write Swift Code so Swift is the programming language we use for iOS development for Native iOS and that sadly only runs on a Mac however now since we have composed multi-platform we have pretty much our whole code base in kotlin and that has a pretty big advantage and that is that also if you're using Windows you can fully follow through this video what you can do on Windows is actually run the IOS app because that requires a simulator from IOS but if you are just a native Android developer and you want to learn how to build such a content list app potentially also with kmm then you can follow this exactly the same as people with a Mac can just that you can't run it on iOS so let's now take a look at the app that we will actually build this will not only be a plain context list app but actually a material 3 context list app so it will support things like Dynamic colors it will support dark mode and use all the new material 3 composables so you can see on the left side this is our Android app on the right side this is our IOS app they look exactly the same since we share the UI with compose and since we share the UI we also need a way to share review models so if that is new to you in kmm then you will also learn how you can do about and just have one single view model for both Android and iOS then as you can see this app also supports selecting and saving photos so each contact could have an optional photo some don't then it will just display such an avatar image but if we want to add a contact here and click on this Floating Action button then this little add contact sheet will open up here we can select the photo one of these let's select that one and you can see we have the contact photo we can give it a name test test some sample phone number and a sample email address like this and if we then hit save boom there is our new contact it gets added to the Recently Added category so we also see a history of our added contacts and I intentionally added this functionality to add a photo here to contacts since that requires native functionality because the local file system obviously works differently on Android than it does on iOS so we kind of need to implement a solution that works for both so photos will be saved just as normal files on the local file system and then we have a local database implemented with SQL Delight which will be used to just save the additional data like the first last name phone number and email address and last but not least you will learn how I would recommend to manage dependency injection on multi-platform projects since here we don't have access to libraries like dagger Hilt and maybe it will also quickly show you Dark theme and dynamic colors what that actually is if you may be new to material 3 that's basically just a new way of new UI guidelines when we use these material components which will adapt the look and feel of your app to your wallpaper and you can see that my Android app has different colors than the IOS app this one here is in green colors well this one has yeah purplish kind of colors so to make that work we just need to define the base color scheme which is this green here on iOS because on iOS um we can't use Dynamic colors so that is just something that is integrated into the Android platform but not yet on iOS but I also guess that won't happen since it's not from Google but on Android this will actually adjust based on the wallpaper we choose so you can also see here we also have these adjusted colors but if we now go back to our home screen you can see my home screen wallpaper is purple and if we change that wallpaper and style and we then click change wallpaper and then we yeah just choose something maybe from this art category maybe this orange like wallpaper here if we select that wait a little moment then make sure that's applied for home and lock screen a little bit laggy here home and lock screens select that and this is now all wallpaper and if we now go back to our app and this is super laggy here on Android the audio is simulator is working much better but doesn't matter if we now go back to the app you will see it adjusts to our new colors and I will need to create a new emulator here but I will do that in a moment you can see we now have an orange color theme yes it's okay and also if we switch to dark mode let's toggle this on well it takes a little moment this is so laggy and there you go so you can see we also have a dark theme which is now adjusted to the user's wallpaper if we click here we have these adjusted colors so on Android these Dynamic colors are quite cool and I will show you how you need to actually adjust your theme in order to make this work and look well and just before we finally get started with building the app there is currently a summer sale ongoing for all my paid courses on my website so if you've been enjoying my free videos and you want to take this a step further and just build more advanced apps for example also a kmm app which really uses the native UI this is the compose UI which of course doesn't feel that native anymore on the iOS side but I also have a more advanced Android premium course about km where you learn how to do exactly that with a cool translate wrap if that sounds good to you or some other courses are interesting for you check the first link in this video's description and make sure to apply the discount code summer 25 during checkout to get them for less and in order to get started you need to pull the initial code from my GitHub because that make sure that you have the exact same version combination that I use for all the dependencies that you already have the setup that you need for composed multi-platform and that you already have the initial color theme setup of course I will show you how you can set up your own color theme and I will explain um how you can create that but since compose multi-platform is currently in Alpha it might be that if you watch this video at a later point and just use the most up-to-date version of compose multi-platform that things might not work out in the way I show you here so that is why I really recommend you to choose the exact same versions that I do so just pull the initial code from GitHub to just make sure it will work right away if you want to learn how to set up such a composed multi-platform app for your own apps and then I have a different video which I already created I will link it somewhere up here but if you just want to learn how to build such an app then just watch this video for now you don't need to know this exact setup yet so here on GitHub you'll also find this link in this video's description you can simply go to code copy this https URL here and then go to Android Studio go to file new project from Version Control and then you simply paste this URL here of course you need your GitHub account linked but then you simply pull this into your Android Studio with a single click on clone and to get started with this I want to explain the dependencies we will use first so just the libraries we need to use which ones we use why we use exactly these and then I will go through the theme so the initial colors that I've set up I will show you how you can create your own theme and how I have created these colors and then we finally get started with writing some code so right here I am in our build up Gradle shared file which contains all these shared dependencies that we share between IOS and Android so that is how that is structured if you're new to kmm then you should definitely watch my km setup video so you need the kmm Android Studio plugin in order to make that work I will also link that somewhere up here but if you already know how to create such an empty kmm product then with we basically have an Android module here which is nothing else done in Android app you already know that from Pure native Android product and we have such a shared module which contains Android main common Main and iOS main I will get into the details here in this video for sure but in the end this shared module contains the code we want to share between ours and Android and then in this app this is pretty much all the code our app has so there's still some platform specific things like image picking and saving images which is why we have this Android main iOS main directory but really most of the code will go into common Main and really just to show you how little Swift Code we need to write this is the full app in xcode so in Swift We just need the single compose view which we need to create and this is how it looks like all the other code will be written in kotlin let's go through the dependencies we need to use here um the start is not so interesting here but what is interesting is this common main variable here which contains dependency is and apart from the compose dependencies which we directly add from the compose multi-platform plugin so you just need to make sure that you have this plugin here and you can add these dependencies so we use material three obviously we use the Amex tenant icon so we have more icons to be used in our app we use the compose resources so we can also deal with things like displaying images in composables in the shared code and then we have some custom dependencies on the one hand SQL the light which is our local database solution which is purely written in kotlin so we can share that between IOS and Android we have kotlin X data that is just a kotlin version of the daytime Library which we have in Java to get things like the current timestamp in milliseconds to just deal with the dates and add days add hours and just do some date math then if we scroll down we have specific set of dependencies for Android Mains such as pure Android dependencies on the one hand we need an Android driver from SQL Delight we need the app compact dependency and we need activity compose for image picking then for iOS main we just have the SQL native driver which we need there and if we scroll down we have an additional set of dependencies down here which we need to specify here and that is Moco that is um and that stands for modern kotlin and is a set of libraries that is special specialized on making carton multi-platform nicer and easier and this Library will make it very easy to share things like a view model it will make it easy to use flows and also have these on Flow collectors in Japan compose so that we can collect the flow as compose State because that of course Works a bit differently since we're now dealing with compose multi-platform and these things also need to work on iOS but that's pretty much it in regards to dependencies we also have some extra stuff in our Gradle file but here we really just specify the plugins we have or compose plugin again and yeah what else we have a Gradle file in our Android module which doesn't really have more stuff this really looks like a normal um Gradle file from a normal native Android product it also was generated that way I just made some very minor adjustments like the jvm version and stuff like that and also edit the compose plugin but here we really don't need to change much and now coming to the color theme how did I create that so first of all when you want to create a material three color theme you need a base color so basically a color your app will be in in Dynamic colors are not supported because those are only supported I think from Android 13 onwards and before that you just need a fixed color theme which won't change depending on your user's wallpaper and for that you can use the material 3 builder from Google so just go to this URL here I will also link that in the description and here you can really just um insert a single color and this generator will generate all the other theme colors for you which is of course super helpful so here on the left for core colors want to click on primary and then we can choose our primary color and depending on that primary color the material calculator will calculate all other colors that you up need so that it looks well so you can see if we move this here then the colors will automatically change and you can of course choose your very own color theme here I will choose my brand green color of course which I think is this color code um yes that is my brand color and if I now click close then you can see how that looks like so it automatically generated all the other colors if we scroll down this is how the material 3 components would look like you can also enter something here because so you can see how it looks like when text fields are focused how progress indicators look like how these chips look like how buttons look like and down here are the actual colors that you need to now copy and paste in your anode project that takes a little moment because you need to copy every single color individually which is a little bit annoying um I wish they would have just generated some code so we can just paste the kotlin code and paste it in our project sadly that doesn't seem to be the case yet so just copy these and paste these as compose colors exactly like that in your code I only did this for you so if you pull the product you already have this take a look in Android studio and go in shared common main UI color then here you see our whole theme colors so yeah just pasted these color codes here as compose colors for light theme and the same for dark view so quite a lot of colors but now we can work with that and Define our theme which is here so we have a dark color scheme we just assign exactly these colors for primary secondary surgery and so on and we do the same for our light color scheme and then I will show you in compose code how we can now um have that final theme wrapper which also worked for compose multi-platform and I would say restart writing our first piece of code and that will just be the core of our app that will be the contact model so in our common main module in this root package here we want to create another directory called contacts for a contact feature we just have one single feature here but inside that we will have presentation domain and data folders or packages we want to put our model in domain of course since that's where a core model belong create that and in here we have our contact that will be a data class and now we need to think about what kind of data a specific contact needs to be displayed on the one hand obviously in ID that is what we use to save it in our database later on and to uniquely identify a contact with only the first name a last name we need an email address and we need a phone number and now we also of course want to be able to save a photo an optional photo with each contact how do we do that or how is how do we save that in Cardinal multi-platform I would recommend to just use a normal byte array here so photo bytes for example which is a nullable byte array since that is just a pure content data tab that can be used to represent a photo you can't use links like a bitmap here because those are only um specific to Android and we can't make this work in the iOS module um I think so we have we have the jetbrains bitmap but not the Android one I will show you how we also make use of bitmap to show these later on but here for this actual model we want to keep this as isolated as possible so we just use the pure kotlin data types and our studio complains a little bit here because we should generate equals and hashcode functions and since then it will also compare the contents of our byte array but let's just leave this here and this will generate some weird stuff as you can see um here I will just leave this because we don't really need to compare two contacts if we don't need to do this then we don't need to worry about that so now that we have our contact model what comes next first of all I would like to start with building the core UI so I will leave out the Recently Added section yet and start with a normal contact list so that we can always check very early if it works on IOS and Android by just using some dummy data and for that I would like to go into our contacts folder and create a presentation package for all of our UI related stuff and in here I will create a contact list state and that will be a data class that just summarizes all the values that could change and have an impact on our contactless screens UI so what kind of data do we need here what kind of State on the one hand of course our contacts list which is just a list of contact initially an empty list we want to have our recently added contacts which is also just a list of contacts we want to have a variable for the selected contact which is a nullable contact and null by default so as soon as we click on a contact we assign the context we clicked onto this state so we can show the contact detail sheet with all of the details we don't want to have a state whether the is um add contact sheet open so some Boolean and folds initially so if we'll if we click on our Floating Action button and this add contact sheet opens then we toggle this to false and we also want to have the same for is contact details open is equal to false well let's actually call it is selected contact sheet open to just have some consistency here in naming and we then finally want to have a first name error which is a nullable string so in case the validation fails for the first name field then we're going to show specific string there and we'll have the same for the other types of fields so last name error we have email error and we have phone number error like this the only thing that's not missing is the values of the states of the text fields of a new contact but we will make that a separate compose only state because I'm entering text fields in a flow or managing that with a flow and reactivity can lead to some problems which I will talk about later but for now this is the full State we need for our screen next up I want to Define an event class that will be a skilled interface actually and there we will Define all the things all the UI actions the user could potentially do on the screen so every single thing like clicking on The Floating Action button to add a contact or dismissing the contact sheet or just deleting a contact so all these things will be one single event because then we can just say hey that is our action but the user just clicked on that button for example we send that to a review model to handle it there and update our state accordingly but that we go to presentation create a new interface which will be called contact list event select sealed interface and in here we specify all the different events the user could potentially send from the UI to the view model on the one hand that will be actually object which will be called on add new contact click when the user clicks on add contact we send that to our viewmodel when the user dismisses that we also want to send that dismiss contact this will be used for both the selected contact sheet and the add contact sheet so we just dismiss all sheets that are currently open then we need events to change the contents of a text field so on first name changed with the new value contact list event duplicate that three more times do the same for a last name this one for email and there's a one for the phone number like this the next a data class when the photo was changed so on photo picked we can pass a byte array um just invite array also contact list event and let's just make that a normal class so it doesn't complain here we again don't care about comparing these events um so they don't have to be data classes we then need an event in form of an object when we click on the photo so when we want to add a photo so on ADD photo clicked contact list event we have an event when we want to save the contact when we hit save contact list event we have an event a data class when we select a contact like this so if we click on the contact to see its details contactlessly done we have the same for editing a context to add a contact without contactless contact and contact list event and last but at least we have one for deleting a contact this doesn't need any argument or it doesn't need the contact to be deleted because we can only click this button to delete the content contact when we already have the selected contact sheet open so in the app when we click on this contact here for example then here's our delete button and then we already know which contact is to be deleted because that's open so if we click this here and you can see it will directly disappear okay so now we have all the things the user could potentially do on our contact list screen which is also the the only screen in our app we have the state oops we have to State here and to also make sure we'll also know which values could have an impact on our screen's UI and I would say we start writing a component a composable for the contacts photo so here if you take a look on iOS the photo would just be this component here if there is no photo for that contact we just display this little box in material three colors with this icon and if there is a photo we obviously display it and this component will not only be used used here and here but also if we select the contact then here this photo component will also be used to also show this in larger so how does that now work how do we now create a composable we do it the same way as we did on Android previously we just go to presentation in this case create a components package where we will put all of our single guy components which we use on our screen and which are reusable and here we create a new cotton file called contact photo normal file and in Cube we can now create normal composables just the way we do that on Android it's super cool so we call that contact photo here we want to pass the contact that we want to show the photo for which could be nullable here we want to pass a modifier which is the default modifier and we want to pass an icon size so how large this contact icon wouldn't be if there is no specific photo which would be DP size equal to 25 DP by default Alt Enter to import that and as you can see there is no real difference to normal compose however in a moment you will notice a difference because if we now go ahead and we just say okay we check if the content a contact dot photo byte is not null so if there is a photo to be displayed and we then use an image composable well we have a painter an image vector and an image bitmap obviously we don't have the option to directly display by the rate we also don't have that in Native Android and Native compose but that is now a bit different because normally what we will do is we would read in that byte array as an Android bitmap and then convert that to compose bitmap we can't do that here because we're not in an Android product and we don't have access to that Android specific bitmap so we need a solution that works for both Android and iOS and that is now where these Android Main and iOS main modules come into play because if we have code we want to use in our shared module but the implementation of that code differs based on the platform based on Android or iOS then we can specify the code itself inside of common main so kind of an abstraction of it and write the implementations once in Android Main and once in iOS Main and that is actually the case for displaying such a bitmap or converting it by the way to a bitmap because that works a bit different on iOS than a thousand Android let's take a look at how that actually works so no root package in common main we create a core package which is just for our shared stuff that could potentially be interesting for multiple features we only have one feature here but I don't see this utility function right here fit into our contacts feature so core presentation and inside there we create a file called bitmap util just a normal file and now what we'll create is a so-called expect function expect is a keyword that is only for kmm products or cotton multi-platform projects at least and here we call this remember um bitmap from bytes we pass our bytes array which is a nullable byte array and we then return an image bitmap which is now from compose that is what we can use in our shared section of the composable but the conversion needs to happen on each platform individually and I will show you what this xpac means in a moment we just want to annotate this with composable to make sure this needs to be a composable function and all this expect is comparable to an abstract class or abstract function with expect we specify we want to have this function but the implementation is the actual implementations belong in Android Main and iOS Main and currently complains because there is no such implementation of this function which we can change now so going to Android main we want to create our same packet structure here core.presentation and inside there we also create a bitmap util file select file and here we now have our composable function which is now the actual function so the actual implementation of this expect function needs to be called exactly the same remember bitmap from bytes we have our bytes byte array call it the same and we return a nullable image bitmap from compose and in this Android main module we now have access to all the Android specific code because this is specific for Android and here we can also make use of the normal Android bitmaps and it's still complaining here um okay because yeah that is not null this image bitmap should be notable of course but the byte Ray is now we also return a nullable bitmap and here we just return remember or from compose um remember with our bytes so whenever our bytes change we also want to change the bitmap that is returned and if these bytes are now not null we just return bitmap Factory which is now the function that wouldn't be available in the shared module I must say Decode by the array which is our bytes our offset is zero and our length is byte's dot size that is our normal Android specific bitmap and we want that as an image bitmap which works for our shared section as well so we convert that as an image bitmap so it will work just fine else we just return now and now we have the Android specific implementation so the code or the shared section will use this implementation if the app runs on Android and it will use the iOS implementation which we specify now if the device is running on iOS so let's actually take this bitmap util file copy this scroll down to our iOS main folder now where we also have just kotlin code but that is now an iOS specific implementation we create our core slash presentation and here we now paste our bit by Future yes just continue we will fix this we will remove this because bitmap Factory is not available here in this iOS main folder now since that's an Android cons at an Android function and instead compose multi-platform comes with another helper function which is now only available for iOS which we can access with bitmap from jetbrains here make from image and here we pass an image dot make from encode it and here we can then pass our bytes then we call as compose image bitmap and now when I'll think about it it might actually work to put this in the shared code because I think there is no reason why this wouldn't be available in the shared code we could also quickly try this out but then you have already learned about this concept which we will need later in this video as well so if we just take this code and put it in here without actual and without this one here then this should actually work just fine so we have access to these functions I'm sorry for that just notice that very spontaneously but I guess that should work so we now have one single shared remember function but you already learned about this concept with expect actual so we can also remove this bit by util from Android again and from IOS since we don't need that it seems um I thought this was only available for iOS but it seems like that's not the case let's not make this of this function for our contact photo so Val bitmap is equal to remember bitmap from bytes and we pass our contact dot photo bytes and then we also want to have a photo modifier since that differs that is actually the same I mean um for our two different cases we have here so if we don't display a photo the the shape of the composable we display is the same as if we display a photo so that is just modifier dot clip and we clip it to a rounded Corner shape with 35 percent rounding and if now our photo bitmap or just our bitmap here is not null we display an image that takes in that bitmap so bitmap content description is just contact.firstname for example then the modifier is modifier actually a photo modifier nothing else and finally want to have our content scale and set that to crop so that we always crop these images so they fit into our squared shape if that's not the case we don't have the bitmap then we want to show this thing instead um actually not this thing but rather what we have on iOS here so this would be the the alternative we will display and which also shows up here in a little bit smaller for that we just use a box modifier dot so just photo modifier photo modifier and here we also want to add a background color which now comes from our material theme so material theme color scheme and for that I just use secondary container when it comes to choosing the right colors for your for the right composables I'm usually trying around a little bit but you can see that these are the colors and I then take a look at um what the ability here is showing me another thing about what would look well for this component that I'm building here so in this case for this little context icon and if I scroll down then usually the primary container would be a little bit too prominent so this green Hue which is for the Fab The Floating Action button so that will be used if you really want to highlight an action but for such rather secondary things I would use the secondary container and if you then want to show another type of container which maybe has a different use case you can then use the tertiary container and it will all look well going back to another Studio we'll make sure that the content alignment is set to Center here so when you Center our icon we show here and now we just have our icon the image Vector will be icons that rounded dot person that's the icon I want to show here content description again just contact DOT first name modifier is um modifier dot size with our past icon size and the tint also now needs to fit to our container that is just material theme dot color scheme on secondary container because that is not the color that is displayed on this secondary container color and then it will look exactly like this so these two fit together perfectly well and that was our very first shared composed component in composer multi-platform what I think doesn't work yet is the preview for such shared composables since it's still in Alpha at least I didn't find anything in regards to that because it's so new so I sadly can't show you yet how that looks like but you'll of course see it the first time when we launch our app later on the next component we are going to build is the contact list item so in the end just one row here of our context list to display a contact and also be able to click on it and things like that so let's get started and implement this contact list item in our components package contact lists item make that a file composable contact list item what does that need to display a contact in the end it just needs our contact object and it also needs an optional modifier which we set to the default modifier and in the end this item is very simple it's just the row that displays our contact photo we implemented previously and a simple text next to that with a contact first and last name so let's have a row this one here the modifier is simply modifier and our vertical alignment will be Center and here we need to specify that with alignment Dot Center vertically in here instead of that row we first of all want to have our contact photo the contact is just or contact we passed here the modifier is modifier dot size 50dp so that is just the size that I think looks good and then I want to have a little bit of spacing so we have a spacer with a width of 16 DP and then we have our text that displays the contacts version and last name so text will be contact DOT first name space and contact the data last name here we want to assign a modifier as well and just say that text will fill the remaining width of our row okay now that we have the composables for displaying such a photo and such a contact item we can start to implement this contact list screen we will leave it out in this recently added section for now as I said but we can already implement this list here and The Floating Action button so in our presentation package outside of the components package we're going to create the contact list screen we'll also just be a composable old contact list screen there's rule on the one hand just receive our estate so it can properly display its UI components based on that it will also have a new contact which is a nullable contact I will get you later why I didn't include this in this state I see that a little bit already but it's pretty much in just the contact we're about to add and we want an on-event Lambda which we will now use with a contact list event and with that single Lambda we can now send such events up to the parent composable of this where we will host the v-model which will then yeah however receive these events which we sent with this Lambda and the V model can then process these up with the state which will then be passed down again and update our UI so as a root for this contactless screen I want to have a scaffold and this is really the same as with normal compose in Android we just used this in this case to display a floating action button at the right position here we can just create that Floating Action button we have an on click Lambda when we click that we want to call our on event Lander with contact list event dot on add new contact click I want to give it a rounded cooler shape of a 20 DP rounded Corner shape 20 DP Alt Enter to import DP and then we want to specify some content for that Floating Action button let's also already put the parentheses here on the curly braces um to get rid of this error and in here for the floating action button that content will just be an icon the image Vector will be icons that around it dot person at so just uh this specific icon here that fits pretty well through contact list app and the content description will be at contact and for such material three components such as this Floating Action button and these will usually always take care of um using the the right theme colors for this um so in this case we don't need to explicitly specify the colors since the material 3 guidelines already covered that which component should use which specific theme color for what kind of surface and The Floating Action buttons that is just the primary container one so this little prominent green color and then on primary container for the eye content we need to add a little experimental annotation here for scaffold also that is nothing new in Android um so let's go inside of the scaffold what do we now want to have here in the under scaffold hosts nothing else than just a lazy column so let's create that the modifier will be modifier dot film Max size we then want to add some content padding of heading of values 16 DP and we want to have some vertical arrangement of arrangement that's spaced by 6 into P so that will just make sure that there is 16 DP of space between each item inside of this lazy column and now we want to start with creating this in my contacts text here so we will simply have a normal item and in that we will have a text the text Will Be My Contacts And Then followed by our state DOT context dot size so however many products we have and this should actually go inside of parentheses on the count of that then we also a modifier to this text which will be modifier filmx with and I want to add some horizontal padding horizontal of the 16 DP and then let's make sure the font is in bold and then we can go on with some more items this time we have an items block um let's choose this import that takes in the list because here we now want to show all of our contacts we then get a reference to each contact which you can now use to display such a contact list item re-implemented previously so let's create that contact list oops what did I do now um go back to contact screen we want to have a contact list item pass in our contact that we're currently looping over and the modifier will be modifier.filmax with we want this item to be clickable in that case we would want to send an on-event um with the contact list event dot uh what is that when we click on an item we want to select a contact and we pass this contact we are about to select and then we also want to add some horizontal padding of 16 DP and I think for this heading values up here I only want this to be for the vertical padding so let's add this um this will just achieve that when we scroll here which is currently not possible um that the scroll container won't be cut off here but the text will actually go to the very top edge of our screen but still the padding is applied here so here is 16 DP of spacing and now I would actually like to try this out to also show you that this is already working but this composable this contactless screen isn't used anywhere and obviously we also didn't really set up our theme yet so usually in Android if we take a look here in our Android specific app then instead of the set content function we have something like this case maybe a context theme which is a wrapper that just makes sure that we can access these theme variables and values wherever we want in our project about in compose multi-platform this doesn't work at least as easily on as it does on Android so what we need to do is we need to go to core presentation instead of a common main module and we're going to create something called a contacts theme will just be a plain file and here we will make use of this expect function again which I showed you previously but we didn't need this previously this time we really need it so an expect function called contacts thing and this now takes some variables or parameters rather on the one hand we want to be able to pass whether the device is in Dark theme or not we want to be able to pass whether we want to use Dynamic colors so that the theme colors are adjusted based on the user's wallpaper and we want to pass some content which is our actual screen like this so just a composable Lambda and why do we now make this an expect function well because the implementation of this differs on Android compared to iOS because on Android we use Dynamic colors which are directly natively supported but to create that theme for that we for example need access to the Android context which we obviously don't have on iOS so we need two different versions of this theme wrapper so taking this context theme file copying that and going to core presentation instead of Android main let's implement the Android specific implementation of that I'll paste that here we want to swap this out with an actual function and now that is actually that needs to be a composable function so let's annotate this here and here as well and now here we have access to all the Android specific functions variables classes like contacts for example and in here we really just do what Android Studio would generate for us when we create a new material 3 project on the one hand we want to define the color scheme we want to use we can do that with one expression because if Dynamic Color is enabled and built dot version dot SDK end is larger than or equal to build that version codes that s because that is the Android version from which Dynamic Color is supported so we're going to just make sure that this is the case and obviously this version check is also very Android specific because in iOS we don't have these SDK versions um on is we also have versions but they work differently compared to Android so in this case if we want to use Dynamic colors and dynamic colors are supported we want to get a reference to the context which you can also now easily do since we're on Android local context.current and we return if Dark theme is enabled then we say dynamic dark color scheme without context that is a function that will now automatically create the color theme color scheme depending on the user's wallpaper if it's in dark mode but if not we will use a light color scheme um actually in Dynamic light color scheme passing in the context as well if that's not the case if either we don't want Dynamic colors to be enabled or they'll not supported then we want to check okay is at least Dark theme enabled for that device because then we want to use our base color theme so the the green theme I showed you in the material editor and we want to show that in Dark theme then we just refer to our dark color scheme which we created here in our common main theme file so this will be the dark color theme and the light one and this will also be used for iOS since we don't have Dynamic colors there and else so if the dynamite colors are not supported or not used as well also not in Dark theme that means we're just on light theme so we can use light color scheme like this so now we already know which color scheme to apply for Android the next thing is something Android Studio will also automatically generate here that's just a little helper to adjust the color of the status bar as well based on your theme first of all you want to get a local view for that that current and if we are not in edit mode if view dot is in edit mode so if we're not inside of the layout inspector in this case then we will have a side effect so this block will now run after every successful recomposition we want to get the window from our contexts um we can get that from the view view that context as activity the dot window so we just get the window reference from the activity itself because with that we can then change the color of our status bar so we can say window status bar color is equal to color scheme dot primaries or primary color that true argb so we just convert that to color that we then apply to our status mode so that way we also just make the status bar color support Dynamic Cutters and something I want to do is window compat a dart set actually get inset controller where we pass our window and or View and then we call that is appearance light status bars and we set that to whether we are in Dark theme or not so this will just make sure that if we have a dark toolbar we have the light icons on it and if we have a light toolbar we have dark icons on it which makes sense because then there is a lot of contrast and yeah that's pretty much it we now only need to call the actual material theme so material theme which is a normal composable and here we can say we have a color scheme which we want to pass color scheme we want to pass our typography so which font we use which we just specified here so this is a purple variant which is just about in our common main directory as well we're just using the default font and our content is just content so now we have the Android specific implementation of this theme wrapper on iOS we now need to do the same but it's super simple um we actually only need this last block on iOS because iOS simply doesn't support these Dynamic calories yet so going down to iOS main core presentation we're going to create our contacts theme here as well new file and we can just take this function from common Main and paste it here in iOS main make that an actual function and actually have the implementation here so this time we just directly create the material theme in the color theme or color scheme in this case is just if Dark theme is enabled we choose our dark color scheme which we specified in our theme file and else our light color scheme then for typography we do the same so we have our typography from our theme folder and for the content that also Remains the Same and boom that's our iOS specific variant here which simply does not support Dynamic colors we still need this parameter here um otherwise it wouldn't it would complain if we have this parameter here but not in the iOS specific variant and now what we need is just one single wrapper that is a composable function which applies this context theme um depending on which platform we are and this will be the composable we will call both in our Android main activity here and in our iOS code later on and for that let's go to our common main root package trade file code app that will be a composable function also called app and here we're going to pass whether we are in Dark theme or not because how we retrieve that value what the current theme is is also something that just differs on Android and iOS same for Dynamic Color whether that's enabled or not and then in here we're going to use our contacts theme passing our Dark theme value and our Dynamic Color value and then this is our wrapper where this theme is now applied then in here let's have a surface a material one so that the background color is also adjusted modifier would be modified at fill Max size and the color of that surface um let's import modifier I'll enter and the color of that surface would be material theme color scheme that background so just or background color and then here in the surface we can finally create our contacts list screen which now needs a state um where do we get that from um well we could just create a dummy State here with some dummy data that is what I will do but I will still create a view model before where I will put that state in we won't implement the functionality of that view model yet but just to have it and to show you how we can get an instance of that in our shared module so in shared context presentation new file called contact list viewmodel make that a class and you will notice that we have a view model um view model import here which does not come from Android because we can't use Android specific view models in the shared module of kmm projects since the iOS site wouldn't know what to do with these um so that is why we use Moco modelcorder in the library and that makes it very easy to share vmodels since it has a very similar import here which is called the same and that allows us to just have a normal view model here as we know it in Android and we can also use that on iOS it just does some optimizations and changes behind the scenes so that iOS is still knows what to do with this view model but it hides these from us so that for us this is really the same as for Android and in here I will just create a state the same way we do it on Android mutable State flow off and we create our contact list state like this we then have a public immutable state which is just state as state flow for now and I want to have a VAR for our new contacts which is a nullable contact and we say that it's just a mutable State off so biomutable state of not initially um I'll enter to import that so that is used to um yeah change the contact whenever we click on add contact this will be just a very empty contact number whenever we're done change the first name for example we update the first name of that contact to represent that in our UI it's a separate field because of the text field issues I mentioned um I will go through that later again but this way we already have effectively have a real view model and I want to pass some dummy data in here so let's just let's just do it down here we have a private file um just some basic contacts which is a list of and in here we can initialize some contacts we can also do this with a loop by the way a little bit easier so I'll say one two fifty let's create 50 dummy contacts and we say um map and create a contact for each so the indeed could just be it that too long first name could be first it last name could be the last it email could be um test at test dollar it.com and we have our phone number which is just I don't know one two three four five six seven eight nine or photo bytes we can't create this here yet as a dummy data so let's just keep these as node and then we say here our initial contacts are just that those contacts we create here so just that we have some data to display so in our app file Above This surface we now want to create that viewmodel instance and in normal Android we would just get that with either view model or Hillview model if you're using dagger field but with milco so the Moco specific view model we use a get view model that works a bit different compared to the normal view model composable here we need to pass on a key which just identifies that view model so it's basically just the screen this is attached to so we can set contact list screen and we have a factory which is just a view model Factory we can use the Lambda for that in which we create an instance of that so if a remodel has construct arguments we can also pass this here but right now it doesn't we still need this Factory so we say contact list view model and just create an empty instance of that with this viewmodel we can now say we have our state which is equal to view model actually by viewmodel dot state DOT collect State and that is the exact same as for real Android so we also have this collect a state function but this time it again actually it doesn't come from Moco so this is the normal the real function um that just converts the flow into compose State here but now we can take this state and pass this here to our screen we then want to pass our new contact which is a view model dot new contact and or on event Lambda it's just a view model double colon on event we need to create that in our review model so let's just have an empty Lambda here that takes in events of type contact list event and doesn't do anything yet the error goes away and you can see how clean that now is so we have our app wrapper we have our theme wrapper which applies the theme Here We then have our view model at the root level so we don't need to pollute our actual screen with that view model which would couple it to that view model which would be quite bad and harm UI testing and also things like the preview which doesn't work yet for compose Modi platform but I guess in future they will also add such functionality and now if we take this app composable scroll up to our main activity and just call this here then we can pass whether we are on Dark theme on Android that's really easy we just say is system in Dark theme and Dynamic Color will just be true since we want to use Dynamic colors and if we Now launch this I hope we should be able to see something on our device greater will probably take a little moment to build this initially and now and I am getting an issue um it looks like I forgot to remove that platform class let's click here to locate where that is and just remove it because that is just created initially by kmm or by run Android studio um but we don't need that and I removed it in the shared package but it seems like not in the iOS Main and Android main one so let's run this again and take a look okay it seems we still got an error and um I remember that there was a reason that I put this in iOS main which I spontaneously did not do here I'm very sorry for that but it seems like we need to I put this back in iOS main synthesis as compose emit bitmap seems to only be available in the iOS main module for some reason we cancel access it here but it will give us this error so let's take this um or rather let's just copy this function signature here paste it above make that an expect function and then have this function in iOS Main so let's remove this go to iOS main presentation paste it here make sure that you have the actual keyword always takes a little moment until it enables syntax highlighting here we can also optimize the Imports and here little moment then we need to import all these values on the one hand composable image bitmap remember how to enter on each bitmap and image from jetbrains and last but not least also as compose image bitmap and then we do the same for Android with the same stuff I showed you before let's just go for this very quickly um paste it in Android main you already know how that works this time composable import image bitmap import remember import just this thing here how we get the bitmap differs here we say bitmap Factory decode byte array with pass knock bytes python 0 and bytes.size and then we say pass image bitmap and now we have an actual function for Android and an actual function for iOS so this should hopefully work this function is still called in contact photo if we take a look um so that shouldn't change anything let's rerun this on Android and take a look hopefully now we don't get any errors anymore and it works you can see here is our list of 50 contacts it also displays that that we have 50 contacts we have our cool Floating Action button and if we toggle our theme we should also see that we have this Inlet theme as well if we switch our background to something else and then we hopefully also see that our Dynamic colors are changing so here wallpaper and style takes a little moment change wallpaper let's take something like I don't know solid colors would be easy let's let's choose something dark and see how that would look like just something black I'm actually not sure how dynamicalus handles this set this for home and lock screen oh there we go let's take a look at how it would look like in our app right here and we'll adjust that okay it seems like it's very similar to what we had before it's a little bit brighter with this red here but we can very easily test this if we also go back to our app or not that app actually main activity and disable Dynamic colors because then it will use our green color theme if we relaunch this as you can see I might immediately adjusted that we don't even need to relaunch it and it also supports dark mode of course cool and that now works on Android but what about making it work on iOS since this app thing here is still a composable function and iOS in Swift doesn't really know composable functions so how do we make it work to make that work that brains and kmm actually came up with a so-called view controller um those are the things that show something to the user on iOS and that view controller allows this interoperability between compose and iOS in this case and to create that that's very easy we just scroll down to iOS Main let's go to the root package and recall that main view controller make it a normal file and that'll just be a normal function main view controller and we set that equal to main view controller actually compose main view controller for some reason it doesn't show that yet or is it composed URI view controller I'm sorry um and here we now get a function and guess what this is now a composable function so that is how yeah the bridge between iOS UI and compose networks in here we can call composables so that means we can also use our app composable well how do we now know whether we are on Dark theme mode not on iOS we can already tell that Dynamic colors are disabled so we set that to false but on Iris we also have a dark and light theme so we should at least support that we can easily check that by having a Val is a dark theme and that is equal to UI screen your on screen this one here a DOT main screen darts trade collection data user interface style and if that's equal to UI user interface style that dark this one here um like this then we know the device is in Dark theme and this is not pretty much Swift Code but adopted to kotlin so that is just something that comes with kmm so that we can still use the Swift Code and write iOS specific code inside of our iOS main directory but it's pretty much just kotlin and kotlin syntax so I like to still use the thrift code in here in iOS main but we could also specify this directly in xcode later on but I don't like that so now we can take this as dark theme and call it here or pass it down and now we need to take this and call it an xcode that is really the only thing that we need to do in xcode itself where we build iOS apps with how do we get to xcode well we need to scroll up to this iOS context MP and here we have an xcode product this is the file that needs to be opened in xcode so that just contains the project information we can open this by right clicking open in xcode then this will open up here and in content view that is just the xcode specific uh or the initial code of xcode I'm going to remove this greeting because our product doesn't have this greeting class anymore but you will see we have a shared import so we can now access our shared code here directly in iOS and all we want to call now in this content view is a compose view which we need to create but that's simple I think I showed that we I showed you that before in this video but to make that work we simply need to go to our root package here new file um Swift file we call that compose View like this and here we want to paste this code um I went through this code in a little bit more detail in the video where I showed how to set up kmm compose multi-platform projects but I don't think it has a lot of relevance here for this course specifically um which is what I just pasted so all that really happens here is we create such a controller for iOS so that we can actually call this in our Swift UI view here and that just um calls our main view controller from kotlin dot main view controller so that refers to the file that refers to the actual function which then contains our compose code you see it complains because there's no such module shared we can try to still launch it sometimes it works sometimes it doesn't if it doesn't we need to rebuild our Android Studio project first of all select an appropriate iOS simulator I will use iPhone 14 pro launch this year and maybe it immediately gives us an error but maybe also not it's building and I hopefully see you back when that is finished or how through an error and that seems like the build was successful it is currently launching here on my iOS simulator and there it is we have our compose multi-platform IOS app as well our scrolling works you will notice sometimes it's a little bit laggy um that is something I noticed as well when building this especially later when we also have some animations this isn't running super fluently on iOS yet which I think is because um kmm compose multi-platform is still in Alpha and I hope they will improve that but I think it's already quite impressive that we are able to to write UI code for both Platforms in kotlin that was that was super cool now we have a working sample of our context list app but what is next of course we're not done yet I would say the next thing we Implement is our actual database so that we can really store and read contacts persistently and then we will Implement overview model logic so we can actually read these context from our database and also handle all the State updates so that when something happens when we click on a contact when we click on our Fab um that actually something happens on the screen when that is done we will implement the image picking and image saving on both platforms and when that is done we will implement the recently added contact Section on top and then we are pretty much done with this product but still there is quite a bit to do so unless better and not waste any more time and jump back to Android Studio where we want to implement our SQL Delight database to get started with SQL the light we first want to go into our Builder Gradle file in our shared module this one here and here we need to configure that SQL Delight plugin which is already added here as you can see we'll do that right here by having an SQL denied block this one here to configure that plugin and the way I scale the light works is it will just generate the necessary classes behind the scenes so the Gradle plugin will do that and we just specify the raw SQL queries once in an SQ file which the Gradle plugin gun however will ensure pred and create the classes for us and here the sql.blog we want to specify that we have a database how it's called well it's called Contact database and we need a package name for the generated classes which I will choose com.pl coding about contacts compose multi-platform for DOT database so just my package name followed by database and we want to set the source folders to a list of whoops I'm just list off and hold sqlite that is just how I will call the folder in which I put the SQ file so the raw queries we can then synchronize this hopefully we don't get any errors and where do we know the find this SQ file well for that we go into common main right click new directory we call that SQL Delight so that is the folder um the plugin will look for since we specified it here and inside that I will have a separate database folder and here if you don't have the SQL to Lite plugin for Android Studio you first need to get that otherwise you won't have a syntax highlighting for that um so by going to the Android Studio preferences you can go to plugins and you need to make sure that you have this where is it SQL the light this plugin here from Square because then you can also easily create these files if you have that from the marketplace here you can then go to SQL the light database right click new SQL Delight file or table we want to call that contact so that is just our whole database and then here we're on the one hand specify how our table looks like and then we Define the different queries we want um to access our database so we only have a single table here which is our contact entity how does a contact look like well first of all it's it consists of an ID which is an integer it's not nullable and it's our primary key and we want to Auto increment it that is just how SQL works so that's due to you then better watch an SQL course this would be a bit too much for this video to also cover every detail of SQL but it should be pretty self-explanatory we then want to have a first name property which is a text and not nullable same for last name then we have the phone number text.now we have email text not null we want to store a created add timestamp which is an integer not now we need that in order to sort our contacts based on when they have been inserted to also get the recently added contacts later on and we're going to have an image path which is just a text but nullable since we won't store the images for each contact in the database itself which you could do um you could just store it by the array in a database but that really is against performance because then every single contact entry in your database could be really loud due to that saved byte array and that can just make queries more inefficient so what we will do instead is we will save the image on the real file system of the device and then just save the path so the reference to it where we can find it and where we can read it into memory again and I'm missing a comma here and then the syntax will also be fine after creating this table we now Define the different queries we need on the one hand we won't have a query to get all contacts and here we just say hey we want to select all columns from the contacts table from contact entity and we want to order that by the first name ascending cool and that is how we Define the queries in SQL Delight it's pretty simple and we also want to have a query for getting the recent contacts again getting all columns from contact entity this time we order by created add ascending uh actually no descending for this one since um the largest timestamp will be the contact that we added the most recently and since we only want to get a limited number of recent contacts we want to call limit with the amount so by using colonier we make sure that the Gradle plugin from SQL Delight we'll just make that a parameter of our function so we can specify how many of the recent contacts we want if we pass 10 then we will get the 10 recent contacts then we obviously need a function to insert a contact entity here we can say insert or replace so if that already exists that contact and we want to insert so the if there is a contact with that ID we want to insert then we will replace it instead so it's automatically also an update function we want to insert something into the contact entity table here we specify the valids we want to insert so just all the values we have last name phone number email created add image path and then we specify the values we want to insert and here we just have six or seven question marks since then the SQL plugin will generate parameters for these which we can then easily insert or pass to our function then two more functions on the one hand delete from to delete a contact entity so delete contact delete from our contact entity table where the ID is equal to the ID we pass so we just delete a contact by its ID and we also have a function to get a contact to buy its ID so select everything from contact entity where ID is colon ID and those are all of our queries now which we can use in kotlin code after we rebuild our project or maybe we don't even need this um let's quickly take a look in another main activity maybe in contact photo just to see if we have access to contact database yes it is already generated as you can see um so we don't even need to rebuild pretty cool so next is we want to go to our common main domain package and here we create our contact data source that will be an abstraction Zone interface we will use which just defines the functionality we want to have in a review model in our presentation layer to access some contacts or to insert a contact or to do later contact so on the one hand you want to be able to get contacts which will just return the flow of list of contact so flow just because that will emit a new value and the updated list whenever something in our database changes pretty cool same as we have that with room we don't want to have a function to get the recent contacts passing in in the Mounds this also gives us a flow of list of contact we don't want to have a suspend function of insert contact where we pass in a contact we want to insert that is a suspend function because we don't return a flow here oops because that is a common question again however why aren't these suspend functions but these are and well because if we return the flow then the flow itself already contains the suspending Behavior and the flow is rather just the router whereas for inserting a contact that is rather an action we do and that is something we don't execute in the flow or where we don't get a real stream of updates so that is why that needs to be suspend function and that simply does not need to be won and we will have a suspend function to delete a contact by its ID I want to pass along here for the ID and now that we have this abstraction we of course need the implementation in our data package because that is how things work in domain driven design that we really have that isolated piece of code in our domain layer right here um so This Cloud does not use any third-party libraries or any library at all it's really isolated and if we at some point decide to change our database implementation for example from sqlite to realm which would also work for km product then we don't need to change any of the classes in our domain layer and only those in the data layer that was the goal of such domain driven design since in the end all of our other classes will depend on this interface and not on the concrete implementation which we're about to create and since this interface won't change all the classes that use this interface also won't change if the actual implementation changes I hope that makes sense just to also explain that for those of you who might not know why we have this domain layer and why we create such an abstract initially also never really knew why we use interfaces but interfaces are pretty useful so in our context package we want to create our data package now and here in data we can now put the SQL the light contact data source so that would be the real implementation which uses SQL Delight and what will this need in the Constructor on the one hand over DB which is a contact database that was not generated from SQL delight and it meets a private verbal um which we don't have yet let's leave that um this will later be for storing images but let's do it one step at a time we want to make this Implement our contact data source and here we can hit Ctrl or command I to implement all these necessary functions so how do we get this stream of contacts now from our DB first of all we want to get the reference to so-called queries just by saying db.contact queries that was generated from SQL Delight and what this query is we now have functionality true actually um access all these queries that have been generated here and in particular what we will do here is we will return queries and add in get contacts so for every single query we Define in our SQL file we now have a cotton function and let's get contacts to return the query of type of contact entity that is the class that represents our database entity we want to call as flow to get these um queries as a flow then we call that map to list so we actually don't have a flow of query of conduct entity but rather a flow of list of contact entity which is what we want or at least almost what we want we have contacts here we have contact entities here so it's not fully done yet because we need to map these we need to take these entities which are the models we store in our database and we need to map this to our domain model same reason as before why we have these multiple models this is our domain model which is isolated and we don't want to trigger a change in this domain model when we change our database scheme for example and that is why we have separate models on the one hand those for the data layer and on the other hand that for the domain layer which we use in our whole code base and in clean architecture we typically just Define so-called mappers for that so that will just be a normal function in kotlin in our contact data package called Contact mapper select file and we now want to have an acceler function that maps The Chronic entity to a normal contact like this and here we can then just construct our contact object pass in the ID or is it a long or an integer I'm not sure it seems like it's fine first name is first name last name is last name email is email phone number East phone number and oops the photo bytes let's keep this null for now and add a to-do here that we want to get the actual image because from our contact entity we get the image path which is a string and in our domain model we already read that in as a by the way but since we don't have the functionality yet to to read a file and to save a file in the first place we want to keep this simple and leave it for now back in this SQL the light contact data source inside this map block we now want to map every single emission of this flow to it so that refers to the list of content contact entities which you get from our database and sends it to list and we want to map every single item of that list we need to call that map again so this outer map refers to mapping the emissions of the flow one emission would be a whole list of contact entities but since we also want to map every single one of these of this list every single entry so every single contact in this case we need another map here so here we could also also give this a name of contact entities and then just make this a bit more clear what this is and here we don't have an individual contact entity like this and then we can say we map this to contact entity and data to contact and boom probably don't have any errors anymore we can then take this piece of code copy it paste it here for get recent recent contact because that's very similar instead of getting the context we want to get the recent contacts passing in our amount and this amount seems to be a long let's just convert that to along here and the rest is fine and then here we have the function to insert a contact also very simple we just call queries insert contact entity the ID is the idea of the contact we create we pass the first name with contact first name we pass the last name with contact last name phone number with contact.phone number email contact.email created at how do we create that because that will just be the Unix timestamp of yeah basically right now and usually in Android we use system.currentive Millis but in km we don't have that because this comes from java and it came and we only have access to kotlin specific functions and the libraries and that is why we actually included the cotton daytime Library so we can say clock the dot system dot now a DOT two Epoch milliseconds and that is how we get the current Unix milliseconds in kotlin and the image path is null for now where we also need to add something later so we need to also save the image before inserting a contact retrieve the file path and pass it here to save that in our database and to delete a contact we want to call queries delete contact by ID also not finished yet later on when we delete a contact we also want to delete the attached photo which we again don't have yet so now we are able to interact with our local database what's the next step The Next Step I would say is creating a review model so we can really read in these contacts and save them in our state so we really see them on the UI to do that let's scroll to our contact list view model which we already have and here we now need to provide this data source the contact data source and the Constructor and since that now depends on our abstraction and not on the SQL the light contact data source this view model will never change if the implementation of our database changes also if the schema of the database changes this view model won't change unless we really add or remove a function of this data source itself and to Now read in these contexts in our state where we have this contact list and also the recent added contacts we can't just assign the values here and read them runs because we get flus back from this contact data source that is why we need to make use of a combined block so we need to say actually State a DOT or not like that we use combine I'll explain that in a moment we want to combine the state with contact data source that get contacts and contact data source dot get recent context let's say you want to get the most out of the trendy most recent contact and in here we know in a reference to the state the contacts and the recent contacts so what does combined block will now do is it will take multiple flows the state here is a flow as you can see this function returns a flow and this function also Returns the flow and this block will now be called whenever any of these flows emits a new value and it will provide us with all the most up-to-date values here so we can now say what we've returned in this combiner is state.copy and we just save the context from our database in our context table we save the recently added contacts also from our database and then after that we call that state in we want to state that in viewmodel scope we're also expanded in a moment sharing started while subscribed passing five seconds here and we pass the initial contact list State here we can then scroll up and remove this dummy list of contacts like this scroll down and remove this one here as well optimize the import and let's now take a look at what we did here so whenever any of these flows embed a new value just make sure that we update our state automatically with the most recent result from our database so if the context changes so if we insert a new contact and actually both these flows will trigger and will automatically update our estate then state in however we'll convert that result so combine itself just returns a normal flow we want that to be a state flow so State n will convert that to a state flow again and this combiner will execute as long as there is a subscriber to that state flow so as long as we listen to that with our compose State and if the subscriber disappears then this will be executed for 5 more seconds that is how this works and now what we can do is we can implement this on event function so whenever the user does something on the screen which will trigger a state change we want to react to that in this uneven function so depending on what that event is we have a one expression we can hit Alt Enter to add the remaining branches to just save us some time when the user is about to delete a contact so when they hit the trash can icon on a contact we want to launch a cartoon in view model scope since we need to execute this panning function here which is deleting the contact and we first of all want to get a reference to the contact we are about to delete we can get that with the underscore State value Dot selected contact instead of the contacts the user will delete and this is the contact here we then want to update our estate with the same state but we copy it and change a single field that will be is selected contact kit open so we just hide the sheet we then say contact data source that delete contact we delete the contact with the ID of the selected contact and this could still be null so let's add that here just make sure we're dealing with the ID this will be the ID and then we remove this done with the latest for 300 milliseconds I will explain why and we update our state by setting the selected contact to now now this 300 milliseconds delay is because of our animation delay so later on we will have a sheet here which will open up when we click let's quickly switch to the other app so if we add a contact or rather click on a contact you can see there is an animation that displays and uh shows this bottom sheet if we now delete the contact then there is a 300 millisecond delay until the sheet is hidden until now we want to keep a reference of the selected contact because otherwise all these values here of this contact will be set to null if we set the select contact to now before which is why we just hide the sheet before wait for 300 milliseconds and then finally reset the selected contact since otherwise yeah we would just have null null here for the name now for the phone number and you would see that for a fraction of a second on the screen yes there would be better ways to handle this I would usually not have a delay here but since this will already be quite a long video I want to keep this simple here you would also implement and a callback for the bottom sheet when it's fully hidden and then react based on that but I'll just keep it simple here next up we have this missing a contact what should happen in this case again we model scope.launch we want to update our state with id.copy is selected context sheet openness false is add contact sheet open is false so we just hide both of our sheets and we want to reset all the errors since if the add contact sheet is open and we dismiss it so we click on the X icon then all the errors should be reset since the next time we get back to the sheet we're going to start with the fresh state last name error now and then we have email error null and so number error now and then we want to also delay this for 300 seconds for the same reason animation delay then we set the new contact channel so the contact we're about to add should also be reset when we dismiss it and also the same for the selected contact that should also be now next up we have editing a contact so when we click on a contact this will be called here we just update our state so stated update it does copy the selected contact will be the contact we attached to our event so that our sheet will automatically open up we also want to set this selected contact sheet to True um oh no actually when we added the contact then we want to show the add contact sheet so is add contact sheet open is true but the select one should be false here since only in the ad contact sheet we have these text Fields where we can change the values and therefore the selected contact should also be null since if we get to the editing mode we don't have a selected contact which we are currently inspecting the details of and instead what we will do is we will have a new contact and we set that to event.contact then when we click on our Floating Action button and once you add a new contact we actually want to update our state with it copy is add contact sheet open true so we just show our sheet and we then set our new contact just to new contact with empty fields ideas null we then have our first name which is just an empty field last name is an empty field the email is an empty field the phone number empty and photo bytes is a null initially so we don't have a photo and maybe just quickly explain why we have the separate compost date here because I don't think this is obvious because we have this flow combiner here which might potentially take a little moment to execute and save the final result in the state flow again and we also save all the text field values of our contact of our new contact in a separate contact entry and not in separate fields that are put in our contact list State we have quite some copy actions that are done before we um type a character in our text field before that is now saved and displayed on the actual um in the state and display on the UI and because there might be a little delay between typing a character processing that and typing the next character this can lead to race conditioners that for example the second character that you enter will be displayed before the first one if the second one is processed quicker and that is why we put that in a separate mutable State off since there is no delay when it comes to updating such a compose state or it's at least so there's obviously a delay when we copy that but it will in the end be updated atomically so let's take a look at what the next event is we need to implement here on add photo clicked we don't need that in the viewmodel we handle that outside of the viewmodel on email changed now we have all the text field changed events which is simple we just um actually no here we want to just update our new contact with new contact.copy and we assign the email with the event.value we then copy this and do the same for our first name changed last name changed phone number changed so email then here we have our first name this one here is our last name field and phone number on Photo picked here we can also paste this piece of code this time we want to update our photo bytes after we pick the photo with the bytes that we attached to the event then saving a contact that is the most complex event here let's start with select contact where we just want to update the state stated update hit that copy here want to update our selected contact with the contact we attached I want to show over a selected contact sheet so we set that to true for saving a contact what do we do here or do we have to do here we first of all want to check if our new contact exists because only then we can save a contact if it exists and now we need to validate all the fields of that contact because we don't want to save a contact with an empty first name empty last name where to validate the email that it's a valid email and that is business logic so that belongs in domain where all of our business logic needs to go into so in our domain package I will create a contact a validator validator that will be an object and it will just consists of a function called validate contact we pass the contact to be validated and it will return a validation result which we create in this class you that will just be a data class wrapper validation result and then here we will just put the results of that so first name error if there is a first name error we put the string here there is a last name or we put it in there email error and phone number error if we would be super strict about clean architecture then this shouldn't go in domain that we specify exact error messages because error messages are just used to be shown on the screen and whatever is shown on the screen usually belongs in presentation but I want to not make this too complex so if you wouldn't want to do it like this and rather only keep such error messages in your presentation layer and then you could also return error code so you could return an enum for example whether a field is empty whether a field is just invalid and not in the right format and then map that result however in your viewmodel two such error messages but let's keep it simple really not a fan to to be too strict about such requirements of guidelines like clean architecture use whatever works for you in your project of course if that would be a huge Enterprise product it makes sense to have such rules and structure but we're just writing a context list app so what we'll do here is we will specify our result as an empty validation result we can also assign default values here so we can instantiate it like this so by default we assume there are no errors and now we check if our contactor first name is blank so if the user did not enter something for that we say our result is result copy first name error let's put that on a separate line first name error is the first name can be empty well let's actually still put it on one line we then do the same for the last name um so I really want to keep the validation here simple and I just check for empty fields and for the email we will use a regular expression to check for a valid format last name last name error done for the email we have an email regex which is a new regex and here we want to put the pattern which I will just copy over from my prepared product you can either copy this from my GitHub repository or just write this off but make sure you don't make any mistakes there's probably not a perfect regex for determining emails but it will catch the most common formats of emails so you can see there could be any number of characters before the ad symbol then there has to be an ad symbol then there is a domain so Gmail for example followed by a DOT and then we have the actual top level domain so.com dot d e for example then we can check if the email regex does not match email regex does not match the email we entered for the contact then we want to say result as result copy email error is this is not a valid email and last but not least phone number for the phone number I just check if it's blank and otherwise we assume it's fine um usually you could also use a regex for the phone number but this I also didn't really find a well working regex here um that accept any number and format of phone number um it never accepted by own one when I tried these so let's also just keep it simple if you're serious with that app I'm pretty sure you will also find regex so here phone number error would be the phone number can be empty and now we have all the errors saved in that result so we can safely return it going back to our contact list view model we now want to use that function so well result is equal to contact list actually not contact contact validator validate contact pass in the contact number validate and then we can check our errors is a list of not nulls so we can pass our result first name error result last name error results email error and result phone number error and this will automatically filter out all errors that are null so if there are no errors this will be an empty list which you can then check if errors is empty that means everything is successful and we can save the contact then we want to say State update hit copy this add contact sheet open is false so we hide it again and we just reset all the errors to now last time error is now the email error is now and the phone number is also not so that next time we open this contact sheet again there are no errors display then after that after we updated the state we say content data source insert contact pass in the content you want to insert um we need to do that in a view model scope so we model scope.launch put that in here again have our delay 300 milliseconds animation delay it would of course also save this in a constant which might make sense here and then we finally reset our new contact so same reason when we hide our contact sheet or add contact sheet and then we still want to keep the values of new contact um so that they are still showing while the sheet is going to be hidden although if that's not the case if we do have errors in that list we just want to update our state with these hours so first name error result for stem error last name error results last name error email error result email error and finally phone number as a result phone number error and then we still get an error here I think because we did not Implement all branches of that um contact list event class so we can just scroll down and ignore all other cases with such an else condition so now we already finished our review model this is really all the code it will ever contain in this app sadly we can't try this out yet since we don't have the UI yet or bottom sheet so that is what we will Implement next that our sheet will open up but we can enter some data and that we can finally hit save to save a contact and then hopefully see it in our list just one thing before we create our bottom sheet is that if we go to our app KT here in our common main module then you will notice that we now need to provide our contact data source here so an instance of that if we now were to create this here SQL the light contact data source then we need our data for that and creating that database again um that just works differently on Android than it does on iOS and to solve that we will create a little Factory class for that so Factory is just a design pattern that is used um solely for creating specific types of objects in this case our data source or SQL the light database instance rather now I'll put that in core create a new package called Data and in there we will create our database driver Factory because this Factory class will create our SQL driver that will be an xpac class since um it has different implementations on Android as it has on iOS as I said and all this really consists of is a function called crit which will return an SQL driver then let's go to Android main core create a new package called Data and put in our database database driver Factory in here and that will now be the actual implementation of that and we have an actual function create which will return an SQL driver and here on Android we just have that different way of implementing that because here we need a so-called Android SQL sqlite driver which needs the context which is something we don't have on the iOS side so let's inject or pass the contact here in the context oops come on pass the contact suture or Constructor private valve context unlike this and if we then take a look here in this Constructor over this class we first of all want to provide a so-called schema which was generated by SQL delight and that is called Contact database dot schema we then need the context which is just the one we passed here and we need a name for our database which I will just choose contact.db4 and that is how we create such a such an SQL light driver on the Android side we now need to do the same on iOS so let's scroll down to iOS Main core create a new directory called data and then here we will have our database driver Factory make that an actual class as well actual function create will return an SQL driver and creating that is pretty much the same as an Android just that we don't need to provide a context so we will just return the so-called native driver in this case so native sqlite driver and here we pass our schema again contact database dot schema and the name is context.db and there we go we know or now each platform knows how to create such a driver which is needed to create our database instance and now if we go to our app file here our very root composable of our app then we still need to provide our SQL data source or contact data source and normally in normal Android projects to pass dependencies to a view model we use something like dagger hilt for dependency injection or we create a view model Factory which we do here but how do we handle these things in um composed multi-platform project because dagger Hill is a Java Library which we can use in the shared section of our kmm code base and in the end we can solve this problem very easily because the core of the parents injection what it's all about is just passing classes to classes so we have a class view model and we want to pass another class another dependency to that class in in its Constructor here so we can just do very normal Constructor inject but I still want to have that central place for order penalties which we call modules Integra field which I will also call modules here where we just Define how these different dependencies are created and for that we will go to our root package of common main we create a new DI package for dependency injection and in here we will create a class called app module that will also be an expect class and here we Define all the different dependencies we have in our very simple appear that is just our contact data source which is a contact data source but since we again made this an expect class we can have a different implementation of this contact data source on iOS then we have an Android let's take a look at how that how that will work we scroll up to Android main create or DI package and in here we will create our app module class which is now the actual implementation and in here we will then have our contact in data source which is a contact data source this would now be an actual valve so the actual real implementation or real version of this contact data source and we can just initialize this with by lazy so that it just gets initialized the first time we access it and then it will always refer to the already initialized instance something we need in the Android specific module is just access to the context since we very often need that to construct dependencies such as our database driver so in here in this lazy block we will now return SQL the light contact data source which will then require a DB instance which we can then create with the just uncreated database driver Factory so we have our database driver Factory since we are in the annual specific module here we need to provide the context and then just call that create um doesn't work because we just created the driver but not the database itself let's cut this out and create our contact database this one here and here we just need to pass the driver and then it will work just fine we can then take this app module here copy it and paste it in our iOS main Creator DI package here as well and simply Ctrl V to paste this continue we can remove the context since we don't have that on iOS and in here just creating that database driver Factory Works differently because here we also don't need to provide a context since we can just create a driver right away like this we can optimize the import and there we go now we have the option to create such an app module for both platforms to have a different set of dependencies on iOS as we have on Android and then we can go to app and then simply pass an instance of such an app module here to this app composable and here in our contact list view model we can then say appmodule.contact data source that is how we pass the the reference from that app module to our review model then we just changed the parameters here of this app composable since we added this app module let's hit Ctrl or command and click on that and then in both the classes where we use that we need to adjust this let's start with the main view controller here we get an error because we need to provide an app module on iOS that's very simple we can just create a new instance of that which you can then always pass down to different composables wherever we need these specific dependencies which we keep all in this app module here if we then go back to app um command click on that again go to main activities or Android specific code also get an error here so here we provide our app module as well just that we here need to provide a context which we can get with a local context but current and let's also make sure we refer to the application context um so there are no um some kind of life cycle issues with the context if it refers to the activity so that was just something we had to fix because we had an hour but now we can finally get to implementing our bottom sheet and here a little disclaimer there is no supported way for composing multi-platform at the moment to have a real bottom sheet in our app we have a bottom sheet in Jetpack compose but that is for Android apps specifically so inside of an Android app we can use bottom sheets but not on the IOS app still we would like to kind of share that bottom feeder logic because the logic is pretty consistent we just have a sheet that animates up and then we can close it when we click on the X icon so I decided to create our very own version of a bottom sheet here but as a little disclaimer this will be a trash bottom sheet so please don't expect that this bottom sheet works exactly the same as the real bottom sheet from Android that it deals with all the different edge cases there might be so in the end we really we just Implement another composable which animates up and which we can close by letting it animate down again and to get started with that we're going to put that in our common main folder in actually core presentation so that is something that might be shared between different screens in here we could call it simple bottom sheet or we call it something like bottom sheet from wish because it's just a bad bottom sheet and please don't use that for real production apps unless the behavior you have here is all you really need and you also tested that on different devices which I didn't so let's call it bottom sheet from wish I like that create a new composable here and that will be called bottom sheet from wish with just two e's cool which parameters does the app function need on one hand we just want to have a Boolean rather this bottom sheet is currently visible or not so if we want to show it or not we then want to have an optional modifier which is the default modifier and we want to be able to pass some composable content which is a composable and a function so this will just be the wrapper around all bottom sheets in our app um so this will deal with animating it making sure it can be also closed and animated um down in that case because we effectively need to implement two of such bottom sheets in our app on the one hand for adding a contact and on the other hand for just showing a contact and both these sheets will work the same which is why I create this rubber class but the content of the actual composables will of course differ because on the one hand and we just show contacts photo we show the actual text like phone number email but as real text and not as text fields to to have these editable that is only for the add contact sheet and in compose we can very easily create such animations for composables that either show up or hide by using animated visibility that is something we also have in compose multi-platform and here we can pass whether this specific tweet is now visible or not we use our past visible Boolean for that and what we need to Define is the enter and exit transition so that defines how the animation will look like to show or to hide the sheet so for the enter transition when it shows up we want to Define that it actually slides in from the bottom so we say that it's a twin which is just an animation a spec um actually not here we first need to Define that we want to slide in vertically the animation spec is now our tween with the duration Milli set to 300 so there is a 300 milliseconds duration of that animation and the initial offset y so where our bottom sheet will start to animate is just it so you can see it refers to the full height here and with that we can basically just Define which kind of offset this slide animation should start if we just leave it at it that means it will start from the very bottom and then slide in vertically as the names has here the same is something we should do for the exit transition let's copy that paste it here and call it exit this time it's not slide in vertically but rather slide out vertically because we want to hide it now where for the exit transition and here it's not the initial offset y but the target offset Y and we also leave it at it here um so it just expands or moves completely down by its full height that's it for the animated visibility we can now open a composable block here to Now define the content that should be animated so whenever this visibility Boolean now switches then this animation will be performed so if it switches from false to true then this slide in vertical animation will be played and if it switches from True to false this animation will be played for the content we defined here and this will effectively just be a column we're going to pass our past modifier we want to clip that so that we just clip the top left and top right corners of our bottom sheet and make these around it so here we have a rounded Corner shape top start let's say we have 30 DP I'll enter to import DP and top end 30dp as well and then after this clipping modifier we want to set a background the color will be material theme that color scheme dot surface that is what we use for bottom sheets without going to add some padding of 16 DP and we want to add a vertical scroll modifier with remember scroll state so that the content is just scrollable if it gets too large then inside of this column we just put in our content composable so in here um this would not be our actual screen content so either our add contact options or the composables to display a specific contact what you see here is we get a column scope I would like to also provide this column scriptural content um so that we can just also directly put in the composables that we want to show in this column and not need to create an additional wrapper so we can change this by going up to a Lambda function right in front of these parentheses and we're going to have a column scope dot so then we can only call this Lambda function at a place where we have a reference to a such a column scope which we do have here since this comes directly from the column but that will enable this content to also use column specific modifiers for example such as weight or comparable modifiers so now that we have the wrapper we can create a specific version of our bottom sheet which is our add contact sheet for that I want to go to presentation components for contact feature and create our ad contact make that a composable which terminals doesn't need on the one hand it needs our state or contactless state um since it obviously needs to access all the different values of our state so if it's open if the um like the new contact which we also need to pass here since that is a separate state which is a nullable contact we want to have a Boolean whether it's open or not we want to have an on event a Lambda which sends contactless events up to the parent composable add we want to have an option modifier and in here we're then going to make use of our simple I call it bottom sheet from wish bottom sheet from wish it's visible if is open is true we then have our modifier which we can pass here so modifier dot filmex width and here we can now put in the content for our add contact sheet and if we take a look here at how this sheet will look like this is the content of it we'll first of all have a box around everything since we can then easily arrange this icon button here at the top start instead of that box we will then have our column so in that column we will just stack all these composables here so our clickable photo we have four text fields and then finally our save button let's start with our box which we will pass a modifier too with modifier.filmax size and the content alignment will be set to top start inside of this box before we get to our icon button that will be at the very bottom of the box since then it will show up on top of everything inside of this box we will first of all put our photo and I actually first want to have a little spacer so modifier height of 60 DP Alt Enter to import TP this will just cause that we have a little bit of spacing and the content won't start at the very top here and we actually want to put that in a separate column otherwise this won't work the way I want it to work so this column will also have a modifier of modifier film x with we will have our horizontal alignment set to alignment Dart Center horizontally so we Center everything here for example this photo and the button below and we then put in our spacer in this column we could then actually already Implement our icon button below so we have an icon button for dismissing this when we click this we want to call on event with contact list event that dismiss contact and here in this icon button we have our actual icon which is this one here with the image vector the image Vector will be icons dot rounded Dot close and the content description will just be I don't know clouds or so that will already be arranged in the top um start here so we can now start to implement our photo here in our column so if there is a photo for the contact so a real photo then we just want to show the photo um so just image composable if there is no photo we want to display this um selectable photo thing kind of and we can just click on and then add a photo so if new contact dot photo bytes is no that means we want to show the option to add a photo which will just be a box with an icon um the Box will have a modifier of modifier that size 150 DP we then want to clip this to a rounded Corner shape 40 rounding we now want to have a background of material theme dot color scheme that a secondary container so it actually has this um bluish color here in our theme or it at least adapts to the Dynamic Color theme we then want to make this clickable if we click on it we have our own event callback for contact list event dot what is it on add photo clicked and last but not least I want to have a little border here and the Border will have a width of 1dp it will have a color of material theme color scheme on secondary container and the shape will also be around the corner shape with 40 rounding we then also want to pass a Content alignment of the center here so we want to Center the plus icon inside of that box here so here I can image Vector is icons rounded a DOT add content description is add photo we want to change the tint of this to material theme color theme color scheme that on secondary container and we want to change the modifier to increase its size to modifier size oops modifier size 14 DP like this and that is already what we need to show our little photo if there is no photo at the start or when the when the contact does not have a safe photo if it does have a safe photo we need to go down here and have an else block then we just display our contact photo composable which we already have the contact is our new contact the modifier is modifier size 150 DP and it has a clickable Lambda where we will call on event contact list event on add photo clicked so if there is already a photo and we click on that then we obviously also want the user to be able to edit it and all that's now really remaining here on the screen on this bottom sheet is every text field and the save button which is pretty simple for the text Fields I want to have a composable down here which is called Contact text field it'll have a value so the actual text it'll have a placeholder which is a string it'll have an error which is a nullable string an unvalued changed Lambda which gives us the new string so the new value so we can update the state and it will get a modifier so this content text field will now just be for showing this text styling it a little bit and also when there is an error for example like when we hit save here that there is the option to show an error message below this text field which is this nullable error string so we just had a column here pass our modifier to it and then we use an outlined text field so the material one the value is value then we have a placeholder where we can assign whoops a placeholder is equal to Lambda we can have a text composable and the text is placeholder text we then this doesn't work it should work after we created all the parameters so let's go ahead with on value changed that is equal to on value changed or is it called on value change on audio change it is we then want to pass a shape to this which is a rounded Corner shape of a 20 DP 20 DP rounded corners and I want to pass a modifier to it of modifier.philimax width and for some reason it still gives us errors okay it's just because the experimental import let's add this and then there are no more errors what do we do with this error message well if there is one we want to show this below so if Aura is not now then we just want to show in our text below the text is error and the color is material theme dot color scheme we can then scroll up and you use this contact text field here below this if else condition um right here first of all a little bit of spacing modifier height 16 DP and now we have our contact text field the first one will be for the first name so the value will be new contact that first name there is no first name it's just an empty text field placeholder will be first name the error message will be state DOT first name error on value changed will be on event contact list event that first name changed with the new first name you can also put this on a separate line I think that's better and finally our modifier this modifier fill Max with and that is just how easily we can now create a text field for our contact we can now just take this copy it paste it three more times one two three for this text field this will be the last name so new contact that last name State last name error and on last they changed this one here will be the email address so email error and on email changed and here that will be the phone number phone number phone number error and on phone number changed like this let's then have one more of these spacers below and we will add our button to save the contact on click will be on event contact list event dot safe contact and this button will just have a text of um save contact or we just call it save for example and that is our add contact sheet this should be complete which we can now use on our contact list screen so let's open that and we can just put this composable at the very bottom below our scaffold since it should also show up above our scaffolds are right here at contact sheet state is just our state new contact is new contact is open is state DOT is add contact sheet open on event is on event and the modifier is actually something we don't need to pass here and I'm not sure if this will already work but since we already have a review model logic since we have a real database implementation and we have the option to enter something in our UI I would say we try this out and see if this works on Android at least that we can actually insert a contact and see that in our contact list here's still my old app let's take a look we now want to click on at hopefully this will show yes it shows the sheet it looks exactly as I want if we close it it will hide again and so on so this is our bottom sheet from wish obviously nothing happens if we click this because we haven't implemented the functionality to show our image Chooser yet which will which we will do um right afterwards but let's see what happens if we just click save without entering anything then yes we get all the valid errors phone number can be empty not a valid email first name oh here I'm missing the last name let's go to our add contact sheet either I didn't change this here no I did change it to the last name error now the error is in our validation so in our contact validator here now contact last name is blank then let's just change this to the last name can't be empty relaunch this and then this should be fixed there we go let's open this save again now it's last name but if we enter something here Philip lagner of course you could also Implement that the errors automatically hide as soon as you enter something you could have an email or for example test let's just enter an invalid email for now to check if the regex validation works the phone number is something like I don't know one two three four five six seven eight nine or so and if we now hit save then only the email error remains if we now enter a valid email test at test.com hit save then there is our contact so everything seems to work just fine for now we see our contact and obviously if we relaunch the app and then since it's saved in the database we should still see our contact there it is we can't click on it yet so that we see its details but that is what we will do after implementing the image picking stuff so I would say let's move on with that and you probably already guessed it that image picking is something that just works different on Android than it does on iOS so that again is an indicator that we need to make use of expect and actual functions and classes here so first of all in core presentation I want to create a class called image picker which will be this xpac class so the abstraction behind it and here we will first of all just have a composable function so a function called register picker because on the one hand we need to register that we have such an image picker and this will give us a callback on image picked which will give us the byte array of the image that was picked this is a composable function send for me to register this figure in a composed context but we also want to have a function pick image because after we registered the Picker we can then use pick image to kind of execute it and to really see our Gallery um with all our images to launch the Picker in the end now let's start with the Android specific implementation by going up to our Android main presentation a core presentation and creating new kotlin class called image picker and for this Android site you maybe already know how this works with picking an image here we need a reference to our activity in form of a component activity and we will work with so called activity result contracts so what we'll do is we will launch an activity for a result so the gallery activity in this case and the result is just depict image and we can do this with such a contract or launcher profit later in the VAR it's called get content here in Android and that is an activity result launcher and here are the generic perimeter we need to pass the type of argument we pass to this launcher in this case of getting content it's just a string and where we pass the mine type of what we're looking for so in this case we only want to get images so we'll pass the image mime type to that we now have a composable function close register picker which will give us an on image picked callback with our desired byte array like this this is our actual function and we also want to have an actual function pick image which doesn't need any parameters and we'll just execute our image picker here in register picker we will just initialize or get content which is activity dot register for activity result the contract is um contract I don't think we can use name parameters here and the contract is just activity result contracts that get content and then in here this is the Callback that will fire with the image Yuri that was picked so we get our Yuri referenced here and if an image was picked so the Yuri is not null um if the user just went back then this will be null if there is an image so if there is a Yuri we need to read it in with our content resolver so activity content resolver open input stream by passing our Yuri and then calling that use to open this input stream and also close it after this used block now we want to call Image picked and the byte array is just our input stream that read bytes and in our pick image function we will just use our get content and we call launch to open the Picker the mine type is now what we need to pass for this input this will be image slash asterisk so any type of image and this is how we pick images on the Android side we now need to do the same just with the iOS specific implementation for that I want to go down to iOS main core presentation called image picker actual class image picker and here in iOS we also need a Constructor argument which is different from Android this time it's called root controller which is a view controller as it calls UI view controller so this is basically um it's comparable to the activity in Android so just the root UI view which is needed as referenced here in this image picker to be able to show this opening pick image sheet on iOS so in here we want to have a private Val called our image picker controller and that is how the thing is called in iOS which we use to pick images this is a new UI image picker controller and we call that apply on that because we want to set the source type so what kind of things we are looking for what kind of images and that is UI image picker controller Source type um photo library this one here it's a very long name um but it basically means we are looking for images in the photo library so let's make sure we set the correct Source type like this then we want to have a private VAR for on image picked which is our Lambda which gives us our byte array you will see in a moment why I make that a separate member variable here we just set that to an empty Lambda for now and then we have something called a delegate in iOS that's basically comparable to an interface or not really interface but just something we delegate the work to um so in this case we want to react to something or we want to react to result of this image picker controller um so what we'll do is we will have an anonymous class here which will inherit from NS object which is just an iOS specific object but most importantly we want to implement two interface here now is those are called protocols on the one hand you are a image picker controller delegate protocol these are very long names and on the other hand Qi navigation controller delegate protocol those are really just two interfaces we want to have here so we can override the functions we need to react to this image um pick events we can hit Ctrl or command o to check all these functions we can all right now scroll down to our image picker controller delegate protocol and this is the image picker function to overwrite um actually this one here and also this one so the top one image picker controller which is a weird function for a with name for a function gives us this UI image so the picked image which you can then do something with and read as byte and this one here is used to react to these cancel events so when the user canceled picking an image we're going to override both of these and in the iOS main folder we can't use superb so this will lead to an error if we call it like that so that remove these super calls and how do we now get the byte array of this picked UI image so what we could do is we could create a byte array here according byte array and this will take a size which we don't really get from this did finish picking image there is no real size or so there is actually but it's not really like an a kotlin integer or so so something we need to do before is we need the image as and as data so which is yeah something like in like a data specific um type of Swift or of iOS and we get that with UI J UI image JPEG representation here we can pass our did finish picking image which is our um UI image and the compression quality is just um a float and then we can use this and as data here image nsdata Dot length actually that length dot to integer since this could be null let's add a return statement here if this is null and then just return all of this function and then we now have our kotlin byte array with the real image data this by the way does not yet contain the bytes of the image because we just created the byte rate and defined how long it is so how many bytes will fit into this array but we still need to copy the bytes from this real image into this byte array and here in the iOS main module we need to do this with mem copy so mem cpy which is a very unreadable because this is um initially coming from C code which is pretty much unreadable by convention um so in here we basically just want to call our bytes refer to the very start of the byte array then specify the actual bytes from where we want to copy so this is the destination here um so we're going to copy something to which is just the start of orbital Ray then from where do we want to copy from image another data dot bytes and the size is again just um image and as data not a length then we don't have any issues anymore and we can finally call on image picked because now we know that we copied this bytes to our battery and we can call our callback with bytes and then we can also say we use our picker and recall on dismiss view control animated so we just dismiss our controller I want to pass through here and for the uncompletion Lambda we actually don't need that we can call a null we can also copy this line and also do the same for when we canceled picking an image so then we also just want to dismiss it without copying something before but now we just Define the delegate we did not yet assign the delegate so what we want to do is we now want to implement our actual function register picker which will give us this on on image picked callback to the byte array and returns a unit this is a composable function and in here on the other side that is very simple we just want to now assign this path Lambda to our Global under and because we need this Lambda here in our delegate which is a separate field we now just needed to make this a public not a public but a member variable of this so we can refer to this Lambda in both this delegate and in this register picker function and then here and our actual function pick image we now want to launch our image picker controller and to do that we use root controller that present view controller to just show the image picker for the rear controller to present that is just our image picker controller which we've created and animate it will be true so yes we want to animate that and we get a callback which is called as soon as this is showed up because here in this callback we want to call our image picker controller and finally assign our delegate that we created above we can do that in the apply function here I've tried that but it won't work you have to reassign the delegate right after the Picker has been shown so we got to do it here okay the next thing we need to do is something similar to what we also did with our database or creating our database we now need to create a factory class for our image picker so to create such an image picker the reason is the same as why we needed to do that for our database so if you remember this database driver Factory here um we we just created that class since um creating such an SQL driver in this case required parameters on the Android side which it doesn't require on the iOS side so on Android we needed the contact on iOS we did not need that and there's now the same reason we need to create such an image picker Factory since on Android we also need the context rather the activity reference so we can show the image picker on that activity and on iOS if you remember um this one here we need this root controller which we obviously don't have on Android so in cool presentation I would also create a new image picker Factory class which will be an expect class and it will have one composable function which is called create picker and that will return an image picker we're gonna take this Factory class copy it go to let's start with Android again go to Android main presentation paste this here image picker Factory this time it's of course an actual class an actual function since here we now need to return the real picker so we want to return an image picker and here we need the activity reference how do we get that on Android well since we are inside of a composable function here we can just get it with valactivity activity is equal to local context dot current as component activity since the context we get here is always the activity context we just need to cast that as a component activity and that we can pass it here and that will work just fine one thing we just need to make sure here is that we wrap this inside of a remember block so let's cut this out remember and whenever the activity changes we want to create our image picker otherwise we would create such an image picker here on every single recomposition that would be a side effect and terrible injector compose um so definitely make sure to use remember so it will cache this credit instance across recompositions then let's take this image figure Factory and also paste it now in iOS main presentation we're going to click continue this time this class will need the um whoops the root controller so the um UI controller as I mentioned which is just um similar to the activity just on iOS that does needed to show this um image picker dialog or image picker or sheet so private Val root controller is a UI view controller on iOS then we want to remove this code here and also remove our unused Imports and then here we then just return an image picker I'm actually also inside of remember remember this and we create an image picker with our root controller import remember about enter and then to use this image picker and now to try it out if we can at least pick images we can't save them yet that is what we haven't done yet but we should be able to pick them and make them display in our image composable so let's go to our app composable so the very root composable and we can't really provide this image picker or make it part of this app module here since in this app module we don't have access to this UI controller on iOS in the one hand but also through the activity and that is also the same if you use dagger Hilton Android then you can really use the activity context to create something because that could of course differ and there is no fixed activity because your application could have multiple activities and we need to create these dependencies once when our application launches so there is no activity reference just for the application context and that's why we just can't have this image bigger as a dependency of our app module which is why we go to app and simply um add the search or permitus we then want to also add this to our contact list screen so here we also have an image picker parameter we can then command click into that to also add it here image picker is an image picker and now in the screen here we can now use this to actually launch this image pick request on the one hand we'll make sure that we first of all register the Picker so image picker dot register picker and when that image was picked this Lambda will fire with our image bytes in that case we'll call on event on Photo um picked and we pass our image bytes that we got from the picked image that just registered the Picker we of course still need to execute it so after registering it we need to say hey when we click this button or when we click on our image composable we now really want to show that sheet for that we're going to go to our add contact sheet and because that is the place where we want to show this um image picker when we click here so we want to kind of intercept this on event Lambda and get our event here and then just call on event with our event but we want to check if that event is on add photo clicked so when we click on that then we want to take our image picker and pick the image and all other events will just be forwarded to the on event Lambda um actually all events not all others so this event will also be forwarded here but we just want to intercept this because we don't have access to this image picker in our viewmodel for the reason I just mentioned so what is now missing to be able to try this out well we only need to make sure that wherever we call this application composable we also pass those image Pickers so we just haven't created it yet on the one hand want to go to mainactivity out here we get an error because now we need to create this instance which is simply image picker Factory um with nothing but then we call create bigger and since that is a composable function we don't need to make sure that we wrap this in remember or so because we already do this here in create picker and then I'm going to the main view controller in iOS Main yeah we also need to create that and this time it's again image whoops image picker is image picker Factory this time we need to pass the root controller how do we do that how do we get the root controller in iOS um since it's actually this kind of controller here but inside of this composable context here we don't have a real reference to this controller since we create that with that composable context but luckily jetbrains thought of that and there is also composition local so we can say local UI new controller dot current to get the view controller and then we call create picker and that is it for our image picking functionality I won't say we try this out on Android for now take a look here and wait until Gradle build and launch this there we go let's click on add contact and here we now want to click on this image composable and something happens we do see our image or our file Library actually and I'm not sure if I have some photos here let's click on that it seems like I don't since that's empty so what we will do is we will go back here we will minimize this and we actually take a photo um open the camera app and then we will see our emulator camera here yes and got it let's just take a photo like this and we should now have a photo to pick go back to our app at this here um seems like it doesn't appear is it in our images pictures yes there it is let's select that and it does show up so that is very cool um so picking images works at least on Android we could also try this out on iOS by the way since we haven't launched this in a while just one thing if we would save this now as I said um this image won't be saved it won't show up for that contact since we don't have the mechanism yet to persistently save images on our file system that is what we're going to do next but I would say let's open this in xcode by going to our iOS context MP xcode projects um and we want to open this in xcode now it seems this is still open very cool iPhone 14 pro that is my simulator let's launch this I don't think we should make any changes here apparently the build failed um let's take a look okay I see why um you can notice that the error seems to be related to SQL and there's just one thing we need to adjust in xcode in order to be able to use in the SQL the light library or an SQL Lite database that is something we can't configure in our kotlin code but we need to go to our root project here in xcode click on our product to get to the settings I want to get to build settings we don't want to filter this for the so-called other Linker Flags this one here this setting I want to double click on that and here we need to add one flag that is applied which is Dash lsqlite3 and that is making sure that we can also use the SQL library at the skl Delight library on iOS we can then close this again run this again and hopefully now the build will be successful it had still failed um didn't it apply that let's take a look in iOS contacts MP and for some reason it's it's not in here L sqlite3 that is what it needs to be now it's in let's relaunch this take a close look that it doesn't remove this um now it's succeeded okay I don't know what the reason was why it removed that or if I'm just down but I missed something but it now seems to launch I will see you back when it fully launched there we go here's our empty contact screen let's click add you can see it's a little bit laggy here in iOS and that is one downside of compose multi-platform at the moment um which I also noticed that if you have animations then these can be quite laggy but obviously it's still an alpha so um Let's ignore that for now and I assume this will get better what we're gonna do is let's first of all click save to see if the error handling works here yes it does let's click on pick image and does something up and yes our image picker sheet opens up on iOS as well if we pick an image then we do see it yeah so everything is working fine The Next Step as I said will now be to take these images when we hit save we want to put these images as a real file in our local file system of that app so we can also show this in the list of contacts so let's go back to Android studio and by the way you can also launch the app here from Android studio right away so iOS context MP I'm kind of used to launching it from xcode which is why I usually took that path and now we also needed to adjust something there but you can also launch it right from inside of Android Studio okay how do we now store our images and maybe you always guess this that is something that works different on Android than it does on is so we again need to make use of x-packed and actual it's uh still quite simple it's not super simple but it's okay we can manage that let's go to Common main first of all Define our image storage class so let's do that in core detail we have our image storage that is a class xpac class it will have three functions on the one hand a suspend function to save an image that will just receive Kaufman byte array which represents the image and it will then return a stream and that will be the final file name under which it saved that image so we can then take this file name and save it in our local database so we don't need to take this whole long byte array of an image and save it in our database but rather just take the reference to it next up we'll function to get an image so if we load the contact we obviously need to load the image as well this will take the file name which is the string and it will then return a byte array which is notable in this case since if the image doesn't exist we still need to return something and last but not least we want to be able to delete an image if we delete a contact and for that we just need the file name if it exists we will delete it then let's start with Android again Android main core data in here we will have our image storage class this time an actual class and it will need reference to the context private valid context um there's one here and the Android version will actually be quite simple maybe you've heard that I'm dealing with storage on Android is quite difficult but in this case it's really not because we're only dealing with internal storage so on Android we have internal storage and external storage external one is the one that is potentially shared with other apps so that other apps can also access these images but in our case we don't really want that we want these images to be just just to be safe for our apps or internal storage that doesn't require any permissions and that doesn't require any fancy handling between different API levels of Android it's pretty simple so let's start with an actual function actual suspend function save image takes the bytes array by the rate and Returns the final file name under which it saved that image and here we on return with context dispatchers .io so just make sure we want this on the i o thread and then we only get the file name so we just want to generate a random file name so we make sure those are unique we don't say that is The UU ID dot random uid Dot tostring and we say plus that JPEG and like this if you would be dealing with different types of images here you could also Powers the extension here I just assume we're only dealing with the jpegs here but if you also have pngs or so um you would need to consider that as well in this case but let's keep it a little bit more simple since this video is already super long so now how do we take this byte array and write it into a file in our internal storage on Android it's very simple we just use context dot open file output so we want to write something out in a file kind of instead of reading something in and here the first parameter is the file name and which one to save this and the second parameter is the mode which is private here so context mode private so that only our app can actually write into this file we then say that use we used that before since we get a file output stream here um so we could say output stream like this and now we say output stream dot right which takes a byte array and rewrite our bytes into that stream and finally into that file under that file name and then we just use our volume to return that after we've written into that file that is how we save an image next up we want to have a function an actual function suspend function not an actual seed class actual suspend function to get an image with a specific file name will return a byte array nullable and here we just need to find the image of the spelling read it in since that will be a file we need it now as a byte array again so we just reverse the same thing as we did for saving an image so again return with uh with context dispatchers IO so we say context dot open file input this time so not file output here we pass our file name um so from which file we're going to read something and then we say that use and we just say our input stream input stream dot read bytes that is how easily that works to get an image and last but not least we have an actual suspend function to delete an image under a certain funnel also we turn with context dispatchers.io and in here we're going to say context dot delete file name that's it and now the rest is for iOS now we have our Android specific image storage now we also need an iOS specific one which is actually more complex um so let's take a look at how that would work in iOS main core data I want to have a image storage class actual class whoops um with an actual suspend function called save image takes our byte array and returns a file name under which we save that image and on iOS with first of all need two Global references on the one hand the so-called file manager which is NS file manager dot default manager and we need a reference to the directory where we want to save this image so this will be the document directory and we can get this with NS search path for directories in domains very long names here on iOS on Android we didn't need to do this because this open and where is it here let's open file output will take the root of our internal storage by default we could also create folders here but on iOS we need to get an explicit reference to this document directory because storage just works differently than it does on Android first of all we need to specify the directory which is NS document directory this one here then we want to specify a so-called domain mask um so that will restrict the search for the document directory to the user's home directory which is NS user domain Maps so if we choose that then it will restrict it to that and we have expand tilt I don't know if that's how you pronounce it but we set that to True since on Linux file systems you have this little tilt symbol so this one here which refers to the user's home directory and you want to expand this here for this document directory reference and then to get the actual reference of that we refer to that first since that Returns the list of directories and we cast that as an NS string so that's kind of an iOS specific string reference just to be sure that this is a different type of class than the kotlin specific string but we can still cast this year as we like and then again we need a file name which we generate with uu ID do we do it with this no um we do it and as uuid a DOT uuid a DOT uuid string this will return a random uid and we say plus JPEG and then we need the full path full path which is not simply our volume as on Android since we need to take our document directory path and append this file name so I'm going to say document directory append um or string by that's called string by a pounding path component without file name so that would simply take the document directory path and append or file name with all the necessary path separators and now it's not as easy as an Android to just take such a kotlin byte array and write that into a file in iOS because iOS itself doesn't know these kotlin by the arrays so we need to convert that to a data type that works for iOS so we have our actual raw image data which is bytes that use pint use pint is a function that is used in kotlin Native in in some cases so in pretty much the cotton project that and Android specific product for operations that require direct access to memory without actually making the garbage collector remove around data or move around bytes so in the end since we are directly dealing with um I think it's a Objective C code here we just get a stable pointer to data so we just make sure this pointer remains stable the data will remain there until we finally write it through the file so in here we say n as data Dart create this function will on the one hand take the bytes which is in form of a c or pack pointer which you simply get with this pinned reference so this pinned byte array dot get I'm actually not get we want a pointer so address off until we say zero so we just refer to the very start of our byte array and point to that and then we have our length so the length of our byte array which is byte.size.2 unsigned long and then we have our reference of Anna's data which iOS now understands and then we just need to say data right to file the path is our full path and atomically is true so we don't run into any race conditions and then we can finally return um what is it our full path which we will learn safe in our database we should also wrap this in with context I'm just noticing so return with contacts and iOS we don't have the iotis Patcher um so we would need to use the default one let's take this whole thing oops this thing and move it in there and that will remove the final return cool that is our function to save an image you can see it's a little bit more complex as it is on Android I'm sure if you do this natively in iOS it's a bit simpler since you don't need to um do this cotton by the way conversion to this endless data but it's fine here let's go here and Implement our next suspend function called get image I'm going to get an image with a file name and that will return a cotton by the way how do we do that we again use nsdata dot um what is called um data with contents of file which will just take a file name this one here if that file exists so we can make a let's check here then we get the bytes which is in this case and as data again so not according by the way so we still need to convert that how do we do that we use this bias reference we call get bytes and here we need to again use this C or hack pointer so this function will then take the contents of this NS data and copy that into a cotton by the way which we first need to create here so about array is an empty byte array with the length bytes.length dot to integer and then we say array dot ref2 which will return this um pointer so we refer to the first or to the starting address of this byte array again and then we call Dot get pointer this will return not returning this will require a so-called Auto free scope um this is again required because um the memory management on Objective C Works differently than it does on Android with the garbage collector and things like that how do we get this out of free scope so we just make sure that this memory is freed up after the juice again for that cotton multi-platform has something called memscoped which is which gives us this Lambda block and here we have this memscope which is such a scope so we can just pass this so the scope and then the length of that is just array Dot size more actually we can also pass bytes dot length after that if that was successful we're going to return our array we also want to use around with context again so with context dispatchers dot default put this in here put this in here we don't need to change this return to return ads with context add a return statement here in front of it with context and if we did not return instead of this with context block we then want to Simply return at with context now um like this okay as you notice that was also a little bit a little bit more complex than on Android about the delete function is as easy as it is on Android so let's just have our actual suspend function delete image the image will have a file name of string and here we will just have with context this patch is default and we say our file manager this is now where we need our file manager remove item at path without file name and for the error we just insert null cool that is how this works in iOS as I said a bit more complex but some shouldn't work exactly the same so where do we now need to create this image storage reference and where do we need to use it we only need to use this if we scroll up to our sqlite contact data source only in here because whenever we save a contact UM here when we insert a contact we first of all want to take the photo bytes of that contact save that in our image storage and then insert that in our database and whenever we delete a contact we just want to take this saved file path of that contact we're about to delete and also remove the image heater and when we get contacts so in this function where we map these contacts here you remember this to do statement here we need to use our image storage again to retrieve the photo bytes from our file system so in short all we need to do here is we need to have a reference to our image storage in our contact data source than in our two contact mapper we need this reference as well so we can just go in here and say image storage dot get image and the file name will just be um photo how did I call it image image path but only if this image path is actually not null so let's use let here um image path question mark let then so if it's not now then we want to use image storage get image with it which is the file path this now also needs to be a suspend function so let's make it a suspend function here although while this will work in our contact data source if we pass our image storage here as you can see there are no errors this is not optimal because what will happen here is it will execute these mappers here sequentially that means it will first load the image of the first Contact and it will load the image of the second contact and the third one and so on however loading these images doesn't really depend on loading the image for the last contact so what we could do is we could execute all these calls in parallel that we just load all these images at the same time since they don't depend on each other how do we achieve that we can use something like a supervisor scope which gives us access to a launch and async which will launch independent core routines which run in parallel now we can say contact entities dot map and we now map this to async blocks where we say it so the contact entity dot to contact with our image storage since this is now the suspend function that is executed inside of this independent async block all these async blocks will now be executed at roughly the same time and to get the results of these async blocks after they have been finished executing we can use another map block down here and say it that wait to get the contact result and then we can remove this piece of code this is just how we execute all these map functions here parallel but that's obviously only indeed if your function is a suspending one which it is in this case but usually mappers are not suspending so let's now take this and also paste it here for the get recent contacts function and then we fixed that already to load images from contacts however we don't save this right now so in here before we insert a contact we'll make sure to get the image path by saying contact that photo bytes dot a let so if the photo bytes exist and then we call Image storage save image with that battery which will then return the image path and we can then take this image path and save it in our database and for deleting a contact we first of all want to check if there is a saved image with that contact so we need to retrieve it from the database so the entity would be queries dot in get contact and buy ID we pass the ID here and we actually also to call execute as one so we just tell a skill the light that we only expect one result out of that then we say entity dot image path if that exists then we call our image storage delete image with that file name and after that we just delete the contact from our database so now we adjusted that and I think we should be able to try this out we just need to pass this image storage reference to our sql.data source which we can do here in our app module I'm actually not going to set this one we need to go to app module in Android main here we get an error because we need to say image storage image storage ah not here actually here image storage is a new image storage where we pass our context we then go to our iOS main um di app module and here we say image storage is image storage here we don't need any Constructor arguments so I would say let's launch this and take a look if we can actually save such an image and really see that in our list and you don't see that but the app actually crashed so let's take a look and look at what the issue might be not sure what to select here is there our app now it actually inserted the logs let's search for errors okay so the issue is something compose related main activity is attempting to register while current state is resumed lifecycle owners must call register before they started okay the issue seems to be inside our image picker here because this should be how we do this and no it's actually not how we do this I'm just noticing I'm using the non-compose way to initialize that so let's not do it like this and instead use the compose import so remember launcher for activity result which is a composable function and it will just make sure that the Picker is registered in the correct life cycle um state so just swapping this out should hopefully work let's relaunch this take a look here there we go here's our contact obviously that does not have a photo saved let's create a new one pick an image the image we just made enter some data email um test oh come on test at test.com whoops and phone number one two three four five six seven eight nine if we click save then we do see the image yes it's working obviously we can't select it yet but the image is saved also if we relaunch this we should hopefully see the image still and yes it works of course if you want to take this to the next level you could also use an image Cropper so you crop it to this squared format but that would be way too much for this tutorial and we're also not done yet since we can click on context to open the contact detail sheet and we also need to implement the con at the recently added contact Section let's do that next but after that we're done so let's go to Common Main context presentation components and here we have our contact detail sheets make it a file composable function contact details sheet and this will have an is open Boolean it will have a selected contact which is a nullable contact so on the contact we just selected it'll take an on event Lambda as usual takes in contact list events Transunion and an optional 100 modifier as usual in here we're then going to use our bottom sheet from which again it's visible if its open is true the modifier is just modifier and here we're going to put our content for displaying a specific contact and let's actually say we want to fill the max width of this modifier and then here we're going to have a box for the same reason we have this in the add contact sheet let's actually copy the content here and just remove everything we don't need so just the box with everything in here including the icon button paste this here and then just remove everything in here the contact photo is needed um so let's oops let's leave that I removed a little bit too much I think no I didn't um let's move the contact photo down we remove this thing here and not including the spacer so we just have our box with a column we have our top spacing then we have our contact photo to display the context photo or just the placeholder where we need to pass our selected contact this one here is not clickable since it's only clickable in edit mode but other than that we also have a sheet to make an icon button to close the sheet which is the same um so let's go on here and take a look at what we're going to build um it just popped up because I launched the other app here on my simulator let's click on the contact and this is how it will look like so we currently have this icon button um to close the sheet we already have this photo to show the photo the context photo or this placeholder so now the next thing would be a little bit of spacing and the full name of the contact and then this edit section so we can edit and delete the contact and then finally these um sections for displaying the phone number and email cool so down here we actually let's first of all adjust this contact photo a little bit since um this time we're dealing with a very large photo and just make sure that the icon size is 50 DP so we just have a little bit larger placeholder icon down here we're then going to have a spacer modifier dot height 16 DP and below that spacer we will have our text the text will be um cops contact select the contact DOT first name actually nullable and selected contact dot last name like this we then want to change the text appearance a little bit on the one hand detects the line should be Center we then want to set the modifier to modifier filmex width we want to say font weight is bold I don't want to say the font size is 30 SP and I'll enter to import SP then we can copy and paste this spacer and now we need this added row so just this row that I showed you here so this year these two icon buttons I want to make that a separate composable down here so private composable function called edit row and this gets to lambda's unedited click and on delete click like this it'll obviously be a row let's also pass a modifier here just so that we could change it a little bit pass this to the row and how do we now create these icon buttons here since they are really just icon buttons but with the background um these are called filled tonal icon button in material 3. so we have one for um on added click which is the first one I want to change the colors here and the colors are icon button defaults filled icon um is it a filled tonal icon button colors to set the color scheme for the specific button on the one hand the container color which is just our material theme color scheme secondary container again and the content color is material theme color scheme on secondary container so with that we just achieve that we have our color schemed button here and then inside for the actual icon we have a jetpack compose icon the image Vector is icons dot rounded dot edit so the little pen icon and the content description is just added to contact and we have the same for deleting a contact UM this time we call on the leads delete click this time it's not the secondary container because I'm deleting something kind of dangerous or destructive action so we want to mark this in Red so in our hour color so this time we say error container and on error container here we want to say that is delete and this would be oops delete contact cool then we have our edit row which you can use here edit row on edit click will be citedcontact.let so if that exists we're going to call our on event with um select contact is it called like that yes with it so the contact we want to select and on delete click in here we just want to call on event with delete contact which will delete the currently open contact if it exists and then last but not least we need these contact info sections so with an icon at the start the little title and then the value which is also separate composable which I will create down here pcomm by the way you probably won't have this um shortcut here since I manually created that but you can also use um just normal comp and then make that a private composable I call that contact info section it'll have a title it'll have a value which would be the actual phone number for example it will have an icon in front of an image vector and an optional modifier cool then for that we'll also use a road should be obvious why the modifier will be modifier and the vertical range vertical alignment is just alignment Dot Center vertically so just make sure everything is in order inside of this row we want to start with our icon so again same concept is here just that we don't have a clickable button so we just use a normal icon the image Vector is our past icon content description is just null here you could also pass this from the outside I'll leave it for Simplicity and then we have a modifier the modifier of modifier that clip so when I clip this to a circle shapes or a circular circular shaped icon and then we're going to assign the background color which is our material theme color scheme secondary container again and we say we apply some padding of 8 in DP last but not least the tint so the color of that icon should be material theme color scheme on secondary container so again get the exact same look as for this edit icon then we will have some horizontal spacing of a width of 16 DP and then we're going to have a column since we will display two texts on top of each other the modifier of that column will be modifier dot weight on weight one f and the vertical Arrangement will be arrangement spaced by 8 DP so what we achieved with these two values on the one hand with weight we make sure that this icon gets the size it needs but then the remaining space will be filled by this column here and then spaced by ADP we'll just now make sure that we have 8 DP of spacing between these two texts so inside here first text will be our title text so title modifier modify fill Max width the color will be material theme color scheme that secondary so it's a little bit different color to not make it too prominent because we want to make the value prominence of the actual phone number or actual email and the font size is also a little bit less than default so 12 SP and we're done take this text paste it below have the same for our value this time the color is on background so it's more prominent and the font size would also be larger too just make it stand out a little bit here then we can take the contact info section and use it the lower added row um we can also add a spacer here again and then oops let's have our contact oops not in caps please contact info section the title in this case would be phone number the value would be selected contact DOT phone number and otherwise just uh not existing because we always have a phone number in this case the icon will be icons rounded DOT phone I guess yes and the modifier will be modified so max width we can then take this including the spacer copy it paste it below this time it will be for our email address selected contact.email this time it's email I guess and modifier Remains the Same this should be everything for our contact detail sheet which we can now also apply in our contact list screen scrolling down to add contact sheet and right here let's do it before we have a contact detail sheet this open is state is selected contact sheet open selected contact is State selected contact on the event is on event and the modifier is something we probably don't need we also don't use one for the add contact sheet okay let's try this out by launching this on our Android device here and we want to open this contact let's see and it opens that is looking perfectly fine it is displaying all of our data looking nice if we close this it does close if we open one with a photo then we also see the photo here so looking pretty nice if we want to edit this then nothing is happening yet we need to check that if we delete a contact I'm not sure if we uh if that works yes that works so deleting Works um but let's take a look at why editing doesn't work um by going into our contact detail sheet because if we click here in our unedited click oh actually I actually called select that should of course be added contact and then that should also work let's quickly try this out again if we open this click edit then yes it will work just fine we can edit our details so for the unlocker edited for example if we hit save then this will also be updated here we can see it which works perfectly fine now you will see the the null values just flashed up a little bit there um once we hit save or once we click edit rather here um which you could also potentially bypass with a simple delay or a waiting until the first sheet closed and done showing the second one which probably doesn't harm user experience but would solve this little visual issue here I also won't deal with this here just for the sake of Simplicity and we're already a lot of time in here but at last but not least we want to create our recently added contact Section and that will be very quick since we have the necessary composles for that already we just want to create a contact preview item which is a normal file contact preview item and this will just be here this section so such an item but since we already have the photo composable we just need to put that on top of the first name of that name of that contact here we want to display that for specific contact we'll have an on click a Lambda and we want to have a modifier which is the default modifier by default now let's have a column as I said we pass the modifier modifier we then want to have this as a clickable modifier where we pass our on click Lambda we want to set the horizontal alignment to alignment Dot Center horizontally and in gear in this column scope we can then have our contact photo the contact is our contact we passed modifier is modifier.size 50dp and down below we're going to add a little spacer with a height of 8 DP and last but not least our text for the contacts first name is contact.firstname that's it for contact preview item now let's have our recently added contact Section in here in presentation components recently added contacts section or recent date of contacts let's leave it like that um composable recently added contacts like this in here we're going to have our contact list which is a list of contacts surprise I'm not going to have an on click Lambda when we click on a specific contact you want to pass this contact to the parent composable and we're going to use a modifier as usual which is the default modifier in here what does this list consist of in the end we're just going to have this title text recently added followed by a lazy row with all of our recently added contacts in here we have our column since we're on a display the title text on top of our lazy row so we have a column pass in our modifier and if our contacts is not empty so if there are reasonably added context then we're going to show the title text is recently added with a capital a um the modifier for this text will be modifier filmex with dot padding only horizontal padding with 16 DP Alt Enter and Below we won actually let's also put in a font weight um Bolt and below this text we're going to have our lazy row modifier is modifier filmex width the content padding is padding values of horizontal 16 DP and I want to add some spacing horizontally so horizontal Arrangement is Arrangement dot spaced by 6 CDP so we just have 680p of spacing between each contact here and inside of this lazy row we're going to have an items block make sure to choose the one with a list which takes in our contacts gives us each contact here individually and we can then create a contact preview item with that contact so contact this contact and on click is on contact actually on click here where we pass in the contact we clicked on now we're going to take this composable and call it in our contact list screen going up to the very start of our lazy column item recently added contacts the contacts come from our state recently added contacts on click would be on event select contact with the contact we clicked on and I guess that is it let's try this out I think there should be everything of this app so want to try this out here there we go the app is launching and we do see this um contact you we just need a little bit of spacing between the title and this but if we click on this then we still get to see the added contact Section the same as here cool very cool if we take a look here in our contact not detail sheet actually what did we just Implement um recently edit contacts here we just want to add some spacing so spacer modifier height 16 DP I would say we launched this on iOS as well select iOS contact MP I'll launch this and I am very curious if this will now also work on IMS nope it doesn't build iOS application failed for whatever reason um and also usually doesn't give us many errors maybe we just need to go into xcode and build it there take a look here what if we launch it from here here it also tells the build failed um not sure why I might need to look into that and then get back to you when I fixed it so there was actually no error I just needed to rebuild the project you will probably notice that there's still an error when you rebuild it um which is something like this illegal State exception this is something you seem to currently just get when you rebuild km products with the current version of Android Studio but you can ignore this because your app will still compile and launch which I've now done here so this is now the up-to-date IOS app let's um click on add a contact because right now there's no recently added contact Section since we don't have any contact obviously let's add one let's add a photo take a look here we can also check if saving a photo Works um let's select that one rate a contact something like this has to attest oops test that is something I've noticed that if we go back and in iOS text Fields then it often removes more than just one character I don't know if that is a back of compose multi-platform or something of the simulator here but that is a bit weird um Let's ignore that and choose a phone number save and yes our image saving works we see our recently added section we can hopefully select the contact we can edit it we can say edit we should also be able to pick a different image right here click save and there we go we saved it finally deleting a contact should also work if we click the delete icon yes Down's gone so that is how you create a composer multi-platform context list app with crud operations so creating context deleting them editing them and of course just retrieving them before you go I got a little gift for you since we've dealt so much time here with building UI with jetpack compose which is absolutely great for building UI as you've seen but there are just so many deadly traps you can step into there you can really do a lot wrong with jetpack compose and I still see it as the future of UI development on mobile but to help you to avoid these mistakes and these traps I've written down 20 things you should really never do with jetpack Combos and I've combined it into one single PDF which you can now get completely for free so this is really not just a list where I say hey that's a mistake this is a mistake this is a mistake every single mistake really gets its own page I included code Snippets that show you what's wrong and how to actually fix that issue with a detailed explanation so every single mistake gets its own PDF page the whole thing is longer than 20 pages and you can really use that to on the one hand check your current project that use compose if you do any of these mistakes or also use it to just learn about these mistakes to not do them in future so if you want to get that completely for free check the link in this video's description and you will get the PDF via email and other than that I have to say thank you for watching this video for this long if you did it this long then you're definitely serious with Android development because on these videos which are two three four hours long usually have an average watch time of 7 minutes or so which I think is a little bit sad I'm given the fact that you can just watch this completely for free but if you reach this point then congratulations I hope you learned a lot and I hope you enjoyed this course I would definitely love some feedback below also if you think a certain thing wasn't explained that well or so that just helps me to improve my way of explaining and make even better videos in future so thanks a lot for watching before I will see you back in the next video wishing amazing rest of your week bye bye [Music] thank you foreign
Info
Channel: Philipp Lackner
Views: 62,452
Rating: undefined out of 5
Keywords:
Id: XWSzbMnpAgI
Channel Id: undefined
Length: 190min 43sec (11443 seconds)
Published: Wed Jul 12 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.