Build an iOS & Android app in 100% Kotlin with Compose Multiplatform

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
the new iOS Target for compose multi-platform means that you can take your mobile kotlin skills to new destinations and to the next level because it means that you can start building your mobile applications in 100 kotlin including the UI the iOS Target for composed multi-platform is currently in Alpha which makes it the perfect time for us to take a closer look at it and see what it's all about and I thought what better way to take a closer look at the technology than building a small app from start to finish without sweating any details and looking at everything that goes into building a proper multi-platform app the end result of this video is going to be a small working application that works on both IOS and Android which will show images of different types of birds that we've consumed from an API and throughout we will see how we can even share the UI layer with compose multi-platform whereas previous with kotlin multi-platform you could already share most of your business logic and pretty much everything up to the view layer in your application compose multi-platform ads that missing piece and allows you to also share the user interface for your apps so let's get started and write some actual code the easiest way to get started with compose multi-platform is to use one of the templates we provide the easiest way to access those templates is to go to jb.gg compose which will bring you to the compose multi-platform landing page from here if we scroll down and click the iOS link it'll actually bring us directly to the compose multi-platform mobile application template that targets both Android and iOS it comes with detailed instructions on how to set up but most things will tackle today along the way since this is a template I can just use the use this template button to create a new repository I'm going to call this one my Bird app it's okay to have this as a public repository because as usual you'll be able to see the source code after we're done with everything let me create this repository and after a few short seconds I'll have my own repo where my source code can now live so now that I have a we can move over to Android Studio we're pretty much going to spend the rest of this video in Android Studio so since our project is on GitHub we can just click the get from VCS button add the URL to our Repository and let it clone I'll give the project a couple of seconds to synchronize and then after that is done we can start exploring and developing but before we do that I want to make sure that my development environment is set up correctly so let's check a couple of things so to check that my development environment is set up correctly I can run kdoctor K doctor tells me that my operating system Java Android studio xcode and cocoapods are all set up correctly if I were missing any of these things kdoctor would tell me how to fix it a small reminder that since we are developing for iOS you do need a Mac to run the simulator and Deploy on actual targets if you want to build a multi-platform application that targets both IOS and Android that's just important to note in advance okay I also want to make sure that in Android Studio I have the kotlin multi-platform plug-in installed this is the piece of software that makes it possible for me to run iOS run configurations from Android Studio as well so first things first let's close this readme file and because we are developing multi-platform the Android view doesn't actually show us everything we want to see so let's switch to the project View and here we can see the whole structure of our project we have an IOS app which is a real xcode project or a real IOS app so this is the part that will then actually get deployed we also have an Android app folder in which a regular Android app is defined in its regular shape including things like the Android manifest configuration files and then in shared we find all the logic that is actually targeting both platforms if we click ourselves through we can actually see that inside the app KT we find a bit of a scaffold for a first compose multi-platform app we can start this app just to see what we're working with the Android uh run configuration is already configured to run on my emulator so that's perfectly fine I can also take a look in my IOS app run configuration and I can make sure that this one runs on an emulator as well and we can run that one too after a short build time we can see that we have the same app running on iOS as well as on Android it doesn't do much besides show us a Target specific string up in the button text as well as do this nice little animation of the compose multi-platform icon but of course the most notable part of this whole story is that both the Android and the IOS app render a consistent user interface that has only been described in code once we didn't have to write two separate user interfaces here in fact everything that you see on the screen right here is defined in this composable if you've done any modern Android development recently that I'm sure these apis will feel quite familiar to you because they're the exact same ones that you're used to from jetpack compose and if not you're in luck because the compose apis follow pretty much the same principles that all modern UI Frameworks follow their declarative apis with some sense of State Management and functions as little blocks that can be composed together to build more complex user interfaces in this example the greeting text and the show image variable which determines whether the image should be shown or not are stored in remembered mutable states which is just the compose Paradigm for storing State and then this little app uses layout Primitives like columns together with modifiers and other settings to lay out the whole application the button is responsible for actually modifying parts of our state and the animated visibility Clauses are responsible for doing that nice little animation that happens when I click the button so these apis are the exact same ones that are already being actively used in modern Android development to ship apps in fact the only part of this code that is compose multi-platform specific is the way that we read resources because we need to provide an implementation that works on both Android and iOS but that one is included with the compose multi-platform framework and as you can see it even reads XML based vector graphics just fine well this is all well and neat but it's of course still far from a useful app so I think it's time we bring in some actual data and some useful stuff to our app so since my idea was to show pictures of different birds in the app I actually prepared a small API it's nothing fancy and hosted on GitHub pages and really it just consists of one large Json file and in that Json file is an array of objects each object representing an image as well as an author and a category for that image that identifies the bird and then we can take this path and if we replace the pictures part with that path part we'll actually see the retrieved image so I'm thinking we move on and we bring some of this data right into our app but let's not go overboard and let's start simple and just show a single image from this API in our app now just like in Jetpack compose on Android in compose multi-platform we would use an image loader library to bring in images from the network image loading libraries range from very simple to very sophisticated and can introduce things like caching or asynchronous loading for your application a number of image loaded libraries already exist that also support compose multi-platform one of them would be compose image loader for example and the other one is camel which I believe stands for kotlin asynchronous media loader at least that's what the uh readme suggests for this project I'm going to use camel because it has a super simple API and quite sensible defaults so I don't actually need to make many changes to get started but of course you're welcome to use whichever one you like just make sure it supports compose multi-platform so first things first let's add a dependency to our project we can see that camel has a nice little dependency snippet down here that we can just copy uh we'll open the Gradle file that's part of our shared holder and just like in any other kotlin multi-platform project uh we can go to the common main dependency section and just add it here this library is kotlin multi-platform compatible which means I will be able to use all of its functionality on both platforms that we Target and most importantly in the common UI code that we're currently writing under the hood camel uses ktor to make the actual Network requests so we need to add a couple of dependencies for that one as well the readme file of camel sends me over to the engine section on ktor but in fact I first want to add my core dependency for K to our client so I'm going to add that one to my common block of dependencies I'm going to hard code the current version number 231 for K tour here but you're of course welcome to use anything from properties to version catalogs to organize the way that you manage your version numbers now for each specific Target we also need to to have an engine so that cater knows how to make the actual requests we have two Targets Android and Native so let's click through those thankfully these are also quite easy Snippets we have the ktor client Android dependency which we will just add to our Android main source set that's that and we will once again just replace the version number and we need to add the Darwin implementation which is for darwin-based operating systems such as Mac OS iOS and tvos which covers Us quite nicely we don't actually have any iOS dependencies yet so we need to open that dependencies block ourself but once we have that it's as simple as copy pasting and adding the correct version number now we can just quickly sync our project and get everything in great so now that this is set up we can actually already use camel and a switch to camel and loading an image from the network is actually quite quick instead of an image we'll use a camel image and instead of a painter resource we'll use an async painter resource which now takes a URL I can just go back to my API and grab myself one of those strings and grab myself this cute picture of a couple of pigeons and we'll just hard code that for the time being and let's try running this on iOS now if I click the hello world button it's indeed going to show me the image of the pigeons isn't that cute it's probably also a good time to take a second and add a proper content description uh just to make our things accessible okay if we try to run the same app on Android we're actually going to see something that might be a little bit surprising indeed the app runs fine but the moment we try to load an image nothing happens and that's because while on iOS we can always call into the network Android requires explicit Network permissions and with compose multi-platform projects that's no different but luckily we can just look up Android network permissions on a search engine of your choice and you will very quickly find the snippet that is required to add to your manifest the permissions for internet and access network state once we have those in our clipboard we can just move to our Android app go to the Android manifest file and add that one above the application block now after a quick restart we can once again press our button and we will see a wonderful cute little pigeon on our Android device as well neat so already with those minor changes we were able to use third-party composables in our application to pull in network images that's kind of cool but of course our API has a little bit more to offer than just a single pigeon image so let's go ahead and actually use the rest of this API consume that and actually parse out the Json Nets in here now whenever I have a task for Json parsing the first thing that comes into my mind is kotlin X serialization it's one of the libraries built by the kotlin team and it's fast it's Reflections and most important for us it is multi-platform so we can quickly set this one up and then we'll be able to read information from our API in no time and give our pigeon a couple of friends if we scroll down to the setup guide we will see that kotlin X serialization comes in two parts one of them is the Gradle plugin that one we can just add at the top of our build file and the second part is the runtime library for which you have to scroll down just a little bit and find it right here this one is a common Library so I can once again add that one to the common source set but the ideal situation would be for ketor which we're going to use to make our HTTP requests and kotlin X serialization to kind of click together and work together so maybe let's also add the integration for that part if I head on over to the documentation under the ktor clients there's a section called content negotiation and serialization there are two pieces we need to add to our build file one of them is the content negotiation itself so that one we can add right here to the common block and we'll once again provide the correct version number as well as the integration for serialization with kotlin axialization that's the second line and we'll add that one as well and just quickly hard code the version number so we can sync this one again as well very good so with that out of the way we can actually move on and start writing the business logic that is responsible for making that Network request and parsing out the individual Json elements first step in that case for me is always to create a model so that I have a typesafe representation of whatever my Json information contains so let me just add a new model directory so the cool thing about kotlin X serialization is that you can just use very simple data class definitions annotate them with ad serializable and the compiler plugin and Library will figure out the rest for you pretty much but I am see so I like installing the json2 kotlin class plugin and that one actually provides me with a new Option when I create a new file which is kotlin data class file from Json so what I can do now is I can go back to my API I can grab myself one of these object definitions I can give it a class name like bird image and I can just hit generate we can add this one to git of course and we already get the data class now all that's left is annotating this one with at serializable from the kotleneck serialization package and serialization works that's all there is to it we can actually write a short snippet of code to convince ourselves that what I just said actually works we're going to ignore app architecture for just a moment but don't worry we'll get back to that in just a second so we can just write a small function called suspend fun get images and that one is supposed to return a list of bird images to make the actual HTTP request we of course need an HTTP client so we construct one from the K tour Constructor function and since we want to integrate it with serialization we need to do a little bit of configuration luckily the cater DSL for setting up an HTTP client is very simple we can just use install and then install for example the content negotiation plugin and instructor to use Json and just like that we have an HTTP client that knows how to do content negotiation and deserialize Json using kotlin X serialization so now we can actually make a get request so we can say our images that we're getting is HTTP client dot get and this one can just take a URL we are going to hard code this one for the time being let me slightly reformat this code and then out of this one we want to get the body but we want to have it in a specific type and here we essentially tell kotlin X serialization how it should attempt to deserialize it if we look at the format once more we'll realize this is an array or a list of these kind of objects so we'll just say give us a list of bird images and that's exactly our return type as well so we can just return this value in fact Android Studio rightfully tells us that we could already inline this variable and probably even make this an expression body function but we'll keep it simple for now because I don't know how this function will change just yet so to convince ourselves that what I just said actually works it would be nice to actually run this kind of function however we have to be a little bit careful even with this proof of concept because generally we don't call arbitrary business logic functions inside composables and why is that well because the compose framework itself actually reserves the right to call your composable functions at pretty much any time like for example when State changes and it has to re-render but luckily there is a mechanism to execute side effects which is essentially what we're doing here so we can use the launched effect Builder function uh to specify our call to get images launch defect does take one parameter called the key which is the variable that has to change in order for the launched effect to rerun in our case we don't really want to rerun our launched effect ever so we can just buy convention use unit which is a constant that never changes so this will only ever be executed once so let's rerun this application and take a close look at the logcat output and if we take a look poking around just a little bit we can see that indeed we have printed a number of bird images with their author category and path and there's a whole bunch of them so it seems like we have indeed properly parsed the Json that we've received from our tiny API now from here we could of course go on and try to wire all of this together with the top level function and just having an HTTP client kind of floating around in the middle of the file and it would probably work because this is a tiny app but in reality you probably structure your application in some kind of architectural pattern where you separate the logic that is responsible for your UI and your business logic those are patterns like mvvm mvi Bloc all of those for example they all serve to separate and encapsulate your business logic and your UI logic from each other there are libraries like pre-compose which provides multi-platform navigation and view models kind of inspired by the jetpack lifecycle and there's Voyager multi-platform Library built for compose that also comes with a ton of different features one of the more simple and easy to use libraries is Moco mvvm because granted even with some architecture in place this app is going to stay quite simple we're not going to have any fancy navigation logic or any other bells and whistles so I'll just use Moco mvvm for this example because it's a simple implementation of the view model pattern but if you're looking to build a more complex app you're more than welcome to check out all the other libraries that are out there as well they all come with their own spins and twists and oftentimes provide a really unique and useful approach to structuring your application so to use Moco mvvm I need to add two dependencies to my application I need the core dependency which is just an API dependency and I need the compose multi-platform specific implementation as well which is a second API dependency let me just do another quick sync and add these to our project great with those in place we can create a new class called Birds view model which is going to encapsulate the state and business Logic for our application the first thing I'll do is make sure that this class indeed inherits from viewmodel this gives us two important parts first of all it gives us access to a view model scope which we can use to launch and manage co-routines and it also provides us life cycle Awareness on Android so that we can do things like release our resources when the view model is no longer needed great so let's move our state management into the view model instead of just having it be spread around in our app KT file so typical pattern for a view model is that it defines some kind of class that is responsible for representing the UI state so I would call mine for example Birds UI state in our case we could start very simply and say that well the images that we've loaded which is a list of bird image is our UI State and indeed when this starts out this is just an empty list then the view model goes ahead and exposes this UI state in a way that is observable from the view or the UI side of things one modern approach to do that is to do it via a state flow so we can define a private field called underscore UI state which happens to be a mutable State flow and we'll import this function off of birds UI State and by default this is just a virtue I state with no function invocations let me close the side panel as well then we somehow need to expose this state flow in a read-only fashion to any consumers of this bird's view model let me give us some extra space here so we'll just specify a public field called UI state which is on our underscore UI State as a state flow so this way only members of The Birds view model can actually update the state but external consumers like our compose based user interface can still read the UI State as it gets changed and indeed we are going to collect this exact UI State variable later on from the compose site but before we do that let's finish moving our code into the view model so we have this HTTP client which we no longer need to be a top level declaration instead that one can live inside the Birds view model likewise the suspend function get images can also be a part of the bird's view model and let's make sure that we get all of our Imports in here as well properly of course the only part of our system that should actually have access to this specific HTTP client is the birds view model so we can make this one private as well and when we think about it we should probably also make the get images function private and the reason for that is while this get images function at least in its current signature returns a list of bird images but remember that the communication Paradigm for this view model is that there might be some kind of external impulse and in response to that impulse the state flow gets changed and then any external Observer that collects this UI state state flow will then be notified of any kind of changes so rather than returning any kind of values directly we would rather update the internal state of our view model and indeed that is something that we can do by providing a function called update images and that one is explicitly public and the task that this one has is to get the new images and then update the UI state so that the changes propagate throughout our system however get images is a suspend function because ktor makes its HTTP requests on top of the co-routines Machinery so as not to block any threads but just to suspend them however if you look into the developer guides for Android developers one of the best practices that are mentioned is that the view model should create core routines so something that should be avoided is exposing suspend functions directly and rather than that calling suspending functions within the view model scope using one of the co-routine Builder functions as the documentation lays out this makes your business logic easier to test because it means your view model objects can be unit tested so we can follow this best practice quite easily because MoCo mvvm just like pretty much all the other view model implementations out there for multi-platform provides us with access to a view model scope which is a CO routine scope that is life cycle aware on Android inside this launch block we can now grab our images by calling the get images function which was private and then updating our underscore UI state which is the mutable variant of our flow so we can just say UI state DOT update and then inside this Lambda we receive the original UI State and we can just make a copy of the original state but we will just set the images to be the images that we have received from our get images function now this view model really can't do much unless the update images function is actually be called so it might make sense to call update images once in its initializer that means when this view model is first instantiated we will also retrieve a new copy of all the images that our API has to offer and of course later on in the life cycle of our application if the user presses like a refresh button or something like that that we might add to the UI then that would just go through and also trigger the update images function in here which in turn would then at some point update the UI State while we're already talking about what's happening when this view model is created we might as well also tackle the part where this view model is destroyed for example when it's no longer needed for that we can override the on clear it method whereas good platform citizens were expected to release any kind of resources that we still have in our case we would close the HTTP client so as to release any kind of threads or other health resources that might still be in there great right so we have a basic view model setup now it's time for us to go back to our actual UI and wire things back together because now our API offers us a nice list of different images so I think it's time we show them in a nice little grid so first things first I think it would be useful for us to get rid of all the code that we've been using to try things out so far because that was mainly related to just our little example and we'll start from scratch we will make sure that we have a separate composable function which I'll just call Birds page which then represents all the composables that are somehow associated with both the view model that we've just built in fact the birds page should get the bird's view model as a view model parameter so that it can access these things now in our app composable which is kind of the root composable of our whole application we want to call the birds page composable and for that we of course need an instance for the view model Moko mvvm has a convenience function called get view model so we could grab ourselves a bird's view model by calling get view model but of course if you're using any other kind of mvvm framework they usually have a similar compose multi-platform integration for you to use the first parameter is once again a key that determines when this view model should be recreated in our simple case there really never is any such situation so we can just pass unit again the second parameter is a factory function that creates that view model luckily there is a view model Factory Lambda included in which we can just pass a call to the actual Constructor and that's it now we can grab that birth view model and pass it down to our Birds page and already with that we have the view model available inside of our Birds page composable and just like I mentioned earlier we can now actually observe it in this case we collect its state so we'll say our UI state is looking at the view models UI state if we collect it as state if we're using the equal sign you can see that the UI state is indeed a state object that wraps the bird by state there's a little bit of syntactic sugar we can use buy instead which unwraps that state object for us and provides us with calls directly to birds UI state which is a little nicer great so from here we can actually start working on our display logic and start building some layouts because it's a framework that's already out there and being used it also comes with a number of layout Primitives that you can use straight out of the box so I have a couple of considerations let's start easy when the images are first loaded I would like to show them with some kind of animation and this part we've already kind of seen in the beginning in the template we can use the animated visibility API and because compose multi-platforms modifiers and composables are exactly the same ones as the ones on Jetpack compose on Android we can actually just have a quick look at their documentation right here and take a look at how animated visibility is used and we'll actually find out that it only takes a single parameter which determines on whether the element should be visible or not and then just takes a block of composables that specify the content and of course there's a bunch of customizations that we could do but that we're not gonna touch today so if we specify ourselves some animated visibility it becomes clear that whether we can show something or not depends on whether our UI State actually has some kind of elements so as long as the images list in the UI state is not empty we want to show whatever composables are in here now we have a lot of images to show so I want to show them in a visually pleasing manner so probably a grid and I also want to show them with performance in mind so ideally I would like to Lazy load the images as they come onto the screen rather than pre-loading everything and you know filling up my memory and drawing all that bandwidth now thankfully in compose multi-platform we have a lazy vertical grid that pretty much perfectly fits this use case and it also comes with a bunch of customization options that we can make use of we can hit command P to see a couple of the different parameters that this one accepts and we can start filling them in well I know how many columns I want uh uh pretty much I just want to have a fixed number which is just two of them uh uh I kind of want to have some consistent spacing between them so I'll provide a horizontal Arrangement which is an arrangement spaced by 5dp and we gotta make sure that we import that extension property as well I probably also need a vertical Arrangement and to keep it consistent we'll do the same kind of spacing with 5dp as well so this defines the spacing between the elements but I also want to make sure that on the sides we get the padding as well so when I specify the general modifier for this element I first make sure that this fills the maximum available size but I also want to make sure that it provides a horizontal padding of 5 DP as well and then the last parameter we need to provide is the content so the content is everything that's supposed to be rendered inside this lazy grid we already have a collection of items that we want to render and we do that precisely using the items function specifically the overload that uses the list and this one we can just feed the UI State images you can kind of think of the items Clause as somewhat of like a map function so inside the Lambda that follows the items declaration you specify how each individual item in the lazy vertical grid should actually be represented visually in our case I'm thinking we create a custom new composable called bird image cell which just takes the definition of that bird image and then renders it out great and we can actually just move on to that bird image cell next which once again we specify as a composable function we'll call it bird image cell and we know that it takes an image which is our bird image now this code is kind of a repeat exercise of what we already had in the beginning it's pretty much just a camel image which takes an async painter which Source but this time instead of hard coding the entire URL we only have to hard code the part that kind of defines our API endpoint and then everything after that is defined by the images path and of course we will once again provide some kind of image description uh which could for example be the uh category uh concatenated with the images author because I want to make sure that this image looks good no matter what the input is we can make a couple of smaller adjustments for example we can set the content scale to be contentscale.crop so the image gets cropped to the right aspect ratio and then using the modifier we can indeed say we want the element to fill the maximum available width given an aspect ratio of one so constraining it to a square great so with that we have reached a point where our application should compile and work just fine again so let's run it here and indeed on Android it runs nice and well and we can see that as we scroll new images are loaded in at the bottom and we see all the images that we have available in our API and here's the magical part we can run the same app on iOS as well and it works just as nicely with the images coming in and us being able to scroll up and down just like we defined it in our apps logic both the UI once and then used it twice and all in 100 kotlin that's already pretty good if you ask me alright so I'm already enjoying scrolling through these uh images of these Majestic Birds but I would love for the app to be slightly more interactive still and if we look back into our API we'll actually realize that we have just the information for that which is the category so with that why don't we add a couple of buttons to our app that allow the user to filter which birds they would like to see and to do that let's first actually build out a little bit of business logic which of course means moving over to our view model this specifically there's two things we need to do we need to add the information about the categories and the selected category to our UI State class and we also need to be able to somehow react when the user selects one of the available categories okay so an interesting observation is that we actually don't need to manually set the categories we can derive them from the list of bird images that we have so we can just say that the categories is our images mapped to their categories and then exposed as a set because these are distinct however what we do need to store is which of the categories our users actually selected so we'll provide a selected category field which is just a potentially null string which by default is null because you probably won't have anything selected so once a user has actually selected a category we can derive one more field which is the list of selected images so we can just say Val selected images and that's our images but filtered where it's category equals the selected category so that's our update for the UI state with the information that we need to store and information that we can derive however the view model still needs to be able to react to the event of the user selecting a category and for that we can just provide a short little function called select category this one is of course public because it will be called from the UI and all this really does is it takes our UI State and it updates it and the only change that it makes is it takes the selected category and sets it to whatever category was just selected by the user and that is all there is to it thanks to the magic of State flows this is going to automatically propagate properly throughout the app so now that we have this information exposed and we also have a way of reacting to the selection of a category we can actually go back to our UI and start adding some category buttons so the layout I'm thinking about is buttons on top and then the grid below it so essentially what I'm looking at is a column layout on the top because we have a column of multiple elements and then inside for the button specifically we have a row that has a number of individual buttons kind of side by side so let's introduce this kind of organization for our Birds page composable we'll wrap the whole part in a column so the closing brace for this goes after our animated visibility and we want to make sure that this column does indeed fill the maximum size of the screen because it's our whole application uh we'll also set the horizontal alignment uh to Center things horizontally that's just going to make things hopefully a little bit nicer and then the vertical Arrangement uh will also provide some centering just to keep things nice and packed now if we were to rerun this application I can just do that very quickly on Android here just to demonstrate it you will see that nothing much has changed because we've just wrapped it in a column that has the full size so let's actually add the buttons and those are organized in a row let me just import that function for this row we want to make sure that it does fill the max width we want to provide the same kind of 5 DP padding which we've used all throughout our whole application and since we have multiple elements it's probably interesting for us to specify a horizontal Arrangement specifically I'd like to do a 5dp spacing again between the individual buttons and inside this row we just want one button for each category and in composed that couldn't be simpler we can just do a for Loop so we can just say for category in UIC dot categories and then for each one of those we'll specify a button that button mainly does two things it has an on click Handler that will get executed once the user actually clicks and in the content this will just specify some text which is just the category title unfortunately we've already laid all the groundwork for the on click Handler so we can just go to our view model and say select category named the same as the button okay so we could already run this but let's make sure that our grid that we have at the bottom is actually also aware of the selected images because right now it's just showing all of them so we can just replace the animated visibility conditional as well as the content down here with selected images instead of the regular images and now it will use only those images that have been filtered by the category so let's rerun our application and let's do that on both platforms and just like that both on Android and on iOS we have category selectors we can click them and they will switch out the images if we scroll around we can see only the images from a specific category so if I grab myself for example the Eagles here you can also see just the Eagles or just the owls or just the pigeons isn't that neat now however I'll admit that our app could use a little bit of extra polish these button elements that we have here by default come from the compose material design components Library which on the one hand first of all might look a little bit foreign on iOS devices but they also just kind of don't mesh that nicely with the rest of our minimalist and very like blocky look now of course what you can always do is you could redesign these components using layout Primitives like boxes or use some kind of third-party UI components library that just have a different style but at least for this simple app we can take an easier route and we can do a couple of customizations because we can do things like change the material theme as well as change the parameters for the individual elements that we have in our app so let's kind of look into what that would look like the first thing I want to do is Define a custom theme for our application and the general pattern that themes for compose follow is that they are composable functions themselves for example we could call this one the Bird app Theme and essentially they are kind of higher order components which means they take content which themselves is a composable function that doesn't return anything and then they would just call this content usually we use the material theme as the base for our theme so we'll just use material theme and then content and we can switch out the invocation of material theme down here with our Bird app now we have a convenient and encapsulated point where we can adjust this material theme and where we can adjust things like colors typography or shapes the adjustments that I want to make for our app here are in the color and the shapes first of all I want to make sure that our buttons are in a sleek black so I can just go in and set my colors to be the original material theme colors but will make them a copy and we'll make sure that the primary color is indeed black and now that they're in a sleek black I also want to make sure that they use very sharp edges so that they're not kind of rounded buttons but instead that they're just rectangles so I can specify shapes as the original material theme shapes but we'll copy those we can see that there's three different shapes here small medium and large and for all of them I'm going to set an absolute cut Corner shape with a size of 0dp which essentially says this is a rectangle cut off the edges but only for zero DP so don't cut them at all which just means they're rectangles we're going to have this for each one of these variants I think I can probably add the names to the call arguments here to also just make that clear in code now when we you rerun this we can see that our buttons are nice and sharp properly rectangular but the buttons aren't really taking up all the space yet that we have available which makes them look kind of wonky so we can fix that by taking a look at the individual buttons and specifying a modifier in this case we just set them to be an aspect ratio of 1.0 which means they are square we'll make sure that they fill the max size that is available to them and will make sure that all of them use the same amount of weight so that they kind of have an equal distribution between them and just like that we got some nice big squares that sit at the top and mesh quite nicely with all the paddings to provide us with a quite uniform design if you're squinting really hard and looking quite detailed you'll see that there's still the tiniest of Shadows under the buttons which maybe doesn't look exactly the same as the rest of our rectangles we can indeed even address that part if we would like to let me just reformat that for a second because there is indeed an elevation property on our buttons and we could for example set those to be the button defaults elevation but we will set the default elevation to zero DP and the focused elevation to also be zero DP and with those properties reset no matter if we run it on iOS or on Android I'm just going to switch back and forth here uh you will see that even when we take a close look at the buttons there are no shadows and they look quite nice and cubic and there we have it our little bird app is done and it's looking nice sharp and rectangular and most importantly it works great on both Android and iOS even though all the code we've written was purely kotlin multi-platform and shared between the platforms now there's obviously a bunch of different things that we could still do to improve this app instead of just doing a bunch of filtering we could use a proper navigation Library like Voyager pre-compose or decompose that would also allow us to have separate views that show us for example specific detailed information for each one of those Bird pictures we could use a repository pattern so instead of having the view model make the actual Network requests we could Outsource that to a repository and that would also give us a layer where we could do things like play with persistence for example or introduce some kind of caching layer for example of course we could also further decompose our composables here and instead for example provide a custom composable just for our button or maybe for the selector for the different individual categories just to kind of break things up into reusable smaller pieces and I am sure you already you have about a dozen ideas of how this code could be changed improved or modified to suit your needs even better but I think we have done enough for one session and looking back at it it's been quite a lot we've seen how to just set up a compose multi-platform project from scratch how to briefly validate that our development environment is indeed ready for compose multi-platform how we can use third-party libraries and composables like the Camel image loader like kotlin X serialization together with ketor and we've essentially built a whole application that can interact with the outside world and still write pretty minimal and nice kotlin code we've also seen how we could use something like an mvvm Library together with State flows to separate out the business logic from the UI logic that we have here and we've used a couple of the built-in components that are available not just in Jetpack compose and Android but through the power of compulsed multi-platform on iOS and other platforms as well like for example lazy grids and we designed our user interface to be neat and cross-platform with even a smidge of customization built in as well I do hope you enjoyed this journey through composed multi-platform with me where we take a small project from start to finish and maybe you've learned something new as mentioned previously in the beginning compose multi-platform support for iOS is currently in Alpha so we're really excited to hear you feedback and see you experiment with it and see what's possible if you want to learn more or play around make sure you go to jb.gg compose or just look for compose multi-platform where you can find all the information on our wonderful declarative UI framework that works on iOS Android and even desktop in the web so thank you so much for tuning in and I do hope that your compose multi-platform story has only just begun alright see you in a future video Everybody Take Care thank you [Music]
Info
Channel: Kotlin by JetBrains
Views: 18,087
Rating: undefined out of 5
Keywords: Kotlin, Kotlin Programming Language, Kotlin Multiplatform Mobile, Kotlin Android, Kotlin for server-side, Kotlin/JS, Programming language, Programming, Android development, Kotlin Multiplatform, Kotlin iOS, Compose, jetpack compose, KMP, Shared ui Kotlin, Kotlin UI, Compose UI, Compose Kotlin, Compose Multiplatform, Compose Multiplatform Tutorial, Compose tutorial, cross platform, Kotlin Multiplatform Tutorial
Id: 5_W5YKPShZ4
Channel Id: undefined
Length: 50min 41sec (3041 seconds)
Published: Thu Jul 27 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.