LongCut logo

The Compose Multiplatform Crash Course for 2025 - Build a Clean Code Book App

By Philipp Lackner

Summary

Topics Covered

  • Feature Boundaries Define Reusability
  • Domain Insulates Core Logic
  • Ktor 3.0 Enables Server-Pushed Updates
  • Build UI First with Dummy Data
  • Custom Serializers Fix Bad APIs

Full Transcript

hey guys and welcome back to another video or should I rather say course because that is what it is as you can probably already tell from the video length I decided to really give something back to all of you who who

have been supporting this channel for for such a long while now by just also giving you a full course like this for completely free which will specifically be about compose multiplatform because I also know that this is currently a very

Hot Topic in the community and that there are not that many resources about compos on C multiplatform yet especially when it comes to to building building full apps from scratch which is why I decided to change that so no matter if

you've never worked with compost or coton multiplatform before and now want to get your hands dirty as an Android developer maybe or if you've already tried around a little bit this video will be for you specifically we will

build an app that involves things like talking to a remote API in compos multiplatform uh that involves a local database with room we'll build an app that involves animations that that

involves navigation transitions in our shared code in compos multiplatform and that works for three platforms so Android iOS and desktop in the first step let me just show you what we have

built because it will be an app called book pedia so a book searching app as you can see we have some default search here set to cotlin or whatever you prefer we can then scroll through dozens

of books you can see not all of these have covers uh these come from an API called open Library which does not require any form of of authentication so you can just talk to it you don't need to register for an API key or so which

is uh really great but when we done tap on a book we get to the book detail screen we get a nice blur effect here for the background um we can actually mark it as favorite as you can see which I already did for this cotton

programming book we do see the rating the amount of pages the languages and if there is a description that the API offers it would also display here if we go back and actually take a look at our favorites we can also swipe here you can

see there is our just um marked book where we um toggled the favorite option but we could also search for something completely different like Harry Potter

for example it searches automatically fetches all those books from an API and there we go here are all of those Harry Potter books if we click on one of these then there we go again detailed screen

you can see Harry Potter supports a lot of different languages and here we also see an actual description for this book we can then toggle it as a favorite which will be saved in a local database using room which supports cotland

multiplatform already and then in our favorites we can already see our Harry Potter book since it's caught on multiplatform this whole thing of of course also works on iOS as you can see here I am on an iPhone simulator um in

order to actually run this on an iPhone simulator you need a Mac OS but even if you are on Windows here and you want to learn Cotler multiplatform then feel free to follow through this video um

because you will put pretty much all the code of this app inside of Android Studio inside of Cartland files you just won't be able to run any iOS code on an iOS device if you are a Macos you can

also launch the IOS app like here you can see looks pretty identical to Android we also have favorites and we can also search for something else like Harry Potter again it will search and

then we will see our Harry Potter books with a a few nice animations as you can see we can click on one and there we go here we have our book with the description and to also show you the

desktop variant as you can see this also works this will work on Mac OS this will work on Windows so on all viable desktop operating systems and here we can also expand this window and then just um scroll through this list I also have to

remember that on desktop we actually scroll with a mouse wheel and not by dragging but with a mouse wheel it works perfectly fine we can also click on a cotland book mark this as a favorite

here for example and then take a look at favorites to actually see this book that we just marked as a favorite so this is the multiplatform app that you will learn from scratch except a few utility classes that I will show you in a moment

by following this single course so what do you need to do to actually follow through this course now the first thing you need to do is to actually clone or download the initial code that I share

Down Below in my GitHub link so you can either directly clone this repository into your IDE or you can just download it as a zip extract it and then open in Android Studio in this initial project I've already included the dependencies

that we will use so you will find this here in under Gradle lips. versions in

our version catalog so here you'll find all those dependency versions that we will use in this course so you can see cotlin cortines you can see coton civilization navigation uh we are going to use coin as a dependency injection

Library we're going to use use Kor 3.0 here as a networking client we'll use coil 3 as um an image loading library and image caching Library so we also keep offline access to all those book

images and book covers KSP is for code generation for things like room then we have SQ light uh which is used in combination with the room in order to just Implement a local database and down

here you will find all those Library versions um for those different libraries that I've just talked about down here we have a few plugins you can see room and KP and uh serialization

comes with separate plugins and here we just uh summarized a few in in in bundles so we can add those dependency versions together with a single uh single call then in our main build grad

file which you will find in this compos app module and in a moment I will also uh say something about what all those modules here are what kind of code we should put in them so in case you new to C multiplatform and this confuses you no

worries I will go over this in a moment but let me just complete what is contained in this initial setup so here in build. Gradle this is the the main

in build. Gradle this is the the main Gradle file in which we can configure our builds you can see I've added a room schema directory so where our um database schemas are actually stored this is a requirement from room I've

added Android specific libraries so the Android specific coin Imports here common main dependencies is for our shared dependencies that belong in our sheared code so things like uh coin for

our shared code things like coil ktool and so on we have one dependency that is specific for desktop that I've added which is O hgp and the DAR client for

Native so for mechos iOS and so on I'm also say something more about this in a moment but the last thing that I've added is something you will find in common Main cotland and here you can

already see a cool package in which there's a domain a presentation package and I've just added a few very needed utility classes where I decided to not create these from scratch in this video because I have dedicated YouTube videos

about these classes for example this result rapper class which is really just used to uh have a class that WPS around for example a network result we load some kind of data from from um from from

the network from our open Library API if a network was successful then we will just wrap this in a result. success we

pass our data so for example the successfully loaded book and if something failed about that Network call we will instead return result. error

with a specific error code and then here we just have a few utility functions to map a result um to convert this to an empty result and to just have some nice listeners for on success and on error but for these utility classes I will

also link reference to my videos below so in case you're curious how I get to this code then feel free to watch these explainer videos just about these topics and of course I'll explain what happens once we get to the part where we

actually make use of these classes next up you will find a UI text class here which is also just purely utility which allows us to um create references to string resources across our app also in

view models in any in any file pretty much without requiring the context which is usually needed on Android to unwrap string resources but that's also something I will say say more about when we get to using these classes and lastly

you will find a color KT file just with four colors that make up our fix color theme Here everything else will really be built from scratch in this course next up I want to say something about

the architecture that we will use so you already have an impression of how we will structure this app in the end I will intentionally structure this in a way that really scales that is meant for larger apps even though this is not

strictly um a large scale app if we just have two screens here but since this is typically what people want to learn um how I would also structure a larger app I want to show you this approach here um so specifically we will have three

layers presentation domain and data and these layers we have for every single feature since again this is a rather Simple app we will only have a single feature so whenever you have a list and

a detailed screen that is pretty much always part of the same feature and if you maybe have problems deciding what would make up a feature in an app and what belongs together in a feature so which multiple screens make up a feature

what help me to always think about is would I be able to extract a certain screen out of this app and reuse that in another app without requiring anything else from the app I extracted it out of

so when you're for example thinking about what this book list screen here what this be a full feature well not really because when you click on a book then in pretty much every case no matter

where you use this uh book list screen you would want to display some sort of details so the detail screen and the book list screen are actually pretty closely cut coupled together however if you then take this booklet screen

together with this book detail screen here these in combination you could very well reuse in a different app imagine you would have a different app which would also require a login system where

you would have to log in in order to see these um this list of books the the details about that then both the authentication feature so with the login and registration screen and the book

feature would be their own features because the authentication feature does not really need to know where you navigate after logging in this could be a social network app this could be a book list app this could be any type of

app that requires a login and the same counts for the book feature here because it doesn't need to know where a user comes from when they see this book list screen and detail screen together but if you use a book list screen somewhere

then it certainly also comes with a book detail screen so I always um try to think about how self-contained a single or a set of screens would be which helps you to decide uh what what would make up

a feature or not but that's not really the idea behind this architecture that I typically use for apps that we will have uh one root package per feature so in

this case as I said we will have a book feature which bundles a book list and book detail screen and inside this book feature we will then split this based on layers so we have presentation which

contains all the references relating to UI for this particular feature so that means the actual UI components that means the view models which have the responsibility to actually update State

and keep State we then have a data layer for every feature which oops that was the wrong place a data layer inside the book feature which just contains anything regarding data so our Network

client our repository our local database if you would use some sort of um external library that can be seen as an API this would also go in data if you would talk to system services like if

you would fetch location coordinates if you would uh talk to the device sensors or so all that where your app is actually talking to some kind of external service external API so it has

to retrieve data from from a place that is not coming from your directly then that goes in data and the last layer we will use is the domain layer domain

layer in this architecture is the innermost layer so the direction is actually like this that we have our presentation layer with an arrow towards

domain and then an arrow from data to domain so that's why domain is called the innermost layer since both presentation and data are allowed to access references are allowed to access

classes and files and functions from domain but not the other way around so domain is not allowed oops uh to actually access something from data directly so an HTTP client um a reference to a database and it's also

not allowed to access something directly from presentation for example a view model or a composable the reason behind this architecture and why the directions here are really as they are is that we want to keep frequently changing code

not have impact on less frequently changing code and if you think of about what is frequently changing code um well those are so-called implementation details so really how your app achieves

something how your app retries data is that with an hgp client is that with a local database is that with websockets and things like how does data communication actually work is this with

Json is this with XML how do you actually transform Json with WID Library all those are implementation details because they describe how you achieve specific results how you achieve that

data in the end is displayed in the app all these implementation details are part of your data layer and these are just things that change much more frequently in the real world than what your app actually does so what problem

it solves because typically in the real world the changes you as a developer commonly face are things like um hey the back and team actually changed adjacent field name please update that in the app

things like oh this HTP library is actually not ideal for our use case let's migrate to another one things like our database schema needs to change let's also fix that in the these are very frequent changes and with this

architecture we achieve that these frequent changes won't have an impact on the rest of our app now the domain layer contains really the core of our app the the core rules and the core logic um some people call that business logic so

if someone would really just have access to your domain layer they should be able to tell which problem your app solves on the one hand with the business logic they will see in the domain layer and on the other hand with the business models

or the domain models so those are typically just data classes that bundle some data in codin like in our case a book model in general an Android app has no idea what a book is because that's something that's a concept from The Real

World but in order to give our app context on what a book is we would make that a book model and since it's such a fundamental Concept in a book app this would be a domain model if you had a social network app for example then you

would have models like um a post a profile um I don't know a common maybe because those are all Concepts that are not immediately clear when learning about your app and the most important rule for the domain layer is that it's

really completely isolated we don't want to use any third party libraries in there um we don't want to have any references to our other layers as you can see with these arrows it should really just contain pure cotton code and

lastly we have our presentation layer so as I said um this contains our UI components actual composes and things like UI models sometimes if you um have a very UI specific way of displaying

something that just differs a lot from the domain model for example if you heavily need to make use of formated dates a form date in the end is only useful in our UI layer so um it can make sense to have a separate model for that

uh the presentation layer contains view models so the logic how and in which case UI updates so this is really our um our layered architecture here and the reason why we have these feature packages and these are then split into

these different layers is that we really keep our features self-contained again a typical mistake a lot of people make um especially when it comes to scaling apps or apps that need to scale is that they

have uh these layers as root packages so you have a data domain presentation layer inside of your app root directory and then in there they put all their features code but this would not be

reusable at all because you would have to reuse the whole presentation layer of your app the whole domain layer of your app from all features and there would rarely be a use case for that so I really always structure my apps in a way

um that I could also migrate the structure as it is to um a multimodule architecture which I never started with by the way but if I would want to migrate to a multimodule architecture at

some point I would really just need to make single modules of these layers and of pretty much any package inside of um these root packages you've also noticed that we have a core package already um

so core is also structured in the same way that we have presentation domain and later on also data um this will just contain the code that is sheared between multiple features so typically utility or if multiple features would need to

access the same model class um you would also put this in core will play a smaller role here in in this course since we only have one feature um if you want to learn how this works with many features then um I will also link down a

course of mine the Android Essentials course where I really build a larger scale multimodule running Tracker app okay um so that's it about the architecture the last thing we need to talk about before we get to the actual

source code is that we also want to celebrate the release of Kor version 3.0 so just very frequently Kor our favorite networking client that we can use for C

multiplatform that we can use for Android was updated to um 3.0 so there was a breaking release a breaking new change and that's also why jet brains is

the sponsor of today's video so thank you jet brains so that's of course also why we want to use kto version 3.0 here as you can see the changes of this version are on the one hand that we get

a better performance according to Jet brains the performance of Kor 3.0 is 30 to 90% better than for Kor 2.x this was

achieved mainly because they switched to the cotton X iio Library which is a a rather low-level API so how kro manages all those data streams in the background this is also why this is a breaking

change so why uh the the first digit of this version actually increased because if you are using these low-level apis in your app uh then this is a breaking change so you might need to fix your project if you're just using konal by

making hgp calls and without actually working with bite streams and all that stuff uh then this should not be a breaking release for you then what also supported is server sent events now so server sent events are pretty much the

the opposite of an HTTP request because with HTP request we have a client that pulls the server for new information so we ask okay hey server can you give me this information that I'm asking for can you give me this list of books while

with server send events it's the opposite way there we have a one-way connection from the server to the client so instead of the client pulling the server the server is able to send new updates to the server in form of events

so instead of the the client always asking for data with server sent events the server will send new data to the client in form of events uh when it's available if you want to work with that that is a separate dependency from Kor

that you can add now but then it will work out of the box so you need to set it up but Kor does support that now then the update to 3.0 brings support for web assembly so if you're working with

Carton multiplatform and web assembly so building websites with cotland then you can now use Kor as a networking client for that um which is of course very cool since when when working with websites we pretty much always need a web client for

that and lastly a very cool change regarding Kor server so you can also use Kor as a server site for workk which I also really like is that this now offers

so-called csrf protection so csrf is a type of security vulnerability where an attacker kind of tricks an authenticated user into making a request with a valid

token with a with a valid authentication and Kor server now allows you to generate a token which really protects your users against this type of attack and what Kor server now also offers is serving static zip files so that means

um you can um you can provide a zip file at a specific endpoint and will provide the contents of that zip file as direct resources you can access from the client so I think this is pretty exciting um

especially where all this all these changes with coton multiplatform compose um navigation compose Kor all those new or new products from the jet PR ecosystem will lead in future I really

hope that in future we really have a viable and also established a way to build multiplatform apps to build apps and software for for multiple platforms from a single code base and using

cotland that would be super cool all right let's start with the actual coding part here so I assume that you've now cloned the initial Branch from myab repository from the link down below now as the latest point where you can do

that in case you're creating a new composed multiplatform project you can do this here on this km. jets.com um

website where you can generate such a project since there is no integrated wizard yet in an studio um to generate a composed multi platform project that's only available for KMP without sharing

the UI but as I said in this initial branch I already included everything uh that we will start off with here and if we open such a composed multiplatform project this is how the structure will look like so you will have this compose

app module um as your root module this will be your application module which you can see with this green square here so kind of the entry point for your application and it will then consist of

an Android main module a common main module a desktop main module and an iOS main module at least if you selected those platforms um and you want to Target these and our goal with compose

and C the multiplatform is now to put as much code as possible in this common main module because this will be the module with our files and classes and functions of that code that we want to

share between all platforms so if we open this app KT file here for example then you can see this is already a composable with some initial logic where you can toggle an image this is just included in this initial template from

this project generator and since we're using compos multiplatform that allows us to also share our compos UI which is why that um goes into of our common main source or common main module it is but in addition to that we sometimes might

also want to have certain implementations that are specific to to one of these platforms only or where a specific shared implementation just differs on each platform so typical

example would be uh creating a database client while the logic to interact with a database is pretty much the same on desktop iOS Android the queries are the same it's on all platforms it's SQL in

the end but creating the database client just works a little bit differently on each platform so on Android we have this concept called the context which is an Android specific concept but the context

is needed to construct such a database client on Android but on iOS we don't have such contexts and we can create a database client in a different way so this is what these other main sour sets are the main modules um are here for so

that we can also have a place um to implement platform specific logic that means in Android main you have full access to all kinds of Android dependencies to the Android operating system to Java code and all that that

would normally work in a native Android app same for iOS just for the iOS specific dependencies um on desktop um it's very similar as an Android just that we um can't use Android dependencies but we can certainly use cotlin on Java dependencies and in this

common main module we have the limitation that we can only use dependencies that support carton multiplatform but nowadays luckily for pretty much any common thing um there's

already a library that supports carton multiplatform so I would say we get started with the absolute core of our apps so our domain layer because you first of all need to of course Define um what kind of problem our app actually

solves in our case um to be able to search through some books The Next Step would then be to start implementing the UI layer for our first feature in this case we we have one feature as I said um

so for our book feature but the UI layer let's let's start off with just defining that for the first screen so for the book list screen and then after that as a last thing we get to the data layer to actually be a able to load books from

the um from our API and then also save these locally I always start off in exactly that order because on the one hand building our UI typically involves our domain models so if you want to show

a list of books then we need a book model somehow so we want to start with this book model but then we can freely implement the UI without actually being blocked by anything else and we can

already test that whole thing because if we would start off with our data layer and first of all start implementing the HTP interaction um if you would Implement our database interaction our database client then we could all do

that but if our data layer would be finished we we wouldn't have no real use with that we couldn't test that because um there is no way for us to actually interact with our we since we are missing the UI at that point if we do it

the other way around we already see what we actually build so our UI we don't need any actual data from our database or API for that we can just work with dummy data and then when we then get to implementing our data layer we can

immediately test that since the UI is already set in stone so let's go to our book feature here open up our domain layer because as I said we want to start off with the book model so that will be

our innermost domain model here which is nothing else than a carton data class yes I want to add that to git and then we can start to think about what a specific book actually offers what a

specific book uh consists of which data does it bundle this is in our case just an ID in form of a string um those IDs just come from the

API later on and the API just uses string IDs it certainly has a title certainly has an image URL for our coverage image it might have one or

multiple authors so we will have a list of strings here for all of the authors of a book it might have an optional description so a notable string um since not every book has a description that

the API provides it might have some languages which is a list of string it might have a first publish date or let's

make it first published year which is optional and a string it will have a rating average let's actually call it average

rating reads better average rating which is a double also notable because not every book from the API has such a rating it has a rating count which is a

nullable integer also not provided for every book it has a number of pages so let's call it num Pages integer nullable and a number

editions this one is not nullable because the API apparently provides this value value for each and every book okay and this is our very first domain model it really just contains information

about what a specific model what a specific concept consists of what kind of data is bundled here that makes up a book and if you would now have a different type of app that would also

deal with books um like let's think of a an ebook reader app or so then the domain model of a book might look completely differently for that different app because for an ebook read app you would certainly also need to

know what is written on each page that's not relevant for this specific app that we built here but for an ebook reader app it would be uh necessary to know that and this is really the idea of the domain layer that we can take a look at

it and we see okay this is actually the problem that our app solves this is this is its business and here we really don't want to include any references to third party sdks to any annotations that come

from libraries um so you would not want to use something like serial name here for example to to um indicate how a certain field is called in the Json resp

response that comes from the API because that this again would couple the domain layer to an implementation detail implementation detail would be how the Json field name is actually called and

that we use Json in the first place to encode and decode data so all that should not be part of domain um I will show you later how this works with this architecture but here we really um only

want to use raw cotland code and now that we have our core domain model obviously you could have multiple domain models um in another app but here we really only have this book model now that we have that we can jump into our

UI for the book list screen and the way I typically structure this is inside of our feature we have a presentation layer um which just contains our UI logic our screens and so on in here for every

single screen part of that feature I create a package um that is called how the screen is called so bookor list and in that we will now put

everything um that belongs to this book list screen on the one hand that is a book list state so here in this of course I will specifically stick to the

mvi architecture so um mvi stands for model view intent and that is an architectural design pattern that decides about how our presentation layer is uh split how we structure our

presentation layer and with MV specifically um what it says is we have our UI so the actual view which is in the end just our composable here then we

have our view model so that is the unit that actually um decides about how and when state is changed MBI specifically bundles the screen state in such a data class here commonly so we just have a

single screen state that we need to observe in our UI and then the I about mvi so the model view intent that corresponds to um a bundled Class A

bundled sealed interface it will be in cotland that contains all those UI actions all those things the user could do on a screen that we might want to send to the view model for example

clicking a button to save a book in in favorites for for example to just select a book and open the details for example to change the search query all of those things that are changed um as a result

of a user interaction would then be bundled um as such a sealed interface which you can then send to the view model so this is the intent about mvi the intent the user has with a certain action and here in this booklet State we

now just bundle our state so all kinds of values that could change over time and have an impact on our screen on our UI on the one hand the search cre for example because that can obviously change when we type in the new search

query the default one here in our case will just be cotlin we then certainly need some search results which is a list of our books

now initially it's an empty list because we have not loaded any data yet when we um initially open the screen we do need a list for favorite books so a list of

book again again an empty list we want a Boolean whether we're currently loading any books so we can show a progress indicator we need an integer for the

selected tab index is an integer and initially it's zero so the first index um just as a bit of a recap let's open the app again so you can see we have two tabs here for

search results and favorites um and the selected tab index will just highlight the index of the currently selected tab so either zero or one and lastly what we

need is an error message so in case something went wrong we want to uh show some kind of error here this will now be UI text here initially null because initially there is no error but what

this UI text now allows us to do is which if you remember comes from our core package presentation UI text it allows us um to either make this a dynamic string so just a normal string

nothing else or to wrap this around a string resource ID so string resource IDs are in the end just references to our string resources which then later on allow us to also localize our app

translate it to different languages and if you want to support that and make it easy later on to localize something we have to work with string resources however the thing is we can only unwrap

string resource IDs in our UI so that we look up which specific string is behind a certain ID but we still might want to actually create um strings based on

string resource IDs from within our view model where we wouldn't be able to unwrap these and this we actually solved with this UI text class because um if we then want to pass a certain error message that comes from our string

resource we can return this in our state in our view model by saying okay UI tex.

string resource ID we pass in unknown error for example but coming from the string resources just as as an ID and then in our UI we actually have access to things like the context which is

needed on Android for that um there we can very well then deconstruct and unwrap this string resource ID Here Again by making use of this string resource composable so it's in the end

really just a convenient way for us to work with string resource IDs um from within a view model the next thing we need before we can get started with the actual UI for our booklist screen is our intent so the actual seal interface that

I talked about that bundles the different actions uh that we could perform on the book list screen this will also go here in presentation book list and will be called book list action

because this is a sealed interface encoding all those different actions a user could perform on the screen will be sealed interface so we just have a limited set of actions and options in

here and specifically what can change on the screen that the user has an impact on on the one hand we can say on search query change we pass in the new query so the new text of our text field and this

will be a book list action then we have a data class on book click so when we just tap on a book we want to pass in the book that we clicked on book list

action again and lastly when we actually switch the tab so ontap selected then that will also have an impact on our state so we want to pass in the um the new

index and also make this a book book list action what's important to consider here is design wise that the UI is not responsible for deciding what should happen after such a user action after

the search cre be changed after clicking on a book that's not the responsibility of the UI but it is the responsibility of the view model so we should really also call our actiones that way um that that they really just contain the

information what happened that the search query changed that we clicked on a book um but we should not call them based on the result they will lead to um later on so that we actually trigger a search so we should not call them um

open book details we should not call them switch to different tab or so but rather just call them what happens and the view model's responsibility is then to decide that this really keeps our UI

and the view model decoupled um so even if you would use your screen with a different view model you would not need to change anything about the naming here so next up let's create this view model um for now really just a blank view

model we won't put any logic in this but really just have our state there um have our function that receives these actions so that we have something to work within our UI so this will be a book list view

model make this a class inherit from view model this comes from the jet brains jetpack dependency and in here we can then declare our state so private

Valore state is a mutable State flow so this is typically what I like to work with when dealing with my States um so there are also other options like using compos State directly here in the view model which is also viable but I feel

like State flow gives us a bit more options um to work with because it's it also allows us to work with reactivity um by chaining and combining multiple flows and it makes it easier to update

state in a threat save manner since it provides a function for that the default State here will be our empty book list State and then we will have a public immutable version of this state oops

which we can create with State as state flow so this typically what we do that we have a private mutable version of the state that the view model can mute mutate but the UI should not be able to mutate this dat directly but rather send

these actions to the view model so the view model can do that that is why we have these two properties then we will certainly expose a function on action where the UI can then pass a certain action to the view model which we can

then unwrap here with a when expression we're going hit Alt Enter to add those remaining branches and leave these empty for now maybe for the search query this is an easy one we could already

implement this so here the view model really just uh responds to those actions when the search query changes the vi model's responsibility is now to update our state and just just update that

search query so we again observe this in the UI and and then see this update so here we would say state. update so

update is this uh function that updates state in a threat save manner so you can safely call this from multiple different threats on the same state object and won't run into any race conditions in

here we then use our existing state we copy this and we set the search query to our new search query that comes from our action and that's how we update state with this architecture same thing would apply to

our tabs so here we wouldn't update our search query but actually our selected tab index to the new index and everything else um that is about searching through books and all that

stuff that comes later so now that we have our very basic screen structure at least everything that we need for our booklist screen let's create our root

structure for our booklist screen so here new file call this book list screen simple file and we make this a

composable book list screen something that I always do for composable that I can only highly recommend you to do is to not work with just a single screen

composable so what I often see people do is they now include the view model reference in here coming from coin view model like this so that in this screen

composable they can directly observe state from The View model and also trigger this on action function we've created however the thing is especially if you use di like coin which will

inject a view model um during runtime this won't work for previews so the preview is not smart enough the composed preview is not smart enough um to actually inject such an instance to actually have a real instance of this

view model because that would come with all your other dependencies all your other classes that you might need for this view model so a repository a database client an HP client and the preview is simply not capable of all

that it's it's simply meant to preview simple UI or to preview just UI so instead of doing it like this what we want is want to have another

composable which we call book list screen root and what this composable will get is this will now have the view model reference furthermore it will

provide a Lambda on book click that we provide in here and all this root composable um will do well all this is responsible for is on the one

hand collecting our state so we can say Val state by view model State collect as state with life cycle and then it calls our subcomposition

the state is the state we observe here the on action Lambda is simply our view model double callon on action so we just um forward all these actions we uh bubble up with as on action Lambda here

in this screen to our view model reference or we actually listen to these specifically since here we also want to Bubble Up these um on book click events specifically uh since in that case we

need to navigate and if we don't have a we don't have access to our nav controller here in this composable we don't want to have access to that same reasons um with a preview but We R I want to just bubble these up to our navc so we can do the navigation transition

there so in here we get access to all these actions we can check when that action is book list action book list action. onbook click then we just

action. onbook click then we just trigger onbook click and we actually pass in the book we clicked on um so we also need to pass this in here and then

uh we can also get R of the modifier I would say yes then we can say else we just don't do anything and all actions no no matter what that is we just forward to our view model anyways so we really just intercept these actions kind

of here um if we if we need to handle these in the UI directly since clicking on a book is something we wouldn't handle in the view model or since there we don't have access to our nav controller but if we structure our screens like this and you can really

always structure these like this you can now put this composable in your nav host um which gets the view model which uh has all those lambdas that would have an impact on navigating but this way our

actual screen composable which contains the actual UI is completely isolated this would now be much easier to um test in an isolated UI test because we just need to provide uh some sort of instance

of our state which we can just construct in the preview and the Lambda is also very easy to construct can just be an empty Lambda so we we don't rely on a view model reference anymore um and that's exactly what we want to achieve in this case we can also make this

private since we don't need this anywhere else this one needs to stay public since that is what we will add to our nav host so what comes next let's take a look at our UI and the first step

when I structured screen is I try to think of reusable components so which parts of the UI would actually make sense to extract in a separate composable but here what would make

sense is certainly our uh search bar and possibly each list item and maybe the list as a whole and that is what we will start off with um so we'll first of all Define a composable that just um defines

how our search bar looks like then we will um work on this tab view tab layout so we can also swipe here and lastly we will then work on these list items as

well as um the list itself starting off with the search bar when I have such reusable components that are still specific to a screen the way I structure this is in the screen composable I

simply create a composable or components package so those are just components composes specific to the screen if I would have components that would be sheared between multiple screens still

part of the same feature I would create this components package here in the presentation layer outside of this book list package and if these components are actually shared between even multiple features I would put them here in core

presentation um so this is how this would really work with this architecture but here we just need the search bar on the single screen so we can say um let's

just call it search bar or book search bar make this a composable book search bar what kind of parameters does it need certainly a search query for for

displaying the right text it provides an on search query change Lambda um so whenever it changes it will Bubble Up the new search query here to the parent

composable and it will provide an onim search action what this will do is well it will trigger the search when we tap that button on our keyboard so if we take a look here again and we want to

type something here uh then you can see on the one hand it will trigger when we just wait for 500 milliseconds but we also have this search button here which can trigger a search when um we would we would have typed something else and we

will trigger this Lambda in that case all right let let's get started sadly the Android Studio does not yet support previewing uh composed multiplatform composes um so this is not possible at this point it probably will be at some

point maybe when you're watching this video you can just put a preview annotation and a composable down here and this will work you can get this to work already if you're using jet PR Fleet um which I haven't used

excessively yet I've tried it out just when it came out and um Android Studio was Superior at that point um just because it's much more mature so it's it's expected it's a preview version but that is why I sadly can't offer you a

preview here in this file um we can HCK our way around that by putting the preview in our Android main module since for Android apps the preview is supported even for compos multiplatform

composes but that is a limitation we have to accept as of now so in here we will put in an outlined text field which we'll make use of here I think we need

to oh no we need to provide a value so our search query we need to provide an on value change Lambda which is on search query change and that itself would already make up a working text

field but it would look quite ugly um so if we would want to preview this we would go to Android main or book pedia create maybe a previews file create a

preview here say okay book search bar preview we would have our material theme in here in which we put our book search bar so in these um platform specific

modules we have full access to our common main module so to all the Shar code but obviously not the other way around so from common M we can't access Android specific code directly so if we go in here and we provide a search query

like cotland if we have an empty Lambda for when the search query changes just like when we trigger the ime search we can

give this a modifier of modifier filmax with then we should be able to see something build and refresh and yes there we go by default

does not have a background we could fix this by giving this a background color of color. white for example oh not that

of color. white for example oh not that one let's give it a hex code like this and then we would have a white color no

for some reason it is not working well then we can also leave this here I mean we we we see that it works we could maybe give this composable um a background color with the modifier color

white why is it using these old colors um I want these composed graphics colors like this but that apparently also doesn't do anything that doesn't matter um let's just move on but this is

how we could preview something um from composed multiplatform so I think for the more complex composes we also do this here in in in this course so we now proceed with the outline text field we

can also open up here um our reference text field so we actually see what we want to build the shape certainly needs to be a pill shape so we can say rounded

Corner shape passing in 100% of rounding we want to give this colors so I'm to style this a bit differently by saying outlin text field outline text field

defaults it's actually called not sure why it doesn't have the dependency uh colors text field colors um text field

defaults text field defaults do outline that it is outline text field colors and in here we will have a text color actually text color can State the default since we have a a white

background it will just be the um on background color I from our theme let's change the cursor color to our dark blue from our colors file as well as the

focused border color to something like sand yellow so you can see um the sand yellow color here is the Border color and the cursor color is this dark blue run something we might also want to pass

is a placeholder so when there is nothing here then we will um include the search text placeholder make this a text and here we

have want to do string resource so we say the text is a string resource where we say Ras from here this resources so this is how these shared resources work

for compos multiplatform r. string.

search hint and I'm not sure if we can create these with ADD enter no we can actually do this for Android resources but not for compos multiplatform as of now at least so let's create these

manually by going to Common main compos resources that is where these are located like for example a compos multiplay platform vector and if we would want to create string resources we

would create a values directory and in there we will create a new file called strings.xml make sure to have the same

strings.xml make sure to have the same namings so compost can actually find these resources here first of all uh we want to add this XML metadata encoding can be

utf8 and then we will have a resources tag um don't worry if the autoc completion doesn't work here um it's just a bit atypical for Android studio um that we put our resources in a random

folder since Android Studio is not um that closely coupled with a compos multiplatform yet nevertheless we can make this work by um having string resources in here where each XML string

needs a name so just the uh string resource ID so for example what is this search and the text will then be search let's call it search hint actually is a

bit more expressive so here in book search bar we can now say Ras string search in which is not available because those resources are actually generated um

after rebuild so what we can do is we can click on make module and this would hopefully generate um corresponding cotton and composed multiplatform classes that implement this uh string

resource like behavior and also would allow us to localize this app you can see my rebuild actually failed um because we need to import this so it's uh what compos did is it generated a

separate class which is not included by default but once we import this then it will be recognized and the rebuild should now also succeed once we did this is actually much faster once we um rebuild our project once so these

incremental rebuilds uh will not take that much time anymore uh but in my experience the first rebuild of a composable platform project can take ages at at this point at least all right

next up we have a leading icon which will be the leading icon let's take a look so in this case it will be this search icon which we want to display here so just icon uh where we say okay

we got an image Vector icons. default.

search there's already a readymade icon for that the content description can be null here and the tint of this can be our material theme this one do colors do

um is it actually that looks a lot like material 2 but we want to use material three here actually um not sure didn't I include this dependency let's quickly

take a look um because with material 3 it would actually be called a material theme. color scheme instead of colors

theme. color scheme instead of colors which is a bit odd let's take a look at our version catalog we do have our where is compose

is there any reference to material three no didn't I include that um I think for composed multiplatform it's actually it's already coming from the composed multiplatform framework so we need to

change this here in build gradal of our um of our main module and here you can see there are certain dependencies that come from compost um I

I didn't add those myself those already come from composed multiplatform from the grad plug-in if we switch to material three here this should hopefully fix it we might also need to repport these um UI components um but

just making sure that everybody of you gets the same outcomes here with these uh components let's make sure that we all use the same material version this way after

syncing we should prob get some errors here in a book app search bar yes because we need this new import you can see from Material 3 I'll enter to do that and now it's actually uh here this

one we also need to import um and now it's actually also called differently which I thought at first here it's now outline text field defaults do colors

and then um it will do the same also need to do the same for texts um not the one from Kato here but the composable need to import this this icon

from Material 3 this time and now we should also be able to set the tint to our material theme color scheme there it is that on Surface and we give it an

alpha of 66f so then we get this uh grayish effect here in between we can also uh just optimize our Imports or remove

these here manually and then take a look in our previews where is that previews here maybe we see something here need to reimport the material matal theme here as well ah it seems um we would need

this dependency for for the Android side as well then let's ignore this and just show this search bar for our preview as it is reload this and then we also need

to actually fix this here this is actually just the the standard app composable here we can get rid of all that this is really just the the standard initial code that we don't need

in our app we can optimize all those Imports um take a look if this is now fixed yes now we can actually see an actual search bar here and for the background

color we could also put this in a box modifier modifier filmx with give this a

background of color. white from compose now and then put this in here and yes now we also have a a much more decent

preview here if we don't have a search query then you will notice or if we actually make this empty then we should see our placeholder yes there it is search so we can go back to our book

search bar and continue working on it what else is important for this text field well we certainly need this to be single line so we set single line to

true so we can't have any um actual new lines there we want to trigger our ime search action by saying keyboard

actions is keyboard actions on search this will simply be our ime search uh seems like we need to create this Lambda here and then trigger on ime search so this will be triggered when we click on this green search button here and when

we then type something here you will notice that um this close icon actually fades in and Fades out we can achieve this with a trailing icon this time in order to make it fade in and out

automatically we can work with an animated visibility animated visibility we say okay the visibility actually changes based on our search

query not being empty so if it's not blank actually then we want to show the icon otherwise we don't and whenever this changes we will animate the

transition and here we can then have our icon button when we click this we will simply call our on search query change

and pass in an empty query so we just um say hey the search updated with an empty text and we say um we have an icon with an image

Vector of icons oops icons. default do

uh what is this close yes content description would be a string resource r.

string and we pass in our close hint um actually not R it's R here in compos multiplatform close hint and I think we still can't create this here from the context menu no let's then go to our

strings XML here we now just need to copy paste this um and make make this a close hint and say clear sege or so this is really purely for accessibility

reasons that we give our icon button here um a clear content description so that um screen readers could also kind of read what the button is for so even blind people also could could use this

app in some sense we can then give this a tint of material theme color scheme on Surface this time we give this full ons surface color without any Alpha value

because this is really meant to be inter actable and should be a prominent action we can then make our module again and we should hopefully be able to um see a valid import for this close hint during

resource yep there we go we probably need to import it again um so this is still a bit annoying about compos multiplatform or at least better for Native Android that we can uh we don't

have to generate resources um with rebuilding but it is what it is and that is definitely a trade-off we can accept I think all right um I think we also need to Define uh that we actually have

have a search ime action so that this search button appears in the first place in our keyboard simply by saying keyboard options is keyboard options

where we say the keyboard type keyboard type is keyboard type. text so we just want to be able to enter normal text here and the ime action would be ime

action. search so this icon actually

action. search so this icon actually pops up and we click it this will trigger and the only real thing that is left is making use of this modifier here let's do this at at the very bottom

passing in our uh modifier we might want to give this text field a background color we say the shape of the background is again our rounded Corner shape with 100% so the background is also adjusted

to this rounded corners and the color will be desert white which we've added previously and we want to add uh minimum interactive component size so what this will do is if we take a look at the

Docks here it will Reserve at least 480p in size um to make sure that these touch interactions for example for this close icon that that there is at least that much space for those um for for this

trailing icon button um so even if the the composer board measure smaller we make sure that we at least have the space for this trailing icon button and if we now take a look at our previews

again uh then we should be able to see a fully fletched search bar after reloading yes there we go here it is we could also start the uh interactive mode

this one here if we type something in there you can see our yellow border orange border and we can then um enter something no we can't because we did not provide an implementation for this

Lambda here obviously a little fun thing we could do is that we also change the cursor color so you will notice this um not the cursor color but the uh text selection color so when we type

something here oops right here test and we highlight this text you can see it also marks this in this uh orange that fits to our theme of our app we can

achieve this by hopping into our search bar compon composable and saying composition local provider and we say

local text selection colors. provides or

just saying provides text selection colors the handle color will be a handle color will be sand yellow as well as the background color sand

yellow open a block of code and then put our outline text field in there so what we can do with this is that we can change the actual value that Loc text selection colors that current will provide for all the code that comes in

here um so those are just typical values you might want to retrieve easily in composable application wide and all the composes that now come in here will use those provided text selection colors and

we could also easily test this already by going to our book list screen and just adding this new composable here so we could say we have a

column we say modifier is modifier filmax size we give this screen a background color of white or let's actually say dark blue um not color that

dark blue but dark blue from our color KT file because this is the background color of our actual background and then our column our list will be drawn on top of that we give this some status bars

padding so we um consider the padding here for our status bar and we can say horizontal alignment is alignment um Center horizontally and in

here let's just have our our book search bar where the search query is now our state. searchquery where our on search

state. searchquery where our on search query change will now trigger our onaction Lambda where we say when the search qu changes want to trigger our

book list action that on search query change with it so the new search query so this will be sent to the view model The View model will then receive this here it will update the state when the

state changes since it's a state flow it will trigger an update in the UI again so the UI will show the new updated search query in the text field then when we actually click on ime search we're actually not going to perform a real

search here because the the search triggers after 500 milliseconds um automatically so I think yeah that doesn't matter that we actually need to um provide a way to let users search

even faster but what we will use this for is we want to hide the keyboard so when we click this um the keyboard will be hidden as you can see we can do this with the keyboard

controller local software keyboard controller. current we can then use this

controller. current we can then use this in here to say keyboard controller question mark. height so we just hide

question mark. height so we just hide our keyboard and lastly let's pass in a modifier modifier. within passing in a

modifier modifier. within passing in a minimum width of 400 DP I'm actually a maximum width so the search bar um doesn't become too large especially on desktop devices that's always something

need to think of of course if you're building a KMP app um that also targets larger screen screens so we say the width of the this text field will never be larger than 400 DP we still try to

fill the maximum width of our screen unless that would be more than 400 DP and then we add some padding 16 DP for example and we could

already try this out by taking this bookless screen composable going to our entry point our application composable let's also get rid of this greeting and platform by the way which we also need

to get rid of here for each platform so platform. Android can be deleted

platform. Android can be deleted platform jvm can be deleted and platform. iOS can also be deleted this

platform. iOS can also be deleted this is the entry point so this is the composable that will show up at first so here we can say we got a book list

screen rout the view model um Can for now be just a view model constructed with remember since we don't need to pass in any dependencies yet so really just for this demonstration of purposes

and when we click on a book we would perform the navigation but we also don't yet have enough controller and this application composable will now be drawn in our normal Android main activity so if you're an Android developer this will

be familiar to you you can see there it is this comes from the Shar code if we are on desktop we really just have a pure main function which uh is created with this application um function here which gives us a composable scope

creates a window and then also calls this app for iOS we have this main view controller which in the end um just constructs this view controller which is an iOS concept of a view of a UI

component and then also lets us call composable in there here so this way let's actually run this on Android clicking run taking a look here and

hopefully uh see some sort of decent looking search bar there we go and we do see that here's our search bar we can hopefully change this yes because we've

already added the logic to update the state and when we um actually click on clear we can also already clear our text that is very cool so what what is the

next step let's actually switch back to our um reference app next up I would say what would make sense to implement is a composable for all these different list items all right let's take a look at our

package structure again to check where we want to put this book list item composable um definitely not in our Android main source set because that will be for Android specific UI

components or classes in general we want to put this in uh common main book list components again of course since this is

an exclusive reusable component for the um for the book list screen so here book list item for example make this a file composable and

we call this book list item well and what does a book list item need in order to display some data obviously a book and we might also want to pass an on click Lambda when we click this so we

can then navigate to the detail screen later on let's make this a bit larger again so we actually see what we plan to build so we now plan to build such a book list item here where we can first

of all start with this surface around so This Little Light bluish kind of background for that we can use a material surface composable that is just used to um create such a surface let's

give it a rounded Corner shape so shape is rounded Corner shape of uh 25 DP or let's say 32 DP that's I think what I

used here 32 DP as a rounding then we give it a modifier so we pass in the modifier we have passed in here and we make that clickable and when we click this we can say onclick is equal to the

on click function uh the on click Lambda that we passed to this composable and the color of this surface will be light blue the one that we actually defined in

our colors KT file and we say copy and set the alpha to2 F so let's now think about what kind of components and layouts we do need for yeah to just display um what we want

display for such an item so we definitely need a row because this image here the book cover needs to be displayed in a row like fashion together with a column here a column of three

texts um then we have another row in here for this text and this uh star icon and then in this very um outer Row in the root row we then have this uh final uh error icon so first of all let's

define this very outer Row for the book cover for these three texts and for the final icon the modifier for this one will be modifier. padding first of all

let's add 16 DP of padding we say fil Max wi and we then say height and we set this to Intrinsic size. minimum what is intrinsic size that is in the end way to

give this row a fixed height that is still dynamic in some sense so if we want to keep our layouts here somewhat Dynamic that um the proportions of how much with this image takes of how how

much with this text section takes and so on if these are Dynamic also based on the GRE of the device then we need a way to find out which part of this row actually has the highest height because if we wouldn't use intrinsic size. Min

here then it could be that this text uh spends more height on certain device types than the image or the image spends more height on certain device types than this text and we don't know that yet during development um which of these

Powers will span over more height so with intrinsic size. Min we give the the height of this whole row here of our whole list item the minimum height that we need to fit all of the single

children in so the minimum height that is needed um to perfectly fit in the image to perfectly fit in the text section and the icon here because if we then use filmax height somewhere inside of this row normally without giving this

a fixed height it would just span the whole screen height because it would that that's how filmax height works but if we give this row a fixed height by using this height modifier um then it will always have the same height or um

at least this this minimum intrinsic size um I hope this makes sense then for this image for this book cover I actually want to put this in a box because because um when we load new images then we have this loading

animation here as you can see which is a separate composable we will Implement all those animations at the end of the course um so we'll focus on functionality but we can already think of this aspect when designing this list

item that we just have a box in which we can conveniently swap out the contents of that so the composable so either the book cover or the animation the modifier

of this box will be modifier do let's give this a height of 100 DP for example and we want to make sure that everything is centered in this box well and now what comes

inside of this box um if we put the animation aside for now is this book cover of course but as you can see certain book covers are available and for some um there is just no book cover available on the um API in that case we

want to display this kind of generic failure icon or so so let's think through how we can do this in the end our image loading library that we use here is called coil um that is a coton image loading library that that I can

only recommend for um compose and cotlon multiplatform projects but also for Android that library is great and if we want to now observe the uh current state of the of loading a specific image from a web URL if we want to do that with

coil then we need to keep track of that with a compos date so we say we keep a reference to our image load result we say that is equal to buy remember and in here we will then have a mutable State

off and the state will consist of a result and here's important that you import this coton result um for this use case we're fine with that we don't want to use our custom result class this will

be a cotton result of type painter and this whole result can be nullable um actually in here and then we need to close this off like this and make this null

initially like this we then Alt Enter to import this by remember delegate um then we have our very initial state so the result can either be a successful result or a failure of type painter so if it was successful then we get a painter

which we can use to paint or draw an image and initially we don't yet have any result in order to then get the Painter we can say Val painter is equal to remember async image painter that now

comes from coil the model that we have to pass is in the end um the image on to load so you can see coil takes in anything here so um this could be a bit map this could be an image URL and then

coil will figure out how to load this image in our case this is simply the image URL of our book then we can overwrite this on success Lambda which will trigger when the image was

successfully loaded this will then give us access to this acing image painter state success but here a little catch is um that sometimes the API seems to um

actually provide an image with no real valid Dimensions so with a size of zero so want to make that check um if that actually is the case so if it do painter

that intrinsic size so the size of the image if the width is larger than one and the same is actually the case for the height so height is also larger than one if that's the case if we have a

valid image then we want to say result at success and we pass in it I'm actually the the painter here behind this it otherwise if we successfully loaded an image but it's uh one by one

pixels then we want to say okay that's actually not a successful result and we say result at failure and we can pass in a custom exception here where we say invalid

image size or so um what kind of issue does it complain here okay it's not thrown we don't need to throw this exception here we really just need to pass it to the failure so we get an um an error message and this whole thing we

then need to assign to our image load result if there was an arrow with loading the image then on Arrow will trigger so this would be the case for example if we don't have internet connection then we could say okay we

first of all want to actually print the stack tray of what went wrong so we get to see the logs if an image failed to load and then we say image load result

is result. failure and we pass in this

is result. failure and we pass in this throwable so it result throwable all right next up now that we have our or a painter that we can use to actually draw an image right now we don't really draw

anything because uh this is really just a state and not a real composable at least not a visual composable so what we want to do is when Val result is equal

to our image load result when that is null actually null then that means um we are still loading this image let's for now just show a circular progress indicator um so that is a very easy way

to get an animation but later on I will then show you how we get this book puls animation but if this result is now not null anymore that means we can draw our image you can simply use a normal compos

image which takes in a painter but the painter will now be our special coil image painter so we can say the painter is equal to if our result is actually a successful result um so the image has

valid Dimensions that was loaded then we use the painter and otherwise if it does not have valid Dimensions we use a painter resource instead where we can now load our um failed image drawable

from our local resources I don't even know if I included this here in the app before no we actually don't have our drawable in here yet I will simply paste this here because this is an XML

drawable which you can of course um also find from get up below but I think um now that I realized this I will also include this in the initial Branch so you should probably already have this here and if not if I for forgot it for

for some reason then uh you can still simply copy paste this XML code uh from the from the same path here and just create a new empty file put this in there and then you're ready uh we now need to rebuild this so make module

that's enough and then we should have access to our new drawable resource um so we did get a um compile eror which is expected since we have an issue in here

but if we now actually let's move this into such braces if we now type Ras this

one here drawable do book error there it is book error 2 um then we will create a painter from this painter resource cool next up what we need to pass here is a

Content description for a Content description where we can just use the title of the book so screen readers would uh read that then what we want is a Content scale this is also dependent

on the actual result here so if the result is Success we want to use content scale. crop so we use this typical

scale. crop so we use this typical Center crop content scale style um so if the image does not fit in our dimensions then it will be cropped and otherwise if

uh this is not successful that means we are dealing with this book error we will use content scale. fit instead um since for for this icon it would look weird when it's cropped and uh this way it

still looks solid and is then centered here in uh in our image and then what we can do is we can say we give this whole thing a

modifier of type modifier obviously and I just want to give this an aspect ratio of 65f so we just force every single image to have the same aspect ratio what

we also want to do is we want to set this ratio on the one hand but we also want to set this match height constraints first set this to True um so that it definitely

tries to fill in the height here of the aspect ratio without trying the width first since um the book covers are all rather um rather higher than wide so this should now be enough to actually

load an image and display um error loading events so the next state of our outer row would be this text section here which is actually a column so let's take a look up here again um so this box

is the first element of our row and if we then go down here we can put in our column for this column we can now say modifier filmax height which would

normally again um span the whole screen's height um if the if the outer Row in this case does not have a fixed height uh but since we assigned this fixed height with this um intrinsic

size. Min we will make sure that this

size. Min we will make sure that this does not happen and we will only span the available height so pretty much the height of our image cover and what we want to say is we want to give this column a weight of 1f what this will do

is if only the single element in this AO row has a weight of 1 f it will simply span all the available width that it can get um after the um the other items have already already been placed and what's

important is that we set vertical arrangement to arrangement. Center so

these text are just centered uh vertically here the first text in here will be our book title so book title we can set the style to material

do typography do tile medium we can give it a Max lines count of two so that we allow it to span over two lines here and

then we set the Overflow to text overflow ellipses so that if the text or the title is even longer then we can fit in in two lines uh then we will cut it

off with three dots then afterwards if there is actually an author then we want to display that not all books have an author um at least in uh what the API provides some books have multiple

authors still here in this um primary list item UI um we want to really provide a rather condensed view of everything um around the book and then display the details on the detail screen

which is why I will only show uh the primary author here for a book so if we take a look at the book do authors then we can say first or null so if there is

an author we call let get the author name and simply display a text with it so the text is the author name the style here would be material theme topography

body large for the author we just want to allow one line and also set the Overflow to ellipses and then the rating text remains you can see not all books have such a rating provided from the API

but if they do um they want to display this as a row with a text that displays the rating value and our little star icon so down here um below the authors we can say book rating average or

average rating I call it if that exists we get the rating and we can display this actually in a row here where we can say modifier is

modifier uh we give this a actually a vertical padding let's do this first not a vertical padding but we Center this vertically vertical alignment is Center uh vertically

Center alignment center vertically there it is we actually don't need a modifier when I think about it we just want to Center this vertically and in here we then put in our text first of all um that

reflects reflects the rating um that two string so it's an integer rating what's a double I think double yes I'm going to show that as a string obviously and we might also want to round this actually

um since doubles often have a lot of decimal places um if we do some uh floating Point arithmetics here this can quickly happen since you can't accurately represent every single

decimal number with a binary sequence um so sometimes if you if you do some logical floating Point math you will end up with something like 3.54 or so which is something you've maybe faced which is why we definitely

want to round this to only one decimal place so instead of actually just converting this to a string let's say we display what we get from the round

function here we pass in our rating we multiply this by 10 and then divide it by 10 again so that means if our actual rating would be

4555 for example what we would do is we would uh say 4555 multipli by 10 which is equal to 45.5 5 and if we then divide

this by 10 again this one would actually be the rounded result if we say round this whole thing then this would

lead to four uh 64 46 and if we then divide this 46 by 10 we get to 4.6 as the final value without any of these

extra decimal places that is how this works style is material theme.

typography body medium and then next to that we want to have a little star icon so we can just say icon image Vector icons default star there we go there is

an existing item for that the content description can be enough for that we don't really need to explain that in any way and we then say the tint should be our scent yellow color so we get a

little nice uh yellow arrange ish icon here and the last thing that remains is the simple icon here for this um Arrow which will be displayed at the very end

of our um outer row so after this column right here we just say we have an icon the image Vector is icons default actually it's Auto mirrored this is in

Auto mirrored do default or I used this one from field a DOT it's called keyboard Arrow right so we want to have a keyboard arrow that point point to the

right content description is null and the modifier let's give this a fixed size of 36 DP and that is our book item composable at least for now the only

thing that is missing is um that we give this a proper animation here um that I've shown you here with a little pulls like animation and also that once we loaded a book you can see we animate

this in this is also what's currently missing here um which is what we will do later in this course next up we can work on actually showing these these list items in an actual list so a lazy colum

and compose which we want to put here in our components package again so we say book list make this a file have a book list composable which obviously will take in

a list of books it will get an onbook click Lambda which gives us access to the book we clicked on when we clicked on a book and

it needs an actual scroll state which is a lazy list State and we can default initialize this here with remember lazy list State like this we

can then move this down here since it's encouraged to have uh the modifier as the first uh default parameter in a list of um your your function signature and

in here we can then make a lazy column this this composable is actually quite simple the lazy column will take in the modifier we past it will take in our

state our scroll state it will take in a vertical arrangement of arrangement that's spaced by 12 DP so if we Alt Enter to import DP what this will

achieve us just that we have Spa uh 12 DP of spacing between all of these list items and we can set the horizontal alignment to alignment. Center

horizontally inside of the lazy column we want to have an items block since want to display a bunch of items which will obviously be for our books so we have our books list in here we then get

a reference to every single book in that list and we can now use that information to show off our book list item pass in our book reference give this a modifier

of modifier within want to give this a maximum width of 700p because again we need to um think of desktop as well and I think super long stretched list items would not look that well um so we rather

want to give these a Max width we can then say fil Max width um so we also make sure to um span over the available width and and then finally some

horizontal pading our padding horizontal of 16 DP and lastly when we actually click on such a list item we can say on

book click and we pass in the book we clicked on something that is also worth doing is that we say items is this and then we also give each item a unique key

keys are pretty much um the most straightforward way in a lazy column to optimize performance and to also enable item anim so when certain items in the list change

because if we give each item each book here a unique key for example the ID then the laser column is Smarty enough to actually detect which items changed if this whole list is being replaced with a different list of books and we

can then on the one hand animate the items that changed and on the other hand um the laser column only needs to recompose those items that changed instead of the whole list again so I would always recommend to pass in unique

keys if you have unique keys and I would say we now have all those reusable components that we need for our book list screen um we still need to work on this tap layout here um but I will

implement this right away in this book list screen um since this is wi and mixed with different composable so we need to know this list we need to know um how this looks like here in this whole layout which is why I've decided

to actually put this in the the in the screen layout itself so let's go here back to our book list screen and I think for this one it's actually worth

creating a preview in our Android Source set here our previews um file since um it definitely helps to actually have um a real preview for the whole screen so

we can just add this here preview book a list screen preview we can uh yeah let's just display this right away book list screen

root actually not the root one we do need to make the real screen here um public normally I wouldn't do that because normally you could put the preview inside of this file directly and then therefore access this private

composable but since compose multiplatform doesn't support previews in the public in the common souret yet let's do it this way so we say we have a

book list screen this one which is now very easy to preview uh since we can just construct a book list State on our own search query can stay Cartland

search results might want to have a dummy list of books here private Val books can just randomly generate at 100 books for

example say 1 to 100 map and we map each index to a book and then say the ID is the current index to string the title

would be book it so book one book two and so on image URL um we won't be able to display any images here in the preview anyways at least not um load

these from the network so we just uh we just pass in some kind of invalid URL the authors let's have a list of of

me like this then for the description uh that is not relevant for this one let's just give this a description it again in case we need this book list later on as

well languages also not relevant the first publish year is not relevant for this preview the average rating is relevant we can give this

something like this so we also check if the rounding works the rating count is not relevant we can pass any random number the number of pages is not

relevant here let's give this 100 and the number editions is also not relevant but now we have a sample list of dummy books we can pass in here and this is

also as I said um the reason why I always start with a presentation layer since now we can already check how our UI looks like it's very easy to create dummy data but if you start with a data

layer and um actually are able to load real data but but you don't have any way to to Really display these and work with that um then you still need to implement the whole presentation layer and addition until you're at the point where

you can test this whole thing for the very first time that is why this is typically easier so here do we need anything else um no everything else can

stay the same what is this this one here for what is that Lambda oh it's on action we can leave this as an empty Lambda I also always recommend to actually name your lambdas that way

unless it's a composable content Lambda because if we have a trading Lambda here it's absolutely not obvious that this is a normal Lambda and not a composable Lambda so avoid that so if we now reload

this we should be able to see our very basic book list screen with our single book search bar no it actually seems to complain we

need to build and refresh yes there we go here's our book list screen preview awesome so from this we now somehow need to get to this and that happens here in the booklist screen I don't know if I

can actually split this here maybe a little bit um can I split right does this do something and if I then expand this over here oh this is very hacky but

if we then switch over here then we do have a working preview pretty much very cool if we let's make this full full screen preview and if I now change something I'm curious if this automatically refreshes the preview in

another file so let's give this a different background color of uh color red for example then ah we apparently still need to click here yes there we go um so apparently it doesn't

automatically switch and refresh through preview we need to still do that on our own but this is a hacky way to make the compos preview work in a compos multiplatform as of now until that's supported in the common main source it

as well so you can see we have our search bar and after that we now want to have our lazy column with this kind of rounded Corner shape and in the end just a wide surface so that that is what we

will go on with we will have a surface compon posable again we set the modifier to modifier weight 1 F again so we just make this white thing span the remaining height of our screen and then say it

also fills the maximum width the color of this will be our desert White and the shape will be a rounded Corner shape um here we really only want

to have these rounded corners for these top two corners so we say top start is 32 DP top end as well and and then if we

open up the surface we should already see when we refresh the preview that we have our surface here yes there we go this looks pretty much like on the left very cool now what comes next is we want

to have a column here because on the one hand we have this tab row which is a single composable and then we have what comes after this tab row so our actual um book list and since we want to arrange these both in a in a columnwise

fashion we will make use of a column this column will have a modifier well actually not not just um horizontal alignment so we Center everything

horizontally alignment center horizontally then in here we do have this tap row which is a material composable for now displaying these multiple tap items here we need to pass

in the selected Tap index um which we get from our state the modifier is modifier we give this some padding of actually actually vertical padding of 12

DP um so this is this vertical padding that you can see here um this orange highlight um has a bit of vertical pading we again say

within maximum 700p for the same reasons I've mentioned before and then fill the whole width that we have in here in this content of the tab row we can now specify those tab items um so single

tabs our search results on the one hand and the favorites that works with tabs surprise surprise when we now need to say whether this tab is selected or not so whether I want to draw this orange

kind of border there for this first tab it's selected if the state selected tab index is zero so if it's the first uh first one that's selected if we click

this first tab item oops um we need to trigger our on action Lambda since um that is a user action that triggers some sort of um State change the switching

the tabs here where we say book list action dot on Tab selected and the selected tab is tab number zero the

modifier here will be modifier weight 1 F so we just want to evenly distribute these um tabs here over the row because this is I think a row scope that we have access to here otherwise you wouldn't be

able to call Waste want to set the selected content color um to our sand yellow so this is for the line here and for the text color and the unselected

con color so when a tab is not selected we we set this to um black but with uh 50% of alpha so color

black copy Alpha uh 0.5f then the text for this tab will be string resource string resource let's

create this here in our values strings XML let's create two of these on the one hand search results search

results and on the other hand for the favorites favorites then quickly make this module again so we generate these string resources go back here make this bigger

again and we get an error that is expected in here we can now refer to these just generated Resources with Ras

dot string do uh search results and the modifier of this text will be modifier. padding and we again give this

modifier. padding and we again give this 12 DP of vertical padding and the second tab will look exactly the same so we can just copy paste this work with that just

replace this one with one so this is selected if the select tab index is equal to one if we click on it we want to select tab number one and the text would be favorites and if we now refresh

our preview we should hopefully see exactly that as well should hopefully see a tab layout yes uh just this purple bar should not be there this should also

be orange I think we need to change this for the tab row which is the container color um now the container color is obviously what's behind um if we set this to Desert white that is what we

should definitely do the content color to our sand yellow and then refresh this what happens um no and I actually just uh

check that we do need to overwrite this indicator um so that is how this thing is called the the currently purple thing that is a tap indicator we can override with this indicator Lambda which gives

us a composable Lambda to have one from tab row default. tab um secondary indicator it's

default. tab um secondary indicator it's called we set this to modifier um modifier. indicator offset and this one

modifier. indicator offset and this one actually gives us access to tab positions which is if we take a look at that a list of tab position so where

each tab is in our um tab layout and we can now use this to properly offset the indicator um so let's actually not do this so we see what happens um by just giving this a color of sand yellow if we

do it like this then you can see this is a completely sad yellow um which might also be because I set the content color there but no and we actually do need to pass in this modifier modifier tab

indicator offset and here we can then refer to the specific tab of um of our selected index so State selected tab index refresh this again and now um you

can see it's it's properly offset so it just uses the the proper position and offset information from the tabs that we already get from the Lambda itself so we don't need to worry about that but we do

need to use this custom um tab indicator offset modifier to properly offset this okay um below this TBL layout I just want to have a little bit of spacing

that means um if we have this tab row here then here we want to have a spacer or we say modifier is modifier. height

with 40p of spacing and what comes here will now be a horizontal pager why do we need a horizontal pager because this this tap row um in in the from the material library is really just what it

says it's really just a tab row it it adds these TBL like components but it does not yet allow any swiping between these tabs it really only adds two tabs and that's us click on these but if we

want to be able to actually swipe between these Pages then we do need um a horizontal pager which is pretty much just um a container in which we can put

a composable and that container lets us swipe between multiple pages of composes in our case just our search results and the favorites list in order to not declare this horizontal pager we can

work with a horizontal P surprise surprise which now takes in this P State this page State lets us then also control which page we are actually inspecting how many pages there are in the first place and I would like to

declare this at the top of our screen somewhere here Val pager state is equal to remember pager state which gives us this Lambda here um in which we need to

pass in the page count so in our case just two the initial page can stay at Z and the offset fraction we don't need to change anything about that we just want to have two pages and then pass in this

pag State down here give this one a modifier of modifier fill Max width and a weight of 1 F so we can fill the

remaining height of our screen and then we get this composable Lambda in here and what we now put in in this Lambda here will be um will be our current page

so we do get the page index here so either zero if we are on the first page or one if we are on the favorite section and depending on this page index we now want to show the right page so we can

say when page index if that is zero we want to show this composable and if it's one we want to show that one if it's zero then that means we are here on the first page where we show our search

results and then we need to have another check because if we are currently loading those search results we to show um a loading indicator so we say if state is loading is true then show a

circular progress indicator which we actually want to Center on the screen so we might want to actually put all this stuff in a box

modifier modifier fil Max size and we give this a Content alignment of Center so we just Center everything here inside of our box and

when then the page index is zero this indicator will then be centered if it is um if we're not loading so else then we actually need another little when expression because depending on

what our search results are we want to um show a different different thing so you can see this if we search for something here where we probably don't find any search results then we display

such a text this is something we would need to um put in this box and if there are search results we obviously show them with our list and if there is an error we also want to show this um so if

we for example turn off our internet connection and we search for something Harry Potter or so then we get an unknown error Ur or whatever kind of

error message we we choose for that so here in this one expression when the state DOT error message is not null so that there is an error then we just show

a text that tells us this error and since this uh State error message uh State error message is now a UI text not a string yet this UI text is just a repper

as I said around a string resource ID we can now take this and actually unwrap that with as string which is a composable function um and will then simply take the string resource ID and

if it first of all check what kind of U it text this is if it's a dynamic string it will return the value right away but if it's a string resource ID um like it is here then it will uh return the

string resource with this ID instead so going back here we can then also change the appearance of this text a little bit first of all setting the text align to Center oh not snap position text text

align is text align Center and the style of the text can be material theme uh typography headline small for example

headline small and we might also want to give this an error color so material theme color scheme error like this similarly you want to do this if there

are no search results so if State search results is um is empty so we search or something but nothing is available then we also want

to show a text not the error message in this case but rather a string resource string resource R

rs. string um and how do we call this no

rs. string um and how do we call this no search results we can then go to Strings XML duplicate the last line create a no

search results string resource and say oops we couldn't or not we but let's say

oops there aren't any books for your search query something like that then again make this module again and after

that we can import this we go um do we get the import no it's not done yet there we go do we now get this

import yes we do I'll enter import the string resource and for the final state so else that means we uh successfully found at least one book with our search query in that case we want to show this

list of books of course so we say book list the list of books comes from our state state do search results we called it when we click on a book then we do

want to trigger our on action Lambda where we say book list action on book click and we pass in uh it so the book we clicked on the modifier can just be

modifier filx size and that's already fine for the scroll State I want to pass something um let's say lazy list State I want to

declare this up here here well lazy list State it's equal to remember lazy list State the reason I'm creating this um we actually

need two of these lazy list states um one for the list for the search results and one for the favorite books um let's actually change this to search results list state or so the reason I'm

creating this is that if we've um scrolled down a little bit and our search results change for example we search for python now then you can see it it animates um a scroll to the very

first item index so in order to actually um Force animation of force animating towards a specific item we need a lazy list state so we can have a launched

effect block here whenever the search results change so when we have different books in our list then we want to say uh search results listate animate scroll to

item and want to animate to the very first item down here in our pager um this book list we now pass our our list

State and for index number one for our favorites here when we are on this page then we also want to show a book list so we can actually have something uh something similar so also if there are

no books in our favorites list so if State um favorite books is empty then we just want to show some kind of text here

so we can we can just replicate this one here um no search results um we probably should change this

um to something like you have um you haven't saved any favorite books yet and

here we can say no favorite books and again same thing that is a bit annoying with having to rebuild all the time when creating resource but we get a lot out of that by being able to share these

resources the rebuild is still going on um but we can already set this to no favorite books and in the else case we can probably just copy paste our book

list from up here put this over there and just Swap this out with our favorite books um here we need to also pass in a

different scroll state so we can say favorite books list State something like that scroll up duplicate this one call

it favorite books list State and we are ready to go I think I think this should be the screen and we can also refresh our preview in a moment rebuild is still going on for me all right and now after

rebuild you can see the preview actually loaded and it looks slightly off um so if we go back to the search results these items all look a little bit like not so high as these are and I would say

we check why this is first of all let vertically Cent these and there's also um the the there's no not enough hor Al spacing so let's fix these easy things

first I would say by going to the book list item and here in our row yes the root row here we need to make sure that we vertically Center

everything like this can also give this row some horizontal Arrangement Arrangement spaced by let's say 16 DP we then refresh this preview we should

already see um that everything is hopefully centered vertically and yes not not so crammed seems like there is some vertical spacing here I mean the

the odd thing about this is that this box here if we click on that then that is the the surface that is smaller than the actual area that gets drawn and I'm

not sure if this is actually an issue or a code or an issue with a preview so I would suggest we just try to launch the app as it is um with our dummy books by going to our

previews taking this private Val books and cutting this out putting this in our um book list State like this let's just have our

dummy books over here make this thing public it's really just for development purposes here we then need to import these books without enter and then we can say okay the initial search results

are just our dummy books if we then launch this on our device let's see what happens because I think everything else

should already be set okay we do get the crash why is that um because of probably missing internet connection let's go to

manifest at users permission and say we want to use internet that was probably it yes it was and here it actually looks different that is uh that is good so it

seems like the uh preview here has its issues with the composed multiplatform composes you can see the images don't load because we have invalid URLs so that makes sense and the rest is looking

pretty much like before going to the favorit works um it just doesn't show the favorites list yet or did we also pass that um also pass in our dummy

books for that no that one is an empty list so we still need to check why that doesn't work oh no okay when when the

when this error book actually loads then we do get this extra spacing so it seems to be related to to somewhere here where we

check whether the search no actually in our list item where we check um if we successfully loaded the result somewhere here it seems like we have too much

padding or so all right guys I did not expect that finding this padding issue here would be that difficult I just spent one and a half hours to actually track down why for only for err images

we get this kind of extra pading here you can see I open the layout inspector for some reason only for these error images there is this extra kind of um spacing around this image which does not

happen for the actual book covers I really copy pasted my exact composable that I have in my sample code in here same issue I double checked everything that I could same issue and I ended up

finding the issue so we can fix this luckily but I still don't understand why I assume that it's either a bug in coil a bug in composed resources or that I just completely missed something here

but I know that people like when I actually fix such things also here in the video CU I think it's important to show that YouTube is also not always sunshine and rainbows and that we just type the code from from beginning to end

and then it magically works that's also not the case for me of course everything is prepared here and even if everything is prepared such issues happen and I need time to debug these that's completely normal so nobody needs to

feel bad if that happens to you that happens to all of us let's actually take a look what the issue is or rather how we can fix it we can fix it by taking a

look here in our book era 2 XML for some reason this uh Vector drawable does does not like being 5002p wide and high if we

change this to something smaller like 200 50 60p which by the way I I don't have that way in my prepared code I actually just copy pasted this file so my prepared

code I have 52 DP for both of these and it it works just fine it does not have this extra padding for these error images but here I need to change this I don't know why I'm using the same versions for coil and all that stuff but

if we change this and then actually rerun our app then take a look here again let's also get rid of the layout inspector so we get this a bit larger then we should hopefully now get a

working list item I mean they are working but they they should look a littleit little bit better without all that extra spacing and there we go you can see now it looks exactly as we

wanted I have no idea why this actually causes an issue because we gave our um our image actually uh a fixed size so here in a book list item the image is is inside of this box which has a fixed

height so it's technically not possible to actually exceed that height no matter what kind of image we put in there but for some reason that still happen so I can only explain that um as I said

either a buck and coil a buck and compos multiplatform resources or I'm missing something if you know the issue uh comment below uh would be really interesting to know but I think this is also not something that we need to waste

ours on so let's just go on with building the book app cuz now we actually have a completely working book list screen where we can scroll through all of our books or dummy books at this

point we can actually change here to the the favorites you can see this does not yet update and that's something we'll do in a moment um maybe we should also change the text color of this one here to to Black so um it doesn't show up as

an error we can do this here in our booklet screen by scrolling down let's shrink the preview a little bit so you get to see more code here for the search results um when these are empty no

actually not these but when the favorites are empty then I want to get rid of the text color here um so it will show up in Black how do we now um how do we now Implement that when

we switch the page that we also update our pager and that when we click on our pager we also switch the page because that's the the only bit of functionality missing here apart from of course

loading the books we need to do this with some launched effect blocks because on the one hand we need to listen to our current page which will update when we swipe here in our pager and on the other

hand we need to listen to the selected tab index of our TA row which yeah we'll just change when we click here and the way this works is pretty simple so it's kind of a two-way binding we can call it

um we have a launch defect block here where we listen to our selected tab index so whenever that changes this launch defect block here will be called and in that case when we click on favorites or search results we want to

use our pag state to animate a scroll to a page and the page will simply be our new selected tab index just like that so this will fix that if we click on something we also switch the page now

the other launch effect will be for switching the page that we also update the selected tab up there here launched effect this will now require the actual um the current page so pager State at

current page whenever that changes which will update when we swipe the pages then we want to check if we are currently not scrolling so if pag state is scroll in progress is false because only when we

really stop the scroll then we want to actually update the um the tab row so here we need to send an action to our view model by saying boist action do on

Tab selected and the new selected tab in this case will then be the pager State current page so just the new page that we swipe to if we launch this then we should be able to um check this there we

go so if we swipe then no it does not yet happen that we actually see the tap row if we click here that works I think this might be because the current page

updates before this Boolean switches to fals let's try if this works if we put this outside this if condition and launch this

again there we go we swipe yes now it works just fine and we can still click here and still swipe yes

now it works um so the the um scroll check apparently wasn't necessary so I think we can start the next chapter of this course and that is to start working on our data layer on the functionality

that we can actually fetch our book data from our open Library API and then somehow display that in our UI so that we are able to actually search for books that we call that that we properly map

everything to state in our book lless V model and so on so to just show you how this API Works which API we actually using um I'm here on this open Library website you can see that's just um open

library.org completely free as I said you don't even need an um authentication for that you can just call the endpoint and the way this works is we do have this search. Json endpoint and we can

this search. Json endpoint and we can just attach this title here I'm so for what we are actually searching and for example if we say Harry plus

Potter execute that then after it loaded we get all of our books here from that endpoint so this is now the Json structure that we

need to kind of replicate in our source code in Android Studio of course not exactly the same structure as here um so there is a lot of information for every single book um but but only those types of fields here that we're actually

interested in um so specifically those that are also part of our um book model so how does this work with the architecture that we've chosen this works with so-called data models so you've already learned about domain

models domain model is in the end what you've seen here this book model for example so really just a pure cotland data class representation of um of of of a core concept of our app of what a book

is now a data model is something similar but it contains implementation details specifically how our Json field names are actually like in which format the Json structure is in and so on so what

we want to create for this book is um we want to have an equivalent data model that we can then use to take our Json structure that the API responds with and powers that into our cotland data class

and we call these these um data related models at least if they come from a network we typically call these dto so data transfer objects because these are transferred via the network you'll notice that these can be quite similar

as this domain model here but I'll share why we have two types of models here in a moment so here in our data package what we want is we want to create let's

actually create a dto package in there all of our data transfer objects and in there we will now have a data class called search searched book dto for

example so um just one book entry from our search response this will be a data class and since we're now using cotland ACT civilization to actually um po the Json into our for cotland data class we

need to annotate this with serializable this is a requirement from this cotland actualization library and now here in this data class we pretty much just want to look up our Json structure um so we

are now actually trying to power such an object here in this STS list trying to parse that into such a data class that we've just created in our dto you can

see have the title which is interesting for us have a lot of IDs from Amazon isbns and so on we we don't care about we have the publish here for example so we now want to actually um extract the

data that we uh that we need here from this Json structure into our Cartland data class on the one hand each book will have an ID in form of a string and we also want to annotate this with

serial name because um remotely these IDs are actually called key I still prefer to work with ID here so with that we just say okay in the Jon it's called key we want to call it ID then we get

the title which is a string Tittle is also called like that in the Jason structure so we don't need the serial name here um we do want the language actually the languages because there could be multiple languages which is a

list of string that can be null so this is not return for every book we have the cover alternative key which is an

integer and nullable so this will be the cover key um which we'll later on need to actually fetch a book cover since that's not contained in the the response there so

this will have a Serial name called cover uncore I which I just like to call cover alternative key makes it a bit

more clear same thing for the author keys so serial name author uncore key which is actually a list so author Keys

list of string nullable again we have the author uncore name for our author names again plural we then have a

normal cover key let's also d at this again cover key which is called cover undor Edition key this will be

um an integer again so um nullable integer there are just two of these cover Keys we'll first of on first of all try to fetch the book cover with this key if that fails we'll try it with

this key and if that fails we will display our error image in general the API design of this open library is not that good you will also notice this later um with a with an issue that we

have with this API that we will need to work around but for now we can just proceed here so we get the first publish year which will be

our publish year let's call it first publish year we have our ratings average

which is a double and called ratings average we have our ratings count which is an integer call this r ratings count

and we have two more that we care about on the one hand the number number of pages median which we

want to call num Pages median and that is an integer nullable and the last one that we care about at least um is the addition count so the number of

additions number editions and I would say for consistency we can also add these serial names to these here by saying this is our title oops and

here I think that's actually called language even though it's multiple ones so let's also add the serial name here so we now have our searched book dto

this looks very similarly to our book model except for these annotations so why do we really work with two models here couldn't we just add these annotations here to this book model so

to make this whoops to make this a serializable and so on uh we could but we would violate this architecture because as I said the domain layer is really meant to be completely isolated it really shouldn't have any any um

references to external libraries to implementation details and while we have a single modular project here we would technically be able to use those here um but very often in practice you don't have that so you have your domain layer

as a separate Gradle module um so you you also have to add your own types of libraries for this domain module and then you wouldn't be able to um use data related references here by default anymore and that's the intention of it

because the purpose of this is of of having two models here is that when implementation details change let's say the the backend team says hey guys I'm actually renamed the key field to ID what we as app developers then have to

do is we have to hop into our code and we need to change this here to ID so that our code doesn't break and such changes in implementation details like the Json structure should not have an

impact on our domain layer they should not require changes in the domain layer and that is why we really um want to keep it like that also if certain implementation details or certain

libraries change if you say I want to swap out cotlon ex realization with I don't know Moshi or Jon well then you also only need to change your data layer your domain layer is completely isolated

of these dependencies so you you don't need to change anything there and also not change anything in the presentation layer since the presentation layer only makes use of the domain models again something else we now need is if we take

a look in our browser again the searched book dto is in the end what starts here from this Json object however the actual J response that is returned is this outer one here so we also need such a

dto for that outer response which contains this doc field which then contains a list of our searched book dto so let's go here in dto let's have a

search response dto for example which will also get our little serializable annotation and in here we will then um just have our docs field or um let's

call it results to be a bit more expressive here this will be a list of our searched book DT o now and we can annotate this with serial name to encode

the remote Json field name which is docs so this was what the API will respond with and uh this contains our desired list of search book dto then okay so the

next step is now to actually implement the functionality that really makes that actual API call and um purses the Json response into this object and that also

goes in our book data layer here where we can just say okay maybe create a package or so and in here we will put a class called Kor remote book data source

because that's exactly what it is it's a data source so a source of books it's remote and we use Kato for that so this is a typical naming um I choose for these specific implementations of a data

source since the class name immediately makes clear what will be inside and this class will take in reference of an HTP client so from our Kor client this is

the main instance that we need to use in order to make um actual API calls and fetch data that way and technically we can just create a suspend function in here which we call um search books for

example that obviously needs the search query and we could pass an optional result limit so nullable integer for um yeah just how many results we want to

have in the response let's format that a bit like this and what will this now return well this will return a result our own custom result class that takes

in two generics a result that holds our search response dto oh no actually not because that would be a violation of our architecture which you will see in a

moment why that is instead what we want to do what we want to return is a list of books so our domain model if there was an error so the type of error codes of error enums that we can return here

in this search books function well those are data errors which we need to create um that is now how this result class works that I um that I added before in our core package

domain and here we have this result class which really just wraps around either a successful piece of data a list of books for example or a specific error that must implement this error interface we have in here it's really just a

marker interface and the way this now works is if we want to extend this for example with a set of um Network related errors then we can go in here we can

create something like a data error make that an interface seal interface and a data error could now be a remote error but it could also be a local error so if we're dealing with a database and the

storage is full or so um then that would be a local local data related error if the API responds with some kind of invalid status code or there is no internet connection or so then that is a

remote related data error so in here we can declare an enim class called remote for the remote errors which is now a data error and this data error very important must also Implement our error

interface so we are able to pass that as a valid error to our result class and let's now think about what kinds of uh remote errors we care about for example

request timeout too many requests so in case the API has some kind of quota um and you exceed that quota then that might be returned for example something

like no internet if you don't have internet connection something like server so if there's any serers side error that we don't really have an influence on then we also want to know

that of course maybe also serialization related errors so if Json puring fails for some reason and lastly let's have some unknown errors for everything else

we can also already add another enm class here for local data errors in which we just have dis full or

unknown like this what this now allows us to do is in our K2 remote book data source we can now make this return a data error. remote so here in this

data error. remote so here in this function we can now make a API call we can check for the result we can properly re retrieve the the list of books in our response if so we will return this as a

result at success with this list of books if it if something failed we will return the the proper data error here um from the inm class remote and for a simple app like this that really just

needs to make use of two different API calls one for the book search endpoint one for the book detail end point we could really just type this off here um but as I said I really want to teach you um how I would also handle this in in a

growing app in an app that scales um and that is why I will introduce some utility functions that I typically use for my bigger projects that I've also shared in my big Essentials course because that will really make your life

easier when you work with maybe 10 20 50 or more than 100 end points which can quickly be the case in in a growing app so what we want to do is in core we

create a data layer for shared datar related logic utility and in there we will have an HTTP CL extension file so here we really just Define extension

functions for the HTTP client or just other HTTP client related utility for example that we have a suspend inline

function we say reify t and no worries I will explain this in a moment call this response to result where we pass in a

certain HTTP response and what this will now return is a result from our domain package here a result of type t or data error

remote so this function is now a utility function that takes in an HTTP response so any type of response that came back from an API no matter what kind of API call that was it will try to actually

extract the data out of that HTTP response so the actual adjacent body and poers that into our type T that we expect here the refight is needed because we um need the type information

of this generic argument um in inside of this function here which would normally not be possible with but reified it is inline is also needed together with that and it's suspending since um the function or the paring process is a

suspending function here but in the end this is really just a when expression so we can return When when the response status. value so the actual returned HTP

status. value so the actual returned HTP status code if that is in the range of 200 to 299 that means the response was um successful because um if an HTP

status code starts with a two that indicates um a success in that case in here we now want to try actually parsing

the Json body into our type T so we say result success and we say response. body

as type T we just try to actually poish that response body into our T data class and this would then return um yeah just our coton representation of the Jon response if that

fails it will throw a no transformation phone exception and in that case we can then return result error instead where we say data error remote serialization

that is the specific error that happened in that case and now after that when things went wrong then the status code does not start with a two instead it will start with something like a four so that will be a client side error

something that is our fault that we made a wrong request or so for example 408 will mean request timeout in that case we can then return result error data

error remote request timeout something like 429 will be too many requests and let's take a look what else we have in here so we have a server set error no internet error that does not have

anything to do with the um status code that is returned that is something we need to check when we make the request and unkown errors so in here if the

error is in 500 to 599 if it start with a five that means servers at error so something on the server went wrong that is outside of our influence so we say

result error um oops actually we need the in keyword here then we return data error. remote. server and in all other

error. remote. server and in all other cases we say result error data error remote unknown so in that case we just display generic error message of course

we could check for every single status code that could be returned here um but I always decide about how F gr I do this um depending on whether I want to show different types of error messages for

these different types of Errors so if there is no benefit to the user of knowing the the exact error what went wrong and then I also would just show a generic error message which can sometimes also be better security wise

since you're not revealing why something went wrong all right that is one utility function that I want to create another one is now a function that safely executes an HTTP call and catches the

necessary Exceptions there well also be a suspend inline function also with a REI for the same reasons as below and we call this save call this will get in an execute Lambda

which um is meant to execute the actual call and this Lambda will return an H H GP response and again put this on a separate

line and this will then again return a result of t and a data or a network or remote I called it and all this will to do is it will

get the response or try to get the response from our execute Lambda which executes the request so it's a a Lambda so we can dynamically use this function with any type of request that returns an

HTP response but this function will now catch certain EX ceptions that could happen there on the one hand a socker timeout exception for example in that case we want to return a result error

data error remote time out and actually return that here if we get an unresolved address exception then um as long as the server

is actually live this usually means that there is no internet connection so then we want to return result error data error remote no internet and

if we get any other exceptions then we want to return result error data error remote unknown here

it's important um if we catch General exceptions in a try and catch block for a suspending function which this execute function could be and will be that we also call cortine context and Sh active

uh I won't go into the details here um but what could possibly happen here is that we accidentally catch cancellation exceptions so when a cortin is canceled in in cotland it will that the currently suspending function will throw

cancellation exception like this execute Lambda the API call and if we catch that then we can break this cancellation um mechanism in our app since the parent scopes of this coroutine um might not be

properly notified about that that can lead to several issues I I made dozens of videos about common cortine mistakes I have a cortin master class which I also linked down below um so won't go into the details here but this is

something we have to do all right so this means if we did not return at any catch block here we successfully got a valid HTTP response and this we will

then simply return by putting this into our response to result like this which will also return the same type of result here and now we have very handy utility

here that we can use to go back to our data source and actually execute this so here for search books we can return our save call function which has this Lambda and

this Lambda now needs to be an HTP call that returns HTP response for which we can then use our HTP client we call get so it's a get request we need to pass in a URL string so the actual URL we want

to make a request to we can also um have our base URL here so private cons Val base URL and that's simply

hgps open library.org and then we can say okay our Ur string is oops actually base URL search. Json that is the URL

that I showed you before and then here we can open a block of code to um configure that request a little bit specifically you want to pass certain um request parameters on the one hand QE

for query we pass in our query our limit so how many books we're interested in for this request will be our result limit if it

exists we want to pass in our language so um the language of the books we are looking for if you if you um come from a different country or you you prefer books in your language then feel free to

adjust this we will use English here of course and lastly what will be um quite useful is that we also pass in this Fields um parameter here um to tell the API which fields we are actually

interested in so all the others then won't be returned you just saw how big the response was with all those Amazon IDs and so on we don't care about that our app so we also don't need to transfer all that unnecessary data so we

can just specify the fields we are looking for I would simply paste these here um those are really just the same fields that we already have the clear here in our search book dto so all of

these just um yeah in in a comma separated string if you're lazy like I am then you can also just look look up this string here in in the GitHub repository below now the thing is if we

would run this this would very likely crash even though we don't have any syntax errors it would crash because what we return here is a list of domain level books but what this response

returns is actually our search response DT so typically you have two options when you face this with a data source it really all depends now whether we want to use this data source application wide

so also in our view models for example also in our domain layer because if we would want that we would need to create an abstraction for it because again if we would now simply use this cater

remote book data source somewhere in our um presentation layer so if we would pass that here for example in our book list view model um this would work so we would have our data source which is a k to remote data source but the moment we

do that we couple our presentation layer to our data layer and when our data layer faces changes again in things like Json field names if um the accepted data or changes then that will cause changes

in our presentation layer as well which we want to avoid with this architecture a data related change should ideally only cause a change in our data layer and a presentation layer related change should only cause a change in our presentation layer so remember the

directions here of our architecture the directions of the dependencies are like this we have our presentation layer towards domain and then we have data that also points towards domain there is no error from data to presentation

directly or from domain to data so this will also not be valid if we would want to use that in our domain layer so what do we do here we could create an abstraction for this so we could go in our domain layer and we create something

like a remote book data source which really just um provides the function signatures like this that this data source should have this actual

implementation can then Implement that um that abstra abration that we've created in domain since this way around it's it's completely valid if the abstraction sits in domain then data is allowed to access that since there was

an error from data to domain as I showed you however in our specific case here when we actually have two data sources a local and a remote one and there's also certain interaction between these because um when we later on also add the

local data source the the local DB in order to also save favorites then we need to somehow coordinate that data access because if we then want to actually display a book then we need to first of all check is that book already

in our local database if so we can show it right away so we don't need to make an actual API call anymore but if not we need to make that API call so this is called offline first that we first try to load something offline and then um

online if we didn't if we weren't successful with our offline query and if we have such a mechanism then we need something called a repository so repository is really just a design

pattern used to um combine multiple data sources and in this case in our app our view model and domain layer just need to know that repository so it will be enough to to create this abstraction for

the repository and um not necessarily for this um remote book data source here at least not in domain so what we can do in this is we can swap this list of

books out here with a search response dto and it's not debatable whether we need an abstraction for this one here or not but if we create one we must create it now in the data layer because at this

point where we added an implementation detail where we added a data model to our function signature at this point point it would not be allowed anymore to have this abstraction in domain because then domain would again have a reference

to a classro data so what we can do is we go to our Network package we create remote book data source which will will be an interface an abstraction and this

will just have the function signature for search endpoint so search books we will have our query in here we'll have our result limit in here

which is a nullable integer and this will now return the exact same type of result here so a search response dto and

then our data error remote if we then take this and actually make this class implement this then we also need to add this overwrite

keyword here we do need to make the um the parameter here default in the remote book data source over here so this way we now created an abstraction of this

data source if we would change the HTP library for example from Kor to something else in future then that would not be a problem because the only class we would need to change is this one or we would just need to add another class

like this with the other HTP client um and the rest of our project would not need to change however I really want to say that this abstraction is debatable it does add a little bit of extra complexity here and it's not necessary

in order to not violate our architecture because we really only need this class inside of our data layer and obviously the data layer is allowed to access dependencies and classes from the data layer so I hope I I did not confuse you

too much with that but I just want to highlight that it would be important that you would not put this interface here in domain if it has a reference to a dto because then domain would have an

actual import to your data layer which would not be allowed and in a case where your domain layer would need to access the data source and you would not need a repository as I said then you would

rather um make this result contain the actual domain model so a list of books as I had it before because then there is no reference to dat anymore so what we now want to do is want to go to our book

data and we create a repository really just simple for now um repository yeah let's call it like that as a package and in there we will create our let's call

it default repository or default Book Repository like that will be a class this will now get our data source so the book uh remote book data source which

will depend on the abstraction now so we could easily Swap this out with a different one and in here for the search endpoint um there is nothing offline first about this um since we don't cash

all the books from our resarch that would be a good homework for you um but that's something I won't do in this video this one will be super simple since it will just delegate the call to our data source so we have a search

books function we just need the query and will then return our result now with a list of books that will become important as you will notice

in a moment with a data error remote just like this and here we can then simply return remote book data source search books pass in our query and

you'll notice that this now gives us a type mismatch because what we expected was a result that consists of a list of books but we got a result that consisted

of a search response D which is exactly what that remote book data source returns that is our data transfer object in the end that is our coton representation of what the API responded so what we now need to do is we need to

take this dto and somehow bring this into a format that we can use across the app so a list of books in the end we someone need to take all those books

that are contained here in this results list all those search book dto and for each and every book we need to take it and map this to our domain level book and for that we use so-called mappers so

a mapper will just map a data model to a domain model we could put this also here in data in a MERS package for example and create our let's call it book mappers this will really just be a file

of extension functions where we will say we have a searched book dto and we want to be able to map this to a book so to a domain model and here we really just construct

a new book out of the search book so the ID will be the ID title will be title image URL will be uh no here we

actually don't have this image URL um in this search book dto if you remember we have this uh cover key and this cover alternative key so those are two keys that we can use to fetch the cover

information about a book so here I actually want to also paste this URL um you can quickly type this off this is the URL that we need to um request when

we want to load an image so covers open Library orc B allet whatever and here we then have to pass in our cover key but we want an if

check if our cover key is not null so if it exists then we want to load the cover from this URL and else

there is an alternative URL so as I said it's it's quite difficult to understand API also took a bit to actually understand how this works um but if this cover doesn't exist it could still mean

that this one exists by making a request to this URL but from this point on where we then mapped such a search book dto to a book we don't need to worry about that that anymore since we just need this

logic here at a s single place next up we need the author's list which is nothing else than our author names the description will be null here since

we can only fetch that from the separate endpoint what is the issue here um okay if the author names is null we will return an empty list

here then the languages is equal to languages if that is null empty list again first publish year here is first

publish year that can stay the same and this is it's an integer but required a string um let's say uh two string so

this would um print null if it doesn't exist and otherwise it uses the publish here then we have uh the average rating

which is ratings average rating count ratings count number Pages uh oops is number Pages

median and lastly number additions is number editions and if that doesn't exist we will say zero so we now have a mapper a function that Maps one object

to another to a book in the end which we can now make use of in our repository so uh where is it repository here which you can make use of here to take this the

response here map this so this map function here comes from our helper utility functions here from this result class which Maps one result of type t to a different type of data for example R

so from a search response dto to a list of books for example so here we do get our um dto and we can now map this

dto that results so the um actual list of search book dtos we can map these to books so it.2 book and then we don't have any errors anymore so this is

really the responsibility of your data layer here um at least one responsibility of your data to take the um dto to take your possible entities that is how we um call the um data

models that we save in in in a local database to take these and map these to domain models so to a format that the rest of the app is able to use and right now if we would actually uh have a

reference of this default repository in our view model again like here default repository ah we would again have this data reference here um actually this one so we can have an import from from

presentation book list view model is in presentation an input from presentation to data which is not allowed again so how do we work around that or how do we fix that this now again works with an abstraction so whenever you need to

access something from data in presentation or domain you need to work with abstractions in this architecture here so the way this works is we get rid of this um let's do this in a moment but the way this works is we now create an

abstraction for this repository in domain so we say Book Repository make this an interface have our search books

function that takes in query and then returns our result um of type list of books and data error remote so you can see this abstraction

because it's it now works on domain models um in this case we don't have any reference to somewhere outside of the domain layer because we are in the domain layer and domain is not allowed to access anything outside of domain

which we also don't violate that way so All Imports here are from domain if we now go to a default Book Repository oops um and actually make this Implement our Book Repository then we do have an input

from domain here but here it's also allowed data is very well allowed to access classes um and and interfaces from domain so if we now go in here and make this an override function we don't

have any errors anymore and if we now go to our view model or is it book list view model and we swap this default Book Repository out with a Book Repository and then optimize

our Imports we suddenly fix this problem because now our presentation layer does not have any references on data anymore but just domain and this is again allowed again recap presentation has an

error to domain and data as well presentation is allowed to access domain which we do here data is allowed to access domain

which we do here a lot of times but domain has no errors outside of it so here we only have our domain level references um so that is why the architecture is the way it is I hope

this is clear let's also call This Book Repository since now we we implemented our data layer at least to um fetch books and I know this was a lot of work to actually get this to to a point where

we can make an HTP call but from this point on if your project scales it's really very easy to add more calls you would just add another function here to your um data source to fetchbook details for example and then you just have

another piece of code that looks like this to do that and you can you can call it right away everything else will be hand handled um in the background by our utility functions all right so I would

suggest we go on in our bookless view model so that we get to a working search screen here in the fastest time possible by now making use of our Book Repository and actually calling that endpoint that

we've implemented now and in order to trigger our search we need to listen to um State changes of our search query field this we can do in a separate function can be private private function

observe search query in which we say state so our state flow.

map we map this to our search query we say distinct until changed so this means um that we ignore emissions here in this um in this flow chain when these are the

same so first of all we have our state which is the whole state the whole data class we map this just to the search query so we get string emissions whenever the search query changes whenever you type something we say

distinct until changed so we um drop all subsequent emissions so all emissions where the uh these are actually the same then we can say debounce and add 500

millisecond delay here also I'll enter on debound to add this to your um view model class what debound will do is that it will really only trigger the sech if we stop typing for at least 500

milliseconds because typically we're typing quite quickly we don't want to make an extra API call for every single keystroke which would be quite bad but only when the user actually decides to execute the search when they stop typing

and this is what debound will do and then we can listen to each emission so to the queries here where we can then check when the query is

blank so if it's empty then we update our state so State update it copy we make sure the error message is reset to

null and we want to set our search results to our cached books so cached books will be a simple list here that we um where we just cach the last load um

list of books so we can quickly update it um if we if we clear the search query so it's not really um cached persistently in our persistance storage but just in memory here in the view

model by having this private VAR cached books list which is H just an empty list here of books so we can then go down here so if we um cleared our search

query field we will simply um show the last loaded list of books otherwise if the search query. length is let's say at least is two then we want to trigger our

search so we say something like search books with our query this will be a separate function but what's also very important is that after this on each block we call launch in V model scope um

because otherwise this whole flow chain won't do anything um we need to explicitly launch it in a cortin scope so what this will do is whenever our search preview changes this will trigger we will debounce it so that we only

consider um cases where there is no emission for for at least 500 milliseconds and for each of those emissions that falls under this criteria we will then um execute this when

expression and ideally trigger our search so in our new private function search books search books we pass an our query to now execute the search here the

first thing we want to do is we want to update our state to say we are now loading so is loading is true then we want to say view model scope. launch

since searching books is a suspending function and we need to execute that in a VI model scope and here we can now make the call to our Book Repository The Book Repository search books with our

query then we want to check for success and error cases which we can do with these lambdas if that was successful we get the search results here which is a

list of books now in that case we want to update our state so update is loading would be false again any errors will be cleared if we got a successful response of course um and then most importantly

or search results will be the search results we got from the API if something went wrong however we do get the error here which is now a data error remote so we know the um we we have a type safe

information about the error type that we have here which is why I really like to work with this um type of result class that I've shown you here so if there is an error what you want to do is want to also up our state of

course where we say the error message let's start with the search results the search results is an empty list so we just reset these is loading will also be default when we got an error and the actual error message will now be a UI

text and this is now also again very handy that we have this UI text because now we can simply write extension functions for each type of um set of errors in our presentation layer and

then easily convert these to the right string resource here so the way this works is we can go to um we can go to our core presentation layer so just

shared presentation related utility where we say data error to string reource and here we will really just

take these data error remotes or just a normal data error and we say to UI text this will now return a UI text and here we can then have when

expression when this so the data error Alt Enter to add these remaining branches and let's actually first of all declare the string resource here and

then return the UI text afterwards if the dis is full then and we can actually let's create these string resources now in uh where is that here's values

strings and for all of these different error cases we now want to have a separate string disk full for example we try to insert a favorite book but the

disc is full um oops it seems like your disk is full something like this then we do have an unknown error oops something

went wrong or so we do have a request timeout we say the request timed out no internet I actually like to prepend

these with error since that makes it very easy to find all the error related strings um so for missing internet um

couldn't reach server please check your internet connection then we have let's keep this

here then we do have uh what else quota exceeded um too many requests it's called your quota seems to be exceeded we have a serialization related

error civilization couldn't par data or so and what else do we have in here civilization a Ser related errors will also just be an unknown error since we

don't want to show the user that something went wrong on our server um could be um that could be bad for security and other than that that's it so let's get rid of the last one um make

sure to make our project again and maybe we can already access these let's try this out r. string.

r. string.

error no not yet we do get an error here because um V model is um now has a Constructor Let's ignore this for now do we have access to our um string

resources error now we also get this popup up ah come on I want to try this error yeah error dis full now it works let's do this for all of our Different

Strings this one would be error unknown error request timeout too many requests no internet um this will also be unknown

for the server set error as I said serialization and this one will be unknown cool now we have the string resource and we can simply return this as a UI text

do string resource ID and we pass in that string resource and now wherever we have a data related error we can very easily po that into such UI text um so we don't need to have this when

expression at each and every place in each and every V model over our project um where we need to pass par a data related error into a presentation readable um error message and it's all

fully localized if you want so you can just translate your app um thanks to string resources so here we simply say error do to UI text and there we go

that's it it's all really just utility you have to write once for each type of error so you might also have such error class for password validation errors

where you have um strings like um Too Short strings like not about a password things like missing a special character and then you have another extension function for that that also returns a UI text and at all places where you have

password validation errors you would then call this function so I hope the idea of this gets clear we now have this search book function um and let's

actually move this call in here and set this function equal to the view model scope and also get rid of the um last closing um bracket here because that way

we will also return this view model scope job that is returned so the run and cortin job since what we want to do is if there is an ongoing search and we then change the search cre we want to cancel the previous search because that

doesn't matter for us anymore so what we can do is up here we have a private VAR search job which keeps track of our current coroutine job for the search

like this initially null and in here we will then say the search job is equal to this and the previous search job we simply cancel so the moment we type something new and there is an ongoing search job we will cancel it and we

instead start a new one that is what happens here all right now we just need to call those functions to observe the search query and to search for the books of course um which actually happens

inside here which I want to do here when we listen to our state um the reason I want to load this initial data in there um is that it's easier to trigger this from test cases so this will now trigger

when flow collection starts when our UI starts to listen to the state um many people will do this in in the NIT block which will work just fine but it is kind of um a side effect of initializing your

view model you won't be able to initialize your view model without also triggering all that initial data load possibly this can lead to problems in tests doesn't does have to lead to problems if this is not a problem for

you feel free to use the init block there have been lots of debates about this there is no single truth about where to load initial data I also have a separate video about that which I made um a few weeks ago but I will use the

onart operator of this flow so as soon as we start listening to this flow in our UI when we call a collect um collect State as life cycle then this will trigger for the first time and only if

our cash books are empty so only for the very first time we want to start listening to our search query because this onart operator um would be called again when we for example navigate to the detail screen wait for for some time

and then go back then flow collection would start again um but not if the cached books are actually um already contain some cach books then we would not call this and rather skip it this

here then needs to be state in view model scope so we um just get a normal flow out of this on the start not the state flow anymore in order to convert this to a state flow we need state in

Sharing started while subscribed with 5 Seconds um so this means that while there are active subscribers of our state we will execute this whole flow chain here and 5 Seconds more than that

so um for at least um 5 Seconds more when the last subscriber disappears and the initial value is just our state.

value just like that so that way we just trigger the initial data loading here once or we um trigger listening to that flow once and from that point on we'll be good okay as much as I would like to

um try this out out already I think we can let's let's try this out as it is without making any more changes um in a very quick way by going to our application composable here we

initialize our view model and let's really just initialize our dependencies in here just to try it out normally um I would never do this here um and you will also see in a moment how how we will

solve this in a better way but let's initialize our default Book Repository here let's initialize our remote book data source here with a Kor remote book

data source and here we need our HTP client for which I I quickly want to create a factory since creating that um involves a few things which we need anyways later on um let's do this here

in core data HTP client Factory so Factory pattern is simply um a class that has the purpose the responsibility to construct a bit more complex objects

here this can be an object we just have a function create want to pass in an engine that we want to use um with K2 so worked with these engines that um yeah

where some of these work on the jvm that work for desktop but then not for um iOS for example so depending on the platform we want to pass in a different engine here this will we return an HTP

client which we can create here with HTP client engine um like this and here we can now install certain plugins from Kor so um depending on the functionality you

want to use with Kor there is a plugin for that for example if we want to be able to automatically po Json responses into our data classes then we need the

content negotiation feature which will do that for us in here we can then configure how our Json paring Library um Works how how that is used we can use this Json function to

say we want to use coton ex serialization and we want to say the Json that is used here the Json object with our desired config will on the one

hand um ignore unknown keys this will be true that's actually the only thing we need to do here but what this will do is if there are um more keys more fields in our adjacent response that we don't

explicitly need in our data class then this will be ignored so our app won't crash because of that then what we can do is we can install an HTTP timeout so that we can set the timeout of our

requests when it uh we don't get a response for certain um milliseconds for example the so socket time out

milliseconds and set this to 20 seconds same for the request time out milliseconds and here we can then go on with installing features for example

call logging um do we have that or is it just called logging yeah logging um so that will simply log our requests which will be very useful for debugging we can set the logger here

to an object of type logger from Kor here we can then overwrite this log function and let's just use a simp super simple logger by printing these um so by

printing the log message and we can say the log level that we care about is log level all so we want to see the whole request that is being made in our lock

hat and lastly we want to say default request um so what we want to configure for each and every request in our application um and that is that we want

to set the content type to application Json because every request is working with the Json data and we we need to um press that here with a content type

header so we uh the client knows what kind of data it can expect and now we can use this Factory to create an instance of our HTP client here in our

app composable by saying um HTTP client is HTP client factory. create and well which engine do we actually pass in here

this engine as I said is dependent on the platform on Android we can use a different engine than we can on iOS that we can on our desktop so we somehow need to pass these in from the outside here and for now let's just pass these in

here that won't stay like that but the engine will be um an hgp client engine and from wherever we now call this app composable in our platform specific code

um oops this does not look correct let's do this um here where we create this app we can pass in um an engine in form of

remember block and we say this one will be okay HTTP the client engine here not create so for Android we can use the OK HTP

engine that drives our requests let's get rid of the preview here then we need this app composable in desktop on desktop we can also use okp since it's

also a jvm environment so here we can say the engine is remember OK hp. create and lastly in iOS that we

hp. create and lastly in iOS that we can't use okp since iOS is not a jvm environment but here we need to use the

Darin engine so remember this will be darn. create Darin comes from our

darn. create Darin comes from our libraries so in case you need to add something like that um just to a specific platform here in your build. Gradle file

in your sour sets you have this native main for example which works for mechos iOS and so on here we then add this Darin engine um that is then really only

available in our iOS main module this way we we should now be able to go to application and construct this HTP

client here with our engine and I think this should be ready to try out um it says we have an error here but apparently not let's run this let's see

if we can search for books um no okay we do get some import issues here no favorite books yeah let's get rid of that or what is the issue with that okay

apparently did I remove that resource let's quickly take a look maybe I accidentally removed that in our strings XML apparently I did let's duplicate

that again no favorite results um no favorite books yet just like that try this again and H okay do we need to make

this again no favorite books did I call it the same way ah no favorite results I'm dump sorry no favorite books that is how it was called here uh do this one

more time yes it's launching and it's loading and it's crashing let's take a look we get a big

error message okay what's the issue let's debug This Together uh we do get a serialization exception um Json decoding exception okay we actually need to catch

that as well um unexpected symbol o in numeric liter at have cover Edition key what's that so the way we need to debug this is we need to take a look at

exactly this um sequence so it says okay we had the stocks field which contains our search results um the first result is affected and specifically the cover

Edition key is somehow off there is a certain character in that cover Edition key that it um did not expect specifically this o my guess would be

that I'm trying to make this an integer maybe and it's actually a string um but we can only take a look to find out search book dto we have this cover

Edition key ah I I said it this must be a string because um yeah paring a string into a string is obviously not an issue but paring an O into an integer very

much is so let's relaunch this and take a look there we go we again see our dummy books for a moment but then we are loading some results ah there it is we see our books um we can probably give

this um the loading indicator some some width so the same width as the book covers so these um widths don't vary here this is pretty much a working book

list awesome and if we now search for something um there aren't any books for your search query okay we search for Harry Potter again 500 milliseconds delay and then we search something and

there it is there are our Harry Potter books Isn't that cool can't yet click on these nothing happens um but if we also take a look in lockhead we will see all of our requests being made and we just

get the fields that we actually asked for very cool I think this is our first real uh Milestone here in this project has been quite a lot of work but we

we're not where we want to be yet so I think as the next step and now that um a very important part of the functionality is working as the next step I would just organize the code base a bit better um

so that means I want to set up coin for um having a proper dependency injection setup that means that we don't want to do something like this and initialize everything in our composable but rather

have our dependencies our actual class instances um at an organized place and um be able to easily inject these wherever we need these after that I would like to set up our navigation graph so that we can um yeah that we

have everything we need in order to get into the detail screen and actually also jump from this one to the detail screen um when we want that okay in order to set up coin our dependency injection framework and I will also explain in

short at least what that is in a moment why we apply dependency injection in the first place but in order to set that up we need so called a modules um which are in the end just containers for a dependency so what is a dependency um

I've mentioned that before a dependency is nothing else than an instance of a class that we might want to use in our project so just something another object might depend on that's why it's called dependency don't confuse this with

greater dependencies um so that's also kind of a dependency since our project depends on these libraries but in our code when we talk about dependencies we mean instances of classes like this bookless view model this repository this

data source and so on and the reason why we want to have a dependency injection setup so dependency class instance injection passing this class instance to another class instance passing This Book

Repository to this view model for example why we want to have this is um well because this right here if we would have this in our real project would be rather terrible because while this works

what would happen if actually another class would actually also need to make use of exactly that repository instance or that data source or our HTTP client so it's very likely that if we have multiple different data sources in the

project project then all of these could use the same HTP client because the config of the HTTP client is likely going to to stay the same across multiple data sources so we need a central place where we manage those

dependencies and from where we can then take these and pass these wherever we need them this will also make things much easier when we actually want to test our code because what happens if for our test code we actually don't want

to use our real repository but a fake one that does not make the actual network holds but just simulates them for a test case then right now we would be forced to always pass this default repository here but if we use a

dependency injection framework or just proper di which in the in the in its simplest form is really just passing stuff to Constructors if we do that then we're also flexible in deciding what to pass where so as I said with coin our

chosen dependency injection Library which is the typical Library we use for that in a KMP project with that we need uh so-called modules so module just a container for such dependencies where we

kind of teach coin how certain dependencies how certain class instances are created that that's of course what it needs to know it can't guess how or HP client looks like so in our common

main we want to create a new root package here in book pedia called di dependency injection and in this DI package we will create a modules file and now if you are maybe already

familiar with um di on Android for example then one New Concept that we will actually have to uh have to work with in C multiplatform projects like here is that certain dependencies

certain class instances are only for specific platforms or the the um actual creation the instantiation process of these differs depending on the platform but some of these we can also um just uh

create in our shared code right away since for all platforms these are the same so these are just some things we need to keep in mind here and that's why we'll have on the one hand a sheared module which is the container for our

sheared dependency so this will be a module and in here we can now Define Singleton dependency so where we really just have a single instance across the lifetime of our application with single

or single off so with single we actually get this Lambda block here we can now say okay this is how our class is um instantiated for example Kor remote book

data source we need an HTTP client which we don't want to create in here because we want to have that as a standalone dependency so we can also pass it to multiple different instances so we might want to first of all make a single for

our HTTP client and we create that with our factory. create and well what is the

our factory. create and well what is the engine of that the engine is the first thing that not differs depending on the platform so you've noticed and what we've done before for iOS for example we

want to use this Darin engine while for Android or desktop we use the okp engine so this dependency the engine differs depending on the platform and this of course a problem that we only face in a

multiplatform project and not on Native Android for example but we know we definitely need to use okp so this is something we will work with in a moment um for now let's just write get here

which um kind of instructs coin to get a dependency at least to try to get it dependency um but if this dependency is not provided that's how we call it um with such a single block that Coen doesn't know these then it would crash

obviously so we still need to keep in mind that we also provide this dependency um at the later point we will do this after declaring the shared module but now that we have a single

block for our okp client or or um kto HP client of course we can also say get in here in the K remote book data source because now coin knows okay this is how

I create an HTP client and here we need an HTP client so let's just take this one and pass it in there that's really the the whole Magic to the eye and before people comment yes coin is

strictly seen um as a so-call service locator so we just have a pool of objects and when we need one we just um try to access it from that pool of object so it's not really di but we get the same advantages from using coin um

than from a library that uses real di like dagger for example on Android but we can improve this a little bit um since now if um if we actually construct this ker remote data source and our

Constructor actually changes so we might want to add another field another class instance that this might depend on then we would always need to go back here and add another piece of get here this is

not ideal so instead what we can do is we can use single off which will just instruct coin to try to create the instance with dependencies it has already available so we can say Kor

remote book data source put a double colon in before and now for all the Constructor arguments in here coin will just try to to check if it has these available if we provided these before something we also want to do is want to call binds

afterwards and make this a remote book data source because this is the actual type of the dependency we pass um but this is the implementation so here in

our repository where is it book data repository default Book Repository here we actually don't pass a k to a remote book data source specifically but the abstraction of it and to um instruct

coin to use this specific implementation we use bind something we then want to do is we want to provide our default repository we always need to remove these double colons before um then input

this and then prepend these then bind this with our Book Repository this time and then the last thing we can provide for now is our view model view models are provided a bit differently here in

coin and we need to use this view model off this time and say bookless view model with double colon oops double colon in before this does not consist of an abstraction so we don't need a bind

here but what's missing now is this engine so we need to kind of instruct coin to to say on Android and on desktop we want to use okp and on iOS or or native and general I want to use

Darin and here we need to first of all introduce a concept that c multiplatform projects introduced and that's a new keyword or new set of keywords called expect and actual so the thing is this

engine that we create here is used in our shared code because every HTP client instance certainly needs an engine but which engine we use that differs on the platform so it's not the case that we just need an engine on Android then we

would just put it in Android main but that on all platforms we need an engine but the engine implementation differs and whenever you have that problem we need to work with an expect Val which is really kind of an abstraction in our

Shar code where we expect to have a certain type of object which we might want to call platform module which is a coin module which then contains the platform specific dependencies and if we

declare this as an expect Val then it's really comparable to an interface where a c M platform now expects us to have actual implementations of this platform module for each and every platform where

we say okay for Android the platform module looks like this for iOS looks like this and so on you can also see this here um we can click on ADD missing actual declarations we want to have

these for our iOS main desktop Main and Android Main and then click okay and there it is here we have a modules Android with an actual Val now where we have the actual implementation of this

module module desktop and modules iOS so for Android as I said we want to make this a module and here we can now

provide our Singleton Engine with okp because here we are in an Android environment now we are in in our Android main source set or module and therefore we have access to jbm dependencies it's

important that we also specify that this is an HTP client engine so whenever we try to inject that it will use the okay HTP engine we can then copy this because

for desktop it will be identical here we also have access to okp but for iOS you will notice if we paste this it won't be able to resolve this because in iOS we don't have access to OK HTP here instead

we're want to use Darin and now that we have such an actual implementation of this platform module for every single platform um c multiplatform will know okay for iOS we we will use this engine

and for all the other platforms we will use the OK hdp engine other than that what we need to do is we of course need to instruct coin to use these modules right now those are unused we can go to

our common Main di created an init coin function function init coin which just initialized coin and tells it which modules to use we can pass in a config

which is a coin app declaration nullable and null by default um this in the end is um kind of a Lambda hidden behind I don't know what that is the type alas um so it's really just a Lambda here that

extends coin application that lets us to that lets us uh configure a specific um coin set up for a specific platform you'll see in a moment how this works but we just want to call start coin to

start our coin um Library we then call config do invoke with this so we get this um coin application scope here and

then we can declare the modules we want to use with our Shar module as well as the platform module so we now do have access to the platform module in our Shar code because we used um xact Val

here we don't have the actual implementation of this here but we do have an actual implementation for every platform so we can certainly make use of that in the the shared code as well so where do we now call our indit coin function well every platform now has a

certain entry point on Android that's for example our application class which has an oncreate function and um we in the end just need to call this once per um app launch so for example for Android

in book pedia we can create our book application make that a class make this inherit from application and then we can overwrite on create in here and this is the place

where we can now call init coin you can see we can pass in a Lambda here to further configure this really just for Android this time so if we had a certain module that contains dependencies that we just want to use on Android not like

our engine that we use on all platforms but if we have dependencies that just that are really just required on the Android platform we could also add these here with uh further modules we don't have this in our case but something we

do need for a coin on Android is um on Android we have this concept of a context um which is really unique to Android and we need to instruct coin to know this context with Android context so that if this context is needed to

construct certain dependencies coin knows how to retrieve this so here we can just pass this book application we also need to register this application in our Android manifest so we can open this in the application tag we say name

book application so this is how we start coin for Android we can then do the same for desktop here it's a bit easier open our entry point this main function this

application here is uh composable so we don't want to call start coin in here which would be a side effect that would call with every recomposition but rather that cut this out and make this a normal

function body and just start coin before um drawing our window here we don't really have any extra config so we can just call start coin like this um

what is the issue um actually call it init coin of course init coin and then we don't have any issues with that lastly on iOS main open the main view

controller and here in this composed UI view controller we do have a function this configure function which also calls just once where we can init coin so this

should be everything we need in order to be able to inject these dependencies that we've provided in coin we still need to adjust our setup here so this application class should not now take an

engine because it doesn't need that you can remove this go inside of application remove the engine here then we suddenly can't construct these dependencies here anymore which we don't

need because we have these in our coin module so let's rather get rid of all that because the only thing the screen the actual UI now needs is the instance of the view model and the view model gets all the rest of our dependency

chain it gets the repository implementation the data source um the HP client all behind behind the scenes but in our UI we really just need the view model and this works with Val view model

is equal to coin view model this is just used to um check if there is a view model implementation of our book list view model and if so coin will retrieve it and um properly bind it and couple it

to our composable screen we can then pass this here to our book list screen route and here um since the book list VI model takes in the repository uh a coin will automatically also create this

chain um of dependencies that the view model relies on depends on and inject these automatically so this way we should still be able to actually launch

our app and use it just like before where we had this remember block here no we are not um because we did not fix these um remaining issues where we pass in our engine of course let's remove

this same in desktop desktop main here try this again checking this on my device there we go it's launching and

that looks hopefully good I'm not sure if it crashed because it flashed a little bit um was there a crash no it seems like it was successfully loaded if

we oh that's a very long lock here um yeah let let me just relaunch this quick then we should see this once and for all there we go yes okay the flash was just because we have our dummy books which we

can actually remove now since we are able to load books from the API so where did we declare these I think in our state class did we book presentation book list State yeah let's remove these

dummy books and just make this an empty list because if we then uh launch this also know a preview oh on the preview we actually do need the dummy books um let's make let's put these then as a

private Val on the preview but not assign this to our state um so we go to our preview we need to extend this a little bit here um in our preview we can

then put our list of dummy books make it a private Val um so we do have it for our preview but not for our initial State let's open the preview again like

this if we now relaunch this then we should not get this flash with our dummy books but we should immediately see the loading indicator um no we did not because the

loading value is false by default let's try this once more yes now now we get it imediately and then we load the books and we see everything very cool so we

now have our dependency injection set up in place and when we now add more classes more instances to our code it's very easy to extend this by just defining the Declaration here in our uh

modules in our Shar module or platform module depending on the dependency the next thing that I would like to do as I said is setting up navigation so that that we have everything uh set in place in order to then get to the detail

screen where should navigation happen in a composed multiplatform project and what do we actually use for navigation um well I would recommend to use the official uh navigation library that comes from jet Brains it's usually a

good idea to stick what comes from the makers of something not always but for navigation I would I would say so there are um other navigation libraries that you can also use like a voyager

decompose which are also um quite decent but since I'm just just so used to using this navigation library from Android I also want to use it here and our app composable is the perfect entry point for that so let's go in here and first

of all let's wrap everything with our material theme so we also get proper theming values and in here we can then declare our nav controller which is the central instance that we use to um jump

from one screen to another we we get that with remember nav controller and we can then declare our nav host so the nav host is really just um the place where we um say okay on na controller this is

our start destination our very first visible screen and here we have all those composable screens that we want to have in our app and that we want to be able to navigate to so here for the nav host the nav controller is just our nav

controller we've created above and then we want to have a start destination which is of type any here because compost navigation a compose multiplatform navigation now also

supports the type save approach so that means we have really type saave routes without having to Define any um string routes anymore like a we URL that was very error prone but now with typ save

navigation this is a lot better and I would like to Define these in in a separate file um let's actually go to our bookia package and create an app

package um so this is just the logic that is used to to Really wire things together so typically this would be navigation this would be just initial setup of something and this app package

is allowed to access any other features and packages in our project here we don't have these con strains with domain data presentation um because if we think of a multimodule project on Android then

we will also have an application module which is the entry point for um for our Android app where we would Define the navigation all that stuff but that is really something for the essentials course it's a 23-hour course bundle this

would be too much for for a course like this but in this app package we can move in our app composable and then also Define something like a routes class sealed interface rather so we have a

sealed interface for our routes in our case list screen detailed screen or we can say we have a data object for our book list which is let's call it route

um because we always deal with a single route and this also needs to be serializable because um composed navigation will then uh figure out a way to serialize this and pass it um to

another screen in this case our booklist screen does not have any screen arguments but the second screen our book detail screen does have arguments which is why it need to be a data class we call it book detail because the argument

we need on the book detail screen is the actual ID of the book that we clicked on so this will also be a route serializable but this way we have types saave arguments we can create a book detail instance say want to navigate to

this specific book um and then the navigation will just work I also like to bundle each feature in an app into a navigation graph so graph is really just um a bundle of multiple screens that all

belong together where we say data object book graph which is also rout and these are already the routes we need in this app so here in our nav host we

can say the start destination is our route. book

route. book graph then here we want to Define this book graph with a navigation function this is how we Define a navigation graph

here in compose we have to um pass the class that makes up the the graph so it would be route book graph again a graph since it can consist of multiple screens therefore also needs a start destination

so the first screen that shows up when we navigate to a graph which in this case will be route book list and here in this navigation graph we can now Define our actual screen destinations with

these composable blocks we also need to say this one is for the book list screen which we can then put in

there and then we have one for the book detail screen which we have in here we can then easily extract the arguments um that we use to navigate there from arcs

or this actually just the backstack and entry but the ARX we can then retrieve with entry. to Route which will then return

entry. to Route which will then return our route book detail and then in our ARS you can see we find this ID so we can work with that something we now can

already do here in our navigation graph when we click on a book we do get the book reference what we certainly want to do is want to use our nav controller and now navigate to our details screen so we

say nav controller navigate to uh navigate just yeah navigate and we want to get to our route book detail screen and we now need to pass in the IDE of

the book want to navigate you and guess how we get that well from this book reference of course so we pass in book.

ID and then for now we can really just put in a dummy box in here for the book detail screen modifier filmax size or so and then I actually need to import this

might also want to Center something here where we say content alignment center and here we might have some something like a hello world text or we

say book detail screen the ID is ar. ID and if we now actually launch this again and try this

out we should be able to navigate to the detail screen so this is really how how easy we can now set up navigation with a compos multiplatform thanks to this type safety before that was really a pain we

now have a book and when we click on that we get to our detail screen so you can see the ID is actually SL works blah blah blah that's actually something we need to adjust Because the actual book

ID is what comes after this slash works so we do need to adjust this in our book meppers since this uh Slash Works SL ID um comes from the API but we only want

to have this in our project so we want to go to our mappers where we take the API book and map it to our domain book so here for our ID we don't just want to

take the ID but want to take the substring after last and pass in this forward slash so we will just take the string after the last slash in this uh in this long string so

if we relaunch this then we should be able to just see the real book ID after loading some books click on this one here yes now that looks much better however one problem we have with this

API is that there is no real dedicated book detail endpoint that contains all the data we need so there is a book detail on point but the only kind of extra data that it adds is the description but it then lacks all the

information about the book that we have fetched before like this one here so the only real way to get this information of a book again on the book detail screen is that we would have to to execute the

the search for that book again and then refer to the first item of that book list which is of course everything but ideal because we're making an API call for a book that we have already fetched well if you would now have um a real

local cache for all the books so you have a you fetch them from the API and the moment you you load them you just insert them all in your local cache then you could just load a book on the detail screen by its ID from your local

database that would be super fast and this is the typical way how this works when you have a local database and a remote API however we don't have that we don't cash all of our books in our local database but just the favorites so we

somehow need a better way a different way to actually still get the book information that we've already loaded on our detail screen while not having to pass such a big book difference uh such

a big book instance as a navigation argument because generally it's really discouraged to pass Big Data um as navigation arguments since there are limits to how much we can actually store in a bundle um that's how it works

internally on Android so if this book gets more and more Fields over time and especially if it has lists like authors where you don't know how big these might be then in certain cases your app could crash because uh it it's just too big to

navigate with that object and whenever you have such complex data that is hard to serialize or that is that that could just become very large when serializing it but you still need to share that between multiple screens then is

typically a good idea to implement a sheared view model so while our normal view models that we've declared in our uh app here the The booklist View model that is really just bound to the

backstack entry where we're currently in so from this book list screen it's really just Bound for the book list screen and when we uh when this screen is cleared from our backstack then the view model will also be cleared but what

we now need is we need a view model that both book detail and book list are able to access so the same instance of the view model needs to be shared between two screens and this works when we have

a navigation graph because a navigation graph also has a backstack entry so we can take a view model and scope it to the entry of the navigation graph instead and the navigation graph will

stay um alive as long as any of these screens in the navigation graph stay alive and that's exactly what we want so if we now go ahead in our book feature in our book list package actually not

book list but just presentations and now we have a shared view model we can call this shared um or just selected Book View model model this will be a super simple view model it's really just has

the purpose of sharing data sharing the selected book we do declare this in here a private Val selected book which is a mutable State

flow um which is initially null so initially we did not select the book but if we do it's certainly a book instance and then the public variant of

that selected book is oops selected book as state flow and then we have a function on select

book pass in the book n and here we really just update our value with book so this is how our Shar view model looks like that's really all the code we put in this um since it

really has the purpose to just share the selected book and this is what I can highly recommend you if you really need a shared view model which you often don't very often um there are much better ways to share data um like

passing it V navigation arguments like reloading it then from the database like just persistent starage in general but in cases like these a shared view model is very reasonable but what many people

would now do is they would create one big God view model that is shared between these two screens which has all the logic for the book detail screen and all the logic for the book list screen but I can only highly recommend you if

you create a Shar view model just created for the state that needs to be shared and rather use two view models per screen than one that contains all the logic for all screens so with this selected bookview model which now has to

be shared we can go back to our app composable and declare this here and for that I will actually um create a little utility function which you could use for any shared view model on um Android or

composed multiplatform will be a private inline function actually which will get a reified t of type view model so um it works for any type of view model in the

end it will extend a nav backstack entry and we call this shared coin new model because that's what it is Alt Enter to import this nav backstack entry and in here we then pass in our nav controller

reference because that's also needed it will then return our type T so the view model we want to create we want to share with that and first of all we now need to use the uh nav controller or rather

the nav backstack entry which is the one where we call this function in so either the nav backstack entry of our book list screen or the book detail screen we now need to use this nav backstack entry to

fetch or to retrieve the parent backstack entry so uh that will be the entry from the book graph since this is the backstack entry we want to scope The View model to so here we can say Val nav

graph route to first of all fetch the route of the parent nav graph that's equal to destination so the current destination book list or book detail

parent which is an an AF graph. route

and if that doesn't exist we can just return return a coin view model here of type T we also need to make this a composable here by the way away composable function then we get rid of

these issues and now with this navra route we can get access to the parent entry so to the actual backstack entry of our Paran navigation graph we can say remember we pass in this as the key so

the nav backstack entry um so whenever that changes this will reexecute and here we say nav controller. Gap backstack entry and we

controller. Gap backstack entry and we pass in the navc route so we now have the back entry of our navigation graph in this parent entry and we can then

return a coin view model where we say okay the view model store owner is now the parent entry so we explicitly scope this view model to our parent nav graph

rather than to this navb entry um that would just scope to a single screen and this function really works for um any type of navigation graph and any um screen feature structure that you have now so you can really just copy paste

that in your project and it will work what we can now do is in here book list we can say Val selected Book View model

is equal to um it so the back stack entry that shared coin view model the specific view model that we

want to share here is the selected Book View model and here we pass in the nav controller let's also move this somewhere down here it's a bit unlucky

like this I think it's a bit better and if we now have the exact same line down here in our book list book detail screen here it's actually called entry let's remove this um we can also remove the arcs I don't think we need these here if

we now call this here this view model instance will be the same as this view model instance so this one is now shared while this one will only live as long as the book list screen is on the back stack and what this now allows us to do

is that we get the full book information we clicked on here in our detail screen so what we can do is instead of just displaying the ID for example we can

also say we display the whole book so actually have our state here Val selected book by selected Book View model selected

book collect a state with life cycle so we just collect that state flow as a compos State and we gen then simply um print this book here if we then click on a book we not just want to

navigate but we also want to update the current currently selected book in the shared view model so we say selected Book View model on select book and we pass in the book and if we get back to this book list screen then we want to

reset the selected book again and this works with a launch effect block with the key true since this will fire whenever this screen appears somehow um so either when it launches the first

time or when we um get back from uh from another screen so here I want to say selected Book View model unselect book and pass it null because whenever we on the screen we want to reset the selected book since no book is selected at that

point anymore and if we now relaunch this then we should be able to really get the full book information here in our detail screen which you can then immediately work with um no it actually crashed let's take a look what that is

the very but bottom here there we go there's our crash here no definition found exception oh okay um maybe you can already guessed what this is so it says coin was not

able to found uh to to find a definition for the type selected Book View model and we should check our modules configuration and add missing type or

qualifier so what coin complains about here is uh you actually want me to inject a view model that I don't know how to create so what we need to do is need need to go to our modules and actually have a reference to this view

model here with this view model off so for each and every view model we need to say we do have a selected view model a selected Book View model now as well and the moment we register this here coin

will know how to create this in this case it's super simple since it does not require any arguments still we need to do that so relaunching

this there we go our books are loading and if we now click on a book then we get the full book in information here on our detail screen so this is now what we can work with on the detail screen as

well if we select a different book for example this one then we also get the different book information as you can see so now with this information we can work with on the detail screen to Simply display those book list details or the

selected book details in a bit more visual way so let's get into that book details screen right now where do we put this obviously it's part of the book feature it's a screen so I want to put it in presentation and since it's a new

screen we don't want to put it in put it in the existing book list package screen but we want to create a new one for that so new package bookor detail this one might also have a package for components

so for all reusable components that are just used for the book detail screen and in this screen we want to create let's start off with our state so book detail State same structure again just like for

the book list screen just at this time we talk about the state that we need for the book detail screen so any values that can change and have an impact on the UI on the one hand we have an is

loading value um so oops not a book um which is false initially no actually true because even though we have most of the book information immediately available on the book detail screen there's still a missing piece we have to

fetch and that is the book description so that's that comes from the from a separate API endpoint and for that we can display a loading indicator we can then have a Val is favorite that can

change uh which we will then use whether we want to make the um the heart icon filled with red or not and lastly we also do need the book that was selected

in here which is null initially um so you might now wonder okay don't we already have that in the selected Book View model yes um but the logic to actually display that book and to do something with that just for the book

detail screen will be in the book detail view model so we need to kind of observe the selected book here in our screen um and then whenever that changes also forward it to our book detail view model

so uh the the selected book is kind of sync between these two next up let's go to book detail and create a book detail action so our sealed interface that again couples what a user can do on that

screen on the one hand that will be a data object on back click book detail screen uh book detail action I mean let me actually launch the uh other app so you can see what we're actually building

here there we go it's loading if we now click on a book oh you can see I still have a the red red box here when I debug this petting issue never mind let's

click on a book you can see here is a back button we should actually make in white or so um we will do this for this project but uh when clicking this we obviously get back and here we have this

heart icon when we click this we Mark the book as favorite or not so this is another thing we can do by saying data

object on favorite click Boger detail action and lastly a data class on

selected book change we pass in the book this can be none null here um since on the book detail screen we certainly know that we um have a book

selected book detail um action it is and there we go that's already everything we can do on the screen and I would say first of all we get started with creating the reusable components um so specifically that will be those chips

that we have here together with these titles and we will create a layout that will help us to draw this blur background here a little bit better and we will do this in book detail

components so let's first of all create a book chip so for one of these blue boxes make this a file book chip we'll take a modifier for

sure it will take a size so certain chips as you can see are larger than others so the language ones are smaller than these ones so we want to pass in a chip size

which we can create here with an enim class uh chip size where we have small and regular for example so by the way a recommendation I can I can also give you

um some people might work with a Boolean here rather so they might have something like is small um as a Boolean which is true if if we're talking about a small chip and false if

we're talking about a regular chip but whenever whenever you're working with a Boolean condition like this one where the negated variant of that is not obvious to someone who does not know the code so where it's not obvious what is

small actually stands for because that is not obious does it mean the chip is big does it mean it's regular does it mean it's XXL we don't know while for a Boolean like enabled it's completely

obvious that when we NE negate this it's disabled because there are only two different variants of this when it's not obvious even even if you just have two options in your case work with an enim class instead um this makes things much

more readable and you can extend this much more easily so we want to work with the chip size enom class here instead and by default it's Chip size um Regular

and lastly we want the chip content here so what is actually drawn in the chip the the texts here to be a composable Lambda like this and this is in the end

quite simple what a chip is we can make this just a box we say the modifier is our modifier here we say within again where the minimum width of this chip is

dependent on our size our chip size um oh I actually kept it on is small so this would be the size of course so depending on what our size

is can add the remaining branches here if it's small then it should be 50 DP wide at minimum if it's regular it will

be 80 DP we can then say we want to clip this so we have rounded Corners to a rounded Corner shape of 16 DP we then give this a background of our light blue

and we add some padding specifically vertical padding of ATP and a horizontal padding of 12 DP and then we get the look that we have here depending on our chip size

furthermore want to Center everything in this box here Center and then in here we really just have the chip content Lambda that we can call and add to our box that way

we are really flexible what want to show here um since for the rating we actually have a row with a text and an an actual icon for the other uh chips we we don't have that where we just have a text

let's now next come up with an actual um composable that helps us to show these chips with an actual title this is what I would call titled content also here

titled content make this a file composable titled content which will obviously take the actual title on a show it'll take a

modifier and again a Content Lambda in here this will just be a column so makes sense I think that the title and the chip are now arranged in a columnwise

fashion the modifier will be equal to modifier we want to set the horizontal alignment to alignment center horizontally and then in this column we

have the text that says the title we can give it this text style from our theme material theme typography title small and after that we simply draw our content um it's a super simple

composable but since we have multiple of these chips here it makes sense to create a reusable composable for that okay the next compos we will create will be a bit more complex because it will be for this blur image background so this

will be a layout where we can then pass the rest let's start off with that again in our components package since it's just needed for the book detail screen

and we have our blurred image background we make this a file composable blurred image background so what does this blurred

image background need it definitely needs to know the image URL which it should load the image from this can be null it needs to know whether we Mark the book as a favorite or not um since

the book cover will also be part of this background we get a Lambda when we click on favorite on favorite click and we also draw the back button there

so on back click and in here we will we will do something very similar to what we did before um that we work with these image load results um since we need to load this image now with coil again but since

coil already cached this image on the book list screen this will be loaded instantly from our cache we still need to of course instructed how it should be loaded and what should happen if it failed to load so our image load result

by remember again oops not remember on back click where we say mutable State off which will hold a result a carton result This Time Again of a painter from

compose UI Graphics which is nullable and null by default then we can hit Alt Enter twice to import this and the actual painter reference will then be remember async image painter where

the model is our image URL and on success is a Lambda we need here where we simply update our image load result with whether um the image

has valid Dimensions so it result intrin what result what was it called actually it. painter it was intrinsic size let's

it. painter it was intrinsic size let's save this in a variable so well size so if the size that width is larger than one and the

size. height is larger than one pixel

size. height is larger than one pixel then we say that was a successful result with the painter otherwise that was not a successful result so result failure

and we say the exception is invalid image Dimensions so let's first of all think through how this should work with this blue image background so in the end

we will have a big box that just Surs our whole screen in this box we will have a column that consists on the one hand of this blurred image section as well as this white content section of

the screen the Blurred image background is in the end just another image we we load with coil so we just load this image from our cach twice with the difference that here we will just blur this then we will have an invisible

spacer for exactly half the height of our blurred image background so we then draw our um image card or actually image here and then everything that comes afterwards will just be dynamic content

that we pass um that we actually need to pass here to our blurred image background so we need a composable Lambda so that we can add our screen

content here so let's first of all start by defining our box um this really doesn't need any modifiers we will figure out the rest here inside the box so let's start off with a big column

this column will fill our whole screen size fill Max size so this will now be the column that consists of this blurred image background and this white section

then for this blurred image background we will make use of a box again which will get a modifier with weight 3F so it

will take up 30% of our height we say fil Max width to just fill the ho with a for screen and we give it a background of dark blue um this will only become visible if there is no image to load for

for the blue background actually not fully remember what happens when we click on a book like this yes then you can see um here the uh blue background will actually show because um blurring

this error image would look quite ugly so um that's something I want to do inside this box we can now draw our image so we have an image

composable give it our painter um so the image load result or rather we need to have a null check here whether the image load result

dot get or null which will return the painter if it was a successful result if that exists we can draw the image so the painter one

here then the content description can be our book title um do we have the book in here um no then let's let's just say we

have a string resource um string resource r. string. book cover let's

resource r. string. book cover let's call it like that we can then go to string resources compos resources values strings XML and maybe we can just add a

bunch of resourc sources here that we need on this screen anyways so we don't have to rebuild this all the time um so let's just duplicate this a few few times here on the one hand we have this

book cover string book cover for the content description we then have our rating string we have our pages one languages

um we do have synopsis we do have a description not available and we say description not available and I think this should be it

for now I'm pretty sure we will need more string resources in this course Maybe not maybe we do um but now we already have a foundation for the screen let's rebuild this get back here then we

should be able to um import this book cover yeah now it was actually quick Alt Enter to import this we want to pass the content scale content scale

crop content scale crop so this will simply make the image fill the whole width of our um image here um and then cropping the top and bottom edges so that it perfectly fits and lastly very

important the modifier since we want to fill the max size of our parent box we then want to blur it with a blur modifier give a radius of 20p so the more you choose

here the more the blur will be applied and that's already enough to draw this blur image um pretty simple I think below this box which is now in our outer column so where we have this blur image we now want to draw this white section

so the actual content section which um spans 70% of our screen so here we will simply draw this it's really just a container composable here with a

box modifier give it a weight of 7f this time make it also fill the max width of our screen and give it our background of desert white and I think we can already

try this out I think from now on we can get rid of the preview here and rather just launch the app since we already have valid data available so I will um remove this one here and rather go to

our screen which we don't yet have I think um let's go to book detail and create our book detail screen composable again same pattern as

for the list screen book detail screen scen root which does take an instance our view model which is our book detail view

model can also quickly create an empty one for that here in book detail book detail view model will be a view

model we will have our state in here um our book detail state it is we have a public state um underscore State s State flow

and we do have an onaction function that takes in our book detail actions this time so just an Mt view model let's also immediately reg register this here in coin so we don't forget about that

duplicate this make this a book detail view model double colon and we're safe so now in our book list screen again h

no book detail screen we can observe our state from this view model collect as state life cycle I'll enter to import

that and then we can come up with our private function book detail screen contains the actual screen screen content where we pass in the state we

pass in an onaction Lambda with book detail actions and we mark this as a composable of course we can then um add the screen here we don't need a modifier for this

one but we do need a Lambda for on back click since in that case we need to pop the screen from the backstack and in here we can then say book detailed

screen pass in our state on action Lambda where we do get our actions want to check if that action was to go back

so if that is a book detail action onback click which in that case we want to just um trigger our onback click function otherwise not do anything

so those are really just all the actions that we um need to act on in the UI outside of the view model so everything else we can then submit to the view model so we can handle and update the

state here so now in this book detail screen we should be able to create our blur image background so we can um just preview that a little bit in our real

app the image URL will be State book image URL I'll make the null check

here then is favorite will be State book is uh what is it no state is favorite it's not part of the book on favorite

click here we will trigger on action book detail action on favorite click then we have on back click where we trigger on action

book detail action unback click we have the modifier where we just say modifier fil Max size and lastly we have our um content which will be

displayed for this uh in this white box here so everything starting from Cartland programming we didn't make use of this Lambda yet anyway so we can leave this empty for now so the only thing that's left in order to try this out is taking this book detail screen

route and adding it to our nav host in application book detail and here we can replace this one with uh what is it with

the book detail screen route where the view model is now a view model we can create here just with coin view model since this is now bound to the screen

again um called book detail view model pass this in here and our onback click Lambda when that is called we can say nav controller

navigate up so we just get to the previous screen what's important is that whenever the selected book changes in our selected book shared view model we also need to um submit that to our um

screenbound view model which we can do with a launch defect block so whenever this selected book changes we say view model or detail view model on action book detail

action on selected book change and we pass in the selected book actually only if it's non null um so selected book question more glet and then we can pass

it here instead so this way we just know the selected book in our um screen bound view model so we'll also see the book information in here since we passing the image URL as favorite and so on so I

would say we try this out let's launch this and hopefully see a valid blured um image background we're loading at least no crashes this time click on this one

and no we don't see a blurred image background why is that so apparently loading failed so this code seems to not execute what happens if we just use the

normal painter here without checking for successful result like this click here and no that also does not work that is a little bit strange can we maybe see

something in our um locks here I don't know if we filter for coil Maybe um let's actually print these um these logs

here so if there is an error then we can say it do result. throwable print stack Trace so we can um see what went wrong

here going back to our lock cat we click on a book and there we get our exception um coil null request data exception the request's data is null so it seems like

the image URL that we pass in here is null why is that I know why that is um because in our uh our book detail view model we don't yet accept these

actions and update the selected book in here in our book detail State when that changes of course that makes sense so if the book detail action is actually selected book change we need to update

our state with that so we need to update our state with it. copy and our book here is now our book that comes from the action um what else do we have here if

book detail action on favorite click which we don't Implement yet and otherwise we ignore that since the back clicks are handled on the UI level this should hopefully fix it there we go

loading this here it is and yeah now it works now we do see our blurred image background let's see how it works like this with our error images um yeah that's also how it's intended because for error images we don't want to have

this blurred background very cool so we can leave this as it is and get to working on our below image background since we're obviously not where we want

to be yet so I've opened the sample app again the next thing that we want to add is this icon button to go back and that is also why we have this outer box so we can easily align things um like this

icon button in any of the corners of this box this is very easily um so just below this column right here in this box we will have an icon button when we

click this we say on back click and the modifier here is modifier Dot align so we want to align this in our box at the top start so

right here as you can see we want to give this a bit of pading um specifically the top pading 16 DP and the start pading of 16

DP so it's not um exactly in the top corner here but a little bit paded to the right and bottom and we also want to add our status bars pading again um to yeah that will just be the the height of

our status by here so we push this to the bottom and actual icon we use for this icon button is an icon with an image Vector

icons default back um error back or it's in this Auto mirrored thing again Auto mirrored do

fil back arrow back there it is content description is ah I think I missed this

resource um R string go back okay let's add this and also add the content description for the favorite button in our string resources oh I

closed it and dump uh here there we go duplicate this twice we have the go back content description as well as Mark as

favorite or remove from favorites because that actually differs depending on the state of the um icon

button Mark as favorite remove from favorites make this again here we can then import this hopefully again as quickly as it was

before yep there we go minimize this Alt Enter import that one and as I said we want to give this icon uh let's let's say a pure white color so it's um

visible on our book background like in a in a real world app I would probably um calculate if this image is rather dark or light and depending on that um decide about the color of this icon button but

let's keep this at White keep this at black if you want um there there will always be book covers that this will be hard to to um where this will be hard to see on um but I think this will be a bit

too much for this course all right what comes next and last for this blurred image background is of course the actual book cover um together with this um with this icon button for for marking it as a favorite of course this book cover now

needs to be offset a little bit here um in this in this outer box and we will achieve this by putting this in a column together with an invisible spacer that gets exactly half the height of this um

30% blured image section so after this icon button We'll add a column that will have a horizontal alignment modifier of alignment center so the book cover is obviously centered and we will give it a

modifier of modifier filmax width in this column we will then have our spacer so our invisible spacing where we give it a modifier of Phil Max height but for

the fraction we choose .15 f um so the column actually spans

.15 f um so the column actually spans over the whole screen now um not just about this not just over this blurt image section so that was wrong sorry but it spans over the whole screen and

since 15% of our screen are just half the height of our 30% um image height here this will just offset the the image

by exactly half the height here and then below this we can have an elevated card to draw this image on which gets a modifier of modifier

with a fixed width of let's say 200 DP so that will be the width of our book cover and an aspect ratio of two to three we give this a rounded

Corner shape of um 0p Corner radius some colors where we can say card defaults card colors or actually um elevated card colors it is where the uh container

color is just transparent color that transparent since um we don't want to have a real container here this is just used to um show off a little bit of

Shadow and also be able to draw content on top of that like our favorite button then the elevation is something we want to override where we can say card

defaults elevated card elevation where we say the default elevation is let's say 15 DP and then we can give this card some content which on the one hand will of course be our book cover so here when

or we can actually also make this an animated content um which is a composable that allows us to animate between different composes based on a certain Target state

which will be our image load result and then in here we get this result and we can add our when expression here so depending on what that result is if it's

null we want to show our circular progress indicator for now but later on our pulse animation and otherwise we want to draw our image again with our

painter but here um we need to check if it's successful or not again so if the what's it called um result is success is

true no it's actually non-nullable in this case so if this is successful we use the painter and otherwise the painter resource and we pass our error

image again so here Ras drawable book error content description can again be a string resource Ras string book cover we

have a modifier which is modifier. fill

Max size so want to fill the the whole size of our outer card and then uh give this a background of transparent so in no cases we have any background of this

book cover but really just the um the image itself the content scale also um is important here which again has the same rule as for the um list screen that

if this result was successful we make this uh content scale crop and otherwise we say fit like this and actually this image um

also goes in a box since we want to um also now draw the icon button here in the bottom right corner so let's put this in a box and

then here below this image we will have our um icon button when we click this we say on favored click we say uh the

colors of this no we actually adjust this for the uh for the icon itself what else do we need here um the modifier since we need to align this in the

bottom right so we say align bottom bottom end and we want to give this this little um glowy background so um it's it's better better seeable even on a on

a red cover um and this works with a gradient so it's in the end just a a yellow gradient towards transparent and we can do this with a brush by saying brush. radial gradient so want to want a

brush. radial gradient so want to want a circular gradient in this case where the colors for the gradient are a list of um on the one hand from Sand yellow from

the center of the circle towards color.

transparent and the radius is let's say 70f 70 pixels inside of this icon button we then draw our icon with the image Vector

icons default favorite um favorite border actually um oh no that actually depends on whether we marked this as a favorite or not so if is favorite is

true then we want to use the filed favorite actually make this explicit here with icons filled favorite and

otherwise icons outlined favorite border we want to set the tint of this to color. red and we want to set I need to

color. red and we want to set I need to give this a Content description which is also now dependent on whether we mark this as a favorite or not if this is a

favorite we say string resource string uh remove from favorites because if we marked it as a favorite clicking it would remove it again um so always remember that these content descriptions are mainly used for things

like screen readers that would read what would happen um when we when we click on this button and if it's marked as a favorite we would remove it

otherwise we would add it Mark as favorite like this and that should be it for this blurred image background the last thing that's missing is that we actually use our

composable Lambda so we can also draw the remaining stuff here and this composable Lambda needs to come in in this column after this elevated card so

let's highlight this go down here and this should be the place where we now want to draw the content I think it's quite a long composable and we could argue about extracting certain parts

into separate composes but I think it's okay um might want to use this modify here as well for the Box modifier is modifier but we don't need to change this in any way let's try this out let's

launch this and we should hopefully see the same as on the left just without these texts here and the synopsis launching this there we go loading our

little books here we are if we click on that oh that looks pretty cool I think and the book cover is a little bit larger than I had it in the preview

which I don't know why from the top of my head let's click on go back that at least works why is this larger so the

elevated card has an aspect ratio of 2x3 and the issue is that this should be a height so we want this to be 200p high

but not wide but of course feel free to adjust this if you if you like this size of the book cover I think it looks decent especially with a with a shadow here um then do do so um but oops let's

relaunch this now with a height of 200 DP and it should look similar as I had it let's search for a different book um

Harry Potter again okay let's use I don't know that one and yeah that's how it looks like pretty cool I think I think it could be slightly

larger actually when I think about it um what if we make this a height of 2 70 maybe let's try that there we go cutland programming I think this looks

decent um so obviously we need to make this thing scrollable or rather the the section below here I'm like the best case would probably that we Implement some somehow that we have a collapsing toolbar but um that would also make this

video way too long and that's rather something for premium course of mine I think yeah it's slightly too large let's make this 230 and that'll be good you could of course also make this dependent on the the actual device size and so on

so to to show a larger cover on desktops than on mobile but I think this will be fine so now we can use this in our book uh detail screen and what we put in here will then show up below so if we have

our hello world text here launching this one last time before we go on with this UI here if we do this then it will show up just um after

our book cover I think this looks really cool so we can go in here and say if there is actually a selected book so if State book is is this nullable state

book yeah it is if this is not null then we show our actual book data this will be shown in a column of course which takes in a

modifier modifier is modifier fil Max with again um we want to limit the width a little bit on larger screen sizes we say

Max is 700 DP Alt Enter within it needs to be within like this we give this some padding so it's not um immediately below the book cover but just pad it a little

bit let's say vertical padding is 16 DP as well as horizontal padding um I mean that should be a bit more 240p and we can give this um oops a vertical scroll

passing in remember scroll State and then we should be able to scroll this column here so that the book cover won't be scrollable but the content down here and everything in there should be

centered horizontally alignment center horizontally so I've opened the sample app as a reference again um so we can now build this remaining part of the screen first of all want to have a title

text which will be the state book title it will have a style of material theme typography headline small and set the text align to

Center not snap position again text align Center we can copy paste this also for the description um actually the author it is State book authors and

since those are multiple authors we could say join to string which will take take the list of authors and concatenate them with a comma in between or if you say you just want to highlight the first

um authors like the main author you can also say authors that first this will have title Medium as a style also text align Center I would say then below this

text we have a row of two of our chips for rating and pages so row modifier is modifier at

some vertical padding of let's say ADP and we want to add horizontal arrangement of arrangement that's spaced by 16p so we just want to

have 16 DP of spacing between these chips and in here we can then have these chips so if there is a rating if State book average rating

exists we want to have our titled content where the title is our rating um no actually not the rating but this is G the description for the rating

so string resource Ras string rating the modifier is not necessary here so we can just open the composable

content in which we will have our chip or book chip how do we call it book chip yeah and here for this one specifically we show the actual rating text um we can

again round it so we say rating multiplied by 10 divided by

10. Z put this actually in a

10. Z put this actually in a string like this and um but after that we want to show an icon for our little

star image Vector icons default star give this a Content description of

null and a tint of sand yellow same thing we now do for our pages so State book pages uh number

Pages thatlet Pages or page count we're going to have the same type of titled content copy paste it here the title here will

be Pages the text will be page count true string here we don't need to round anything and the icon is not necessary for the page count all right um then in

the next row after uh this row we want to show all of our languages um so you can see that um for books that have a lot of languages like Harry Potter again

we will see all of these languages here when clicking on this right here um so lots of languages and we want to show these all um if you want at least so we will have a languages text and then we

will make use of a so-called flow row um so just a row that arranges all these chips in a row likee fashion but if they um get too too um too wide to fit in a single row uh then these will jump to

the next line um until we looped over all of them so below this row here we want to have a check if State book languages is not empty so if we have any

languages to show then we say we again have our titled content where the title this time is um string resource Ras string languages modifier is again one

with a bit of vertical pading so vertical HTP and then we have our actual content so this is now the titled content that shows this languages text together with

the content which is this flow row so flow Row give it a horizontal arrangement of arrangement Center so that in the last

row everything will be centered we can give this a modifier of modifier R content size also pass in um Center here

and then simply Loop over all of our books all of our languages so State book languages for each uh

language we have our book chip um which shows the text so the language actually. uppercase we can give

language actually. uppercase we can give this style as well style material theme typography body medium this will be and these chips

specifically will be small chips so the size will be chip size that's small and we add a bit of padding between each chip by saying modifier pading 2dp so

that we just achieved this tiny spacing between the chips the flow rrow is marked as an error because this is is this is experimental so we want to Alt Enter and opt into this experimental layout API and then below this languages

section after this if statement where is it here we will have our descriptions so our synopsis the description we still need to load from the API but we can already draw the headline for that so

text that's string resource Ras string synopsis the style material theme topography

headline actually title large title large give this a modifier of modifier do align align this to the

start um the start of our column so it's all left aligned here alignment.

start give this um some padding actually after we say fil Max with give this some padding specifically top padding of 24 DP so that we have this spacing here as

well as a bottom padding of eight DP so at the very bottom here and then we do have our description text so text is equal to if the state book description

do we already have that field yeah if that is not null then we say the text will simply be the book description if it is null which it will be in all of our cases right now

because we didn't yet implement the API or the endpoint to fetch that if it is null then we say string resource Ras string

no what did I call it book description description unavailable um this is foldable if we do this yeah we can do that that that's a bit better I think oh

no let's actually keep this because um some of these descriptions are actually blank so let's check for is null or blank um without the null check here and

in that case move this in here sorry that's a bit of a mess here but this way um it's not better so if it's blank if if there is no description um and we just got an empty string from the API then we also want to show it's

unavailable and otherwise we show it for the style here we can say material theme type phography body medium the text align here I would say

is justify so that it always fills the whole um the whole width here I think this looks decent the color of this um depends on whether we have a valid

description or not so if State book description is now blank again we will use a color like color

black but we say the alpha is only 40% and otherwise just color black and the modifier of this one will be

modifier give it vertical padding of 8 DP and I think that should be it um something we might also want to do is want to show a progress indicator here

when we load so if state is loading then we say okay we have a box

modifier modifier fil Max with and weight 1 F so we just fill the remaining height of our screen and we Center the progress indicator

here and then simply pass that in if we're not loading and it really depends on the description that we've loaded okay let's launch this this should contain almost

all of our UI for the screen now so what's missing is of course on the one hand the functionality for loading the descriptions and on the other hand the functionality to Mark books as a

favorite let's click on a book and there we go the rating looks very off um but other than that um it's a bit

weird that we don't see the progress indicator here for the description um because we are actually loading by default I think um so book detail view

model let's take a look at the state this is true um so I don't know why that isn't showing what if we just put the progress indicator in here and comment this out let's also take a look at the

rating very quickly book app ship I think what we could do to easily fix this is um like on the one hand we could of course put these in a row which we should have done but I would say in most cases if we have multiple composers in

here these will be arranged in a row anyways so what we could do is we could make this chip content actually extend a row

scope so um we automatically provide this row scope by putting this in a row here we can say vertical alignment is

alignment Center vertically like this and maybe also horizontally Arrangement Center and then

that should be fixed let's also check the description issue there we go um Cartland programming ah now we see the progress indicator I think this is okay

um and this won't won't hide in this case because we never update it to false which is what we will do um next so loading the description and then Mark things as favorit so we don't yet have

any database implementation of course that is what will come last let's spend time on working on fetching the description let's quickly actually take a look at Harry Potter again if that now

also works yeah multiple languages that looks decent we can scroll here as you can see very cool very cool we slowly get towards a working app so yeah as I

said next up is the book detail endpoint fetch the description merge that into AA book and then be able to to show that and lastly be able to also have some favorite books here which um we also

still have to do let's think about how we implement the book detail end point to fetch the description of a book because that's really the only piece of data that's missing for a book at least for our app here and it might sound

easier than it will actually be because as I already said the API design is really bad and what I mean with that is that if we take a look here I have prepared two of these detail responses so you can see the endpoints open

library.org works so to find out a specific um book and then we pass in the book id. Json

and I've prepared two of these and if you take a look at the Json response then we will notice okay here is the description that we're looking for for this particular book all the other fields we don't care about here for this endpoint so you can see the description

is simply a string however if we take a look at the different endpoint um like it's the same endpoint but a different book then the description is suddenly a Jason object so it's not a string anymore but the string is inside of this

Json object hidden behind this value field so for some books the API responds the description as real Json object and for some other books it responds that as a simple string and this really shows me

that this API was not designed for mobile development since in Mobile development we typically work with type safety when it comes to puring Json data and a Jason structure like this is easy to PO if you're using uh JavaScript and

you're building a website or so but it's terrible to PO this on a mobile environment still that is something we have to work with and that is also something you will often face in your daily life as a mobile developer that

you just have to work around such bad API design um since that's in the end what's given to you and you have to make the best out of it so I think this will still be a good practice how we can work around that so we can start by again

going to our book feature since that's obviously part of that going in data dto because now we want to define a different dto data transfer object or um cotland data class representation of

what we want to purse this Json into and we call this book work dto for example this will be a data class and here we will really just want to um Define the

description and ignore all other strings the description is still nullable so even though some books have that as a Json object some other books have it as a string there's still also books that don't have description at all and we would need to annotate this with

serializable again but by default if you would just do it like this this would not work it would only work for those book workor dto for those Json responses where the description is really a string

field but if it's an object then the serializer by default won't know that it needs to find the description inside of this object so what we need to do in order to work around this issue is we

need a custom serializer so if we take a look here in this annotation and we open parenthesis and then we actually have this width parameter which is a k serializer um so we can say that this book work dto specifically should be

serialized in D serialized with a different type of serializer where we can then Define our own logic and we want to create this here in dto as well called book work dto serializer this can

be an object and it will inherit or implement this a serializer interface which needs to consist of the type of object we want to serialize or deserialize then we can hit command I

here to overwrite these three fields and functions and on the one hand we need to Define this serial descriptor so this really um just gives this serial as a kind of an outline of which Fields we're

actually looking for to deserialize and we can actually just set this equal to build class serial descriptor um with the serial name so that is the the class

name book work dto we could also I think give this the um a dynamic name dependent on our dto um so this would also adjust if we would rename this this should work with bookwork dto double

colon class. simple name I think this is

colon class. simple name I think this is the simple name of the class as it was declared in the source code or null if the class has no name and apparently this can return null which it shouldn't

because our class definitely has a name so we can assert this here and then we can open a block of code here in which we specify those elements so those are now the J field elements that we care about here in our case that's really

just the description needs to match the field of the um of the Json field and we need to specify what type of object that is in this case it's a nullable string we need to Alt Enter here to import this

element and then we also don't have any more errors and now this serializer gives us a deserialize function which uh just gives us this decoder which you can now use to step by step decode those fields that we care about and return

these as our desired object the book work dto and a serialized function so if we would have a bookwork dto and you would want to push that with the API for example um then we have to take this carton object and serialize it as Json

and that is what we Define with the seriz function um this actually we don't need because we only fetch book work dto um still for the sake of completeness I would um implement this as well it's really super simple for this one just

the DC logic is a bit more difficult so here for this DC function what you want to do is want to set this equal to decoder that decode structure and passing in this scrypt here so this is

just how we initiate this um decoding process here of the of the Json that was returned and in here we then want to have a VAR description which is our nullable description we set this to null

initially since at this point we don't yet know the value of that and then we want to have while true Loop so while true Loop in which we check or we say

Val index is equal to decode element at Index this function now comes from this decode structure block so we just now trying to decode this whole object by passing in this descriptor again and

then this function returns a specific index which corresponds to uh the elements that we've declared in here so in our case we just care about a single field so we we only care about the index zero because this is the first element

here if we would also want to decode something else like the title and we would have the title here then if this returns index one we would know okay we're talking about the title field in

this case if it returns index zero we know it's talk we're talking about description and that's all we care about in this case so we can remove the title again just that you know that this index

does not refer to the um actual index of the Json response like the order of the field but to the um order that we've specified up here and if this index is

now equal to zero that means we actually decoded this uh description field which can now be a string or adjacent object we can now Define this logic to decode

this um these two cases in here first of all we need to Json decoder which is equal to our decoder as Json decoder if

this is not um castable here to adjacent decoder that means the response was not in Jason so in that case we can throw a serialization

exception where we say this decoder only works with Json and now we can say Val

element is equal to Json decoder decode Json element an adjacent element here is really just anything relating to adjacent so this could be adjacent

object um this could be such an object here in curly braces this could be a simple field like just a string this could be a list like here so this is what we need to check next so we can say

description is equal to if this element is actually adjacent object so we're talking about a real um object that we need to deserialize here which would be the case again um here if the

description isn't actually object then that means if it is an object the description should be assigned from this object specifically with the value field so we go back here we can then say

decoder that json. decode from Json

json. decode from Json element since we have a Json element here and we want to want to now um decode that whole object into our own dto class again which we have to create

so here in dto we say description dto which is a data class

serializable and this actually contains the uh the field value also nullable um let's leave this like that and I think this is actually non-nullable if we do

have this description dto so if we actually have a whole adjacent object the API responds with um then the description exists just if we don't have this response type of um of an object

then it might not exist so our goal is now to go back to our serializer we say we want to decode from Json element so from this one where we now know okay this is actually a Json object so we

definitely um that we definitely currently have to deal with this certain type of Json element structure Json object structure so we need to pass that we are um actually working with a

description dto the element here is our Json element and the deserializer for that is our um

description dto serializer and this will now just normally decode um such a such a um this element here into such a description dto but since our description is a string and not a

description dto we need to refer to that value this is now the value that we got from the API here so this specific string however if we are working with a

plain string so if the description is directly here as a string that is now what we need to put in the lse case so here if it's adjacent object we now have

this logic if it's not ajacent object specifically else if this this element is now adjacent primitive so for example a string an integer anything like that

and the element is also a string so only then we know we are dealing with a valid description then we can simply say element. content which will be uh the

element. content which will be uh the actual value of the description field and in all other cases so else um if if it's not adjacent object or adjacent primitive or simply not a string maybe

it does not exist or so then we will just assign null okay furthermore in this one expression we want to catch the case of compo uh composite decoder

decode none so that means we're just done with deserializing in that case we can break out of our Loop and in all other cases we can throw an

exception unexpected index or so and we pass in the current index so now we just assigned a real value to our description so after this Loop once we break out of

that when we um went through all of our indices we can go down here and return at thecode structure our actual book work dto with our description so this is

now our custom serialization or der serialization logic and you can see it's it's quite a pain to make this work with a with a single field like this so if you are working together with a back and team and uh you you are actually in in

communication so um you kind of own the API as well in your company then talk to them that they don't do this because this is really something mobile developers hate and web developers

typically have no idea that we have this type safety here in the mobile world and they just they can just make easy type checks with JavaScript they can just check okay um use this field if it's if

it's not null um and otherwise use this field and and then decode it as an object this is much easy in JavaScript but in Android or um also iOS things work differently for the zes function we

now get a value a bookwork dto which we now have to serialize um into this encoder so as a Json string and this is really easy we can just say value

description dolet so if that exists we just say encode string I'm actually encoder encode string um actually string

element but we do also need to call encoder dot encode structure again so just the uh the opposite of decode

structure pass in the descriptor um like this then we can say um end code string element there it is

pass in our descriptor um the index is zero and the value is simply eight so our description but as I said the serialized function um won't ever be called in our app anyways since we don't

need to serialize such an object back to Jason again um since we don't need to push that to the API in in any case still I also wouldn't leave this unimplemented um because if you later need this then it would lead to issues

and you would first of all be confused why okay now we have the zerizer which is is still unused so we need to go to our um bookwork dto and say with

bookwork dto serializer double colon class so we say this particular serializable class should now be serialized and deserialized with our custom serializer all right now we are

actually settled to um add the endpoint to fetch a book work by going to our Network remote book data source and in here we simply add another function so

we say get book details where we say we pass in the book work ID and we then get back a result of our book work dto and data error remote

again and now we can hit on this Arrow to go to our implementation our Kor remote data source but we now have to implement this so here we can just have

our getbook details function where we can just say return save call again and in here we use our um HTP client to make a get request

with the URL string um what did we call it base URL actually let's have it like

that base URL slw Works SL bookwork id.

Json this is exactly the endpoint I've shown you here so if we take a look again you can see open Library slw works this is the book work id. Json and then we get this type of data so you can see

how easy that now is thanks to this save call function and which automatically Umar the result of this call into a result class so that we can perfectly

work with that we should also be very explicit about the type here um actually it doesn't accept that for some reason this is actually the place I'm noticing

um that takes the specific type of data we expect so sorry we should specify this here book work dto and probably also here with a search

response dto able to fetch book details we can go to our default Book Repository to also extend this one here with a function for that and since in our case we really just care about the

description of a book and that's also all we fetch in the book work dto let's call this function in the Book Repository that we also expose to the presentation layer get book

description pass in the book ID and that gives us result of a string and a data error here I'm not actually um using

data error remote because later on we might also fetch this book description from our local database this could also very well be a data error local searching books only works via the API so it's clear that if we have an error

it must be a remote error but to get the book description this happens on the detail screen and if there's already this book um in our favorites then we can load the description immediately from our DB so back to book default

repository get a book description here we can now return if or actually not if we can just return our remote book data source that get book

details pass in our book ID and then that gives us a result of book work dto we need it as a result of string so we can simply map this to the description

and that's how we fetch a book description so we can now go to our book detail view model we can then add a function down here um fetch

book description for example VI model scope. launch since

it's a depending call and here we say um Book Repository or did I just call it oh don't we have a reference to that yet oh okay still need to inject this here Book

Repository make sure to use the abstraction and then we say Book Repository get book description pass in the book ID which we can get from our

navigation argument instantly after navigating um so we want to use saf State handle here uh safe State handle since that is what allows us to actually um get navigation arguments also in the

view model so this is really nothing else than um a persistent map of items or a bundle of items so we say okay this key is mapped to this value book ID is

mapped to this specific ID for example and um yeah Android will just pass the navigation arguments over there as well or rather KMP not on Android here and

then we can pass this down here by saying book ID is safe State handle to Route specifically our book detail

um route and we refer to the ID we can also cut this out have it available as a global field because we might need this multiple times here on the screen but

this way we just get um a type save ID which we can then pass here ID or book ID and on success um so if this API call was

successful we simply update our state state. update hit. copy and we just say

state. update hit. copy and we just say our already um loaded book is it. book.

copy and we update its description with um it no actually with the description we fetched here like this that's the description we also want to set is

loading to false now and then we can call this function again in our onart function of our state flow so we can say

on start so really same reasons um as how why we did it for the list screen so here we can say fetch book description

and we say state in view model scope sharing started while subscribed with 5 Seconds and underscore stated value as the default State I think this should be

ready to try out whether we are able to load descriptions or not um I will launch this take a look on on this device here I think um oh what's complaining here

unresolve soft reference is that an issue apparently not still launched so that was a buck here we have our list of books and now the magic um magic happens

when we click on a book and it has description available um because for this book I think it also is not available but let's look for a book like Harry Potter again which certainly has a

description there we go Harry Potter and clicking on one here and yes we we did fetch the description very cool there it is if we go back and click on another

book also description and so on so um this seems to work just fine we can also look for actually the same books that I opened up here so this um 48 law of

power for example had a description where it was directly a string so that we um certainly also test this type 48 Laws of Power there we go click this yes

it also loads the description for this book amazing so next step is to implement our favorite function which we currently don't have so if we click here nothing happens um so we need to add a

database a local database with room that we can save the um the books that we've marked as a favorite and as a last thing we also still need to implement um certain animations like the poles

animation that I've shown you but other than that we are done um so let's get to the database first and then the animations for the database I will um go

back to our boa data package obviously and here we should probably just have a database package package in which you first of all create a book entity so an entity is really just the the equivalent

um of a dto just for local storage so in the end just a database table we're going to make use of room here which is completely KMP compatible in which we need to annotate these entities with

entity so room knows Ah that's actually an entity that's a table and then in here we can have things like the ID which is a string uh not a string resource just a string we need to

annotate this with primary key to tell room this is our primary ID but we don't want to autogenerate it so this should be false because and we get these unique

IDs from the API already we have a title that we want to save with a book we have a description which is a nullable string we do have an image URL which can also

be nullable we have languages which is a list of strings we have authors well authors

also list of string first published year which I think we don't even make use of let's still add this

here Val ratings average is a nullable double well ratings count is a nullable integer well

number Pages median is a notable integer and lastly the number of additions is an integer and now when we we have another such

data model um just like a dto this is also now a data model that specifically um yeah couples this object to our uh local database framework to room since it also adds room specific annotations

which is why this should definitely be a separate object here because we have that we also do need another mapper so just like we had a mapper here to

transform um our search book dto to a normal book domain model now if you want to have a book domain model and we want to save that in our database we need a function that takes the domain model and

Maps this to our entity so just book to um book entity book

entity and here we return a book entity the ID stays ID in the end all stays the same

here so we can just pass all that stuff as it is the languages languages authors authors

first publish year first publish year ratings average ratings count number Pages median and lastly number additions like

this uh rating count um oh this should be rating average average rating I call it there okay so we now have a function to take a book convert that to our database entity so the next step when

implementing a room database is always to come up with a so-called da so data access object it's pretty much just an interface that outlines the functions that we uh need in order to interact

with our database so in the end we just Define um the different queries that we would like to um work with when it comes to the use of our database this also

goes in database so a favorite book da for example will be an interface that we need to annotate with a DA and here we can then outline the

functions we need for example a suspend function to upsert a book so um insert it if it doesn't exist and if it exists we update it instead and the book here is simply of type book entity we

annotate this with upsert and room will do the rest for us then apart from that we need a function to actually fetch our books um so function

get favorite books for example which will return a list of book entities now and actually what we would like to have here is reactivity so we want a function

that always automatically triggers when we change something um about this query when we change something about uh the favorite book table by for example adding a new favorite by deleting an

existing one so what we want is want to wrap this in a Cartland flow this is something a room luckily supports um so thanks to this flow we can observe this list of book entities and it will always

give us the most upto-date list when something about our query changes we do need to specify this query of course so how we want to fetch those books that's now SQL which goes in this query

annotation for which you can say select want to select everything so all the fields all the columns from our book um what is it book entity how there is no Auto completion probably just book

entity and there's also no wear condition because we just want to fetch all books from our table this is not a suspend function here um because a flow is already asynchronous by default um so

it's really not a oneoff call but just a call that gives us this flow which in and of itself does not do anything until we launch it in a coroutine scope and then we have then we um have the the scope again which gives us this suspending ability kind of and that is

why if you return a flow it does not need to be suspending because constructing a flow isn't suspending but just launching it is which we then still have to do in our view model later on then we do need a function a suspend

function again since this is um again a one-off call to get a favorite book favorite book by its ID we pass in the ID and we

want to load the details of that book so we say book entity nullable since it may not exist and this will be a query where we say select everything from our book

entity table again this time we do have a we condition since we want to say um where the ID of our book is equal to colon ID so that refers to the ID that

we've passed as a parameter here so we just get all the fields from our book entity table where our ID matches with this one and lastly we want a function

to delete a book so that means if we um kind of unfavorite a book then we obviously need to delete it again so delete favorite book by its ID this

doesn't return anything and here instead of Select from we say delete from where the ID matches and now what's missing is we need to define a database class so we

need to explicitly tell room what our database actually consists of um what are the Dows what are the tables we work with what is what is the database version is there any other config um that is what we need to tell it which

you can do in here again call this favorite book database make this a class actually an abstract class it needs to be of type

room database we annotate this with a database we need to specify our entities or different tables in our case we just

have the book entity table and we need to give the version which starts off with one so whenever you um change the database schema in future then you would you would need to

increase this version so room knows um that it would need to migrate your previous version to the new one for which you would then also need to provide a migration but this is only relevant if you actually um have an

existing user base that has your database installed and then these existing users get an update with a new database schema um so this summer needs to be migrated in that case then in here

uh we can say we have an abstract Val for our favorite books Dow which is a favorite book Dow um this will automatically be assigned by room

let's stay consistent here favorite book Dow and we could give this a companion object or so to specify the name of our database in a con Val DB name is for

example book. DB something else we need

example book. DB something else we need to do is if we take a look back at our book entity we are saving lists of strings here and by default lists can't be just saved in the database we can

really just save primitive fields or a bite array or so in a database field but not lists that room simply doesn't know how to save that because we might very well also make this list of strings for languages a separate table where each

language gets its own um element but this would be yeah I would consider this definitely overkill for this smaller app if you would save a lot of languages here for a lot of books um then it would

absolutely make sense to work with a separate table here so that each language is just saved once and then um the book is just linked with those languages because right now um if we

save two Harry Potter books and both books actually add 20 languages here then we would save those 20 languages twice one for each book and same for the authors so if if two books have the same

author then we could technically reuse that data that we've saved but only if we make that a separate table but um this is really something I don't want to do in in in this video since Separate Tables add quite some complexity um so

what we will do is we will simply take a list of strings serialize that into um adjacent string so then we again have a string that we can easily save in a database like this URL or the description and when we then load a book

from our DB we again deserialize that string and for that what we can do is we can um Define a so-called type converter so we can say um actually string list type converter because that's what it's

for that's what it converts um this will be an object which just contains two functions on the one hand um a function to convert from a string here we get the

value what we' saved in our database so the Json string and it needs to convert it needs to return a list of strings here we can really just say we

want to return Json carton X realization decode from string and pass in the value that's it need to make sure to annotate this with type converter this one here

the the singular form and then we need to have the same function just in the other way around so um a two string function or we call it from list for

example where we now have an actual list of strings so when we we try to save such a book entity in our DB we would um we would assign these list of strings

which we now have to take and um transform to Json so to a string after all in this case we say encode to string

and we want to encode our list Alt Enter and there we go that's our converter we can then add this to our database by giving it another annotation um called

type convert test the plural now to specify all of our oops all of our type converters which in our case just a string list type converter and that way

room knows which TP converters we want to use so this is how defining a room database would work on Android it's as you can see exactly the same on in a KMP project the only thing that is unique to

KMP is that we need a so-called a database Constructor so the way this works is we can create a new class called book database Constructor

this will be an expect object so just as I said before um where we had these expect vals for the coin modules this just says that we do want to have this Constructor in our shared code to be

able to initialize this database but the actual implementations of that that differs depending on the platform but the thing is if we make this a room

database Constructor here of type book database favorite book database and we then override the initialize function just like that then that is all room

needs to know we do get an error here because um there are no actual declarations in our platform modules but the thing is for this class room the room compiler will actually generate

those um actual declarations so that's that will all happen behind the scenes um we still get the error but we can suppress that by saying suppress no

actual actual no actual for expect and then it will beun because as I said room will generate these

actual DEC declarations for each platform I actually don't know what this does exactly under the hood but it's just something that room needs for KMP but something we do need is still a way

to um initialize our database um on our own so we still need an expect actual declaration here to create a database but this is needed in addition apparently um at least based on the room

dos so one last class or one last um thing we need to do in order to be able to create a database instance is the

database Factory um this will be in our shared code it will be a class an expect class actually which will have a

function to create an instance of a room database. Builder and we pass in our

database. Builder and we pass in our favorite book database and this is now a class where we also need to provide the actual declarations so if we click add missing actual declarations um want this

for Desktop Android iOS click okay then we now need to implement these so this one here for iOS for example we need to now say um where specifically want to save this database file on the iOS um

Operating System since the file system and how that works obviously also differs on Android iOS and desktop so now for each platform you need to specify how and where that should be

saved so for iOS we do have a DB file which actually depends on a so-called document directory so it's kind of the inter internal storage on Android which is a function we want to create

here document directory which returns a string so simply a directory path and the document directory here is equal to NS

file manager do default manager. URL for directory where the

manager. URL for directory where the directory is equal to NS document directory so again kind of the internal storage in domain is simply um NS user

domain Mass um so you can kind of filter things that way I think the appropriate for URL is something we don't need we can set this to null um create is false so that means to create this directory

if it doesn't exist but the user directory definitely exists and the error can also be null and then we can return this whole thing we can say require not null specifically the

document directory. path like this um this here

directory. path like this um this here um recurs this experimental foreign API which you can add here or let's add this

to the file Alt Enter here opt into this by adding this to the top of our file and then in here for the DB file we can say we use our document directory so the

internal storage and we simply add our um favorite book database DB name and then we can simply return room dot

database Builder pass in a favorite book database instance that we want to construct here and the name is our DB file so now we have an actual implementation where we

say iOS this is how you specifically create a room database next up we can jump into desktop this is the second most difficult um operating system to uh

to to work with when it comes to the file system since it's it doesn't need to be a single operating system here so we are running this on the jvm so this could run on Macos this could run on Windows this could run on Linux and

depending on the specific OS the path where you might want to store the database file might just differ so we first want to be able to actually um check which specific operating system

we're running on so Val OS is equal to system get property and we can get this from os. name and convert this to

from os. name and convert this to lowercase furthermore in in order to just get access to the specific um directory where applications typically store data like a database file on a

specific operating system we need access to the user home so to your home directory on your operating system which you get from system get property user.

home and then we can fetch the application data directory in this one expression depending on whether the OS actually contains win then it will be

running on windows so this is the typical application data directory on windows so here we can say system. get

En because the specific app data directory path is an environment variable on Windows um and the name of that will be app data so this will give us the the path of our app data

directory and then inside of oops and then inside of that we want to create um another directory just for for our application so we can say book pedia so

a file doesn't need to be strictly a file but it can also be a directory and by doing this we will just take a look in this app data directory create a book pedia directory in there and that is the

pl where we will save our DB file if the OS is actually Mac then we will create a file in our user home directory and then

we say library SL application support SL book pedia so this is the uh typical user data directory application data directory in a in a Mac OS environment

and lastly else if we're running on uh Linux it's a file in user home and the relative path from there is lo/ share SL

book pedia and this is now the directory where we want to save our DB file in we want to check if this actually exists because on the first run this won't exist since this book pedia directory will definitely not be there so we can

say if the appdata directory does not exist then we say appdate directory MK dur so we want to create those directories if they don't exist and then

from that point on we can create our DB file which will be another file in our app data directory now and uh the name of that file will simply be our uh what

is it favorite book database. DB B name and we can then return the same thing as we did before room database Builder and we pass in our DB file that absolute

path and the type is already inferred here so this is how we create or how we get the path to store um such application specific data um on in KP for desktop for all those different

operating systems the last platform to consider here is Android Android of course again uses a different type of file system and permission system and all that stuff but this one is actually

the easiest way here since on Android we do need a reference to the context this is what's required in order to get access to things like the file system so here for this actual class on Android

specifically we can just pass an instance of that and then make use of this we can then get the application context by saying context that application context and then immediately

construct this DB file by saying app context get database path favorite book database DB name and we again return room database

Builder and we pass in the DB file absolute path it is and here on Android specifically you can see this also requires the context to create this database instance so we can say the

context is our app context let's also give this one a name and that is how we create a database instance um a room database instance on KMP or compos multiplatform so now we've defined

actual implementations for all platforms for Desktop Android iOS so the next step is to actually be able to inject our actual database instance using coin so

in our modules in the common code again here we can now have a single where we want to say get we want to get a

database Factory we then call that create on that so this will now give us if we um save this in a variable here uh this will

give us this room database Builder which we can now use to configure how our database should be created with room specifically so for example we can add migrations we can add a callback type converters dynamically if we want but

all we really want here is want to set a driver so what drives that database and that is an SQ light bundled SQ um it's actually bundled SQ light

driver bundled SQ light driver um which the data which the database will use and then we can call build to get the actual database implementation so this will now

return the actual favorite book database we can then use to retrieve our Dow to make queries and so on however we don't yet have a definition in coin for our database Factory since creating that is

again dependent on the platform since on Android creating that request the context but on desktop and iOS it doesn't there we just create a normal class instance and that is why creating that and providing that for coin needs

to go in our platform module so here in di for iOS we can simply go in here and say uh single database Factory and

that's it can copy this same thing for desktop modules. desktop paste this in

desktop modules. desktop paste this in there and for Android here in Android main di modules Android we paste this

and here we now need the context which we can get with um I think application Android application yeah that is our um contact that we've previously introduced to coin already so if you remember here

Book application here this is the point where we've linked our Android application class with this Android context so coin is able to inject that as well we can then also uh go back to

our modules where was that here and provide our Dow specifically since that is typically what we want to inject in our classes so we can say single and we

say want to get our um what is that favorite book database and we provide the favorite book Dow so we can also inject that if we want so now that we are able to

actually create and inject an instance of our database we can finally get started working with that so that means you in our repository in our default book repositories since that is also what our

what our app will interact with in domain specifically so the abstraction we first of all want to add a bunch of functions on the one hand what we definitely need is a function to get all

of our favorite books this will then return a flow of type list of book so again our domain model we don't want to use our book

entity in here since this would couple our domain layer to our data layer since book entity is in our data layer we would have this import on data so we would have an arrow from domain towards

data which we don't want here so we instead want to again map these uh book entities back to our book domain models that's by the way something we need a mapper for um which I haven't done yet

so in mappers book mappers we only we can only convert a book to a book entity but not the other way around so we can just copy paste this function and make this also for a book entity to convert

this to a book which would return a book make this a book um the image URL here was nullable from the book entity but apparently in the book it's not

nullable so we might want to change that actually in book entity this one apparently doesn't need to be null there we go um I think we just call the fields a bit differently average rating is

ratings average um waiting count is ratings count okay um and num pages is num Pages median but that should be all

the differences so we can hop back into our Book Repository apart from getting our favorite books we also want to be able to observe whether a

book um is a favorite so is book favorite based on a specific book ID which gives us a flow of Boolean um since this is a flow we need to observe

in order to also update our um favorite icon the hard icon in live so this flow will trigger in the moment where we Mark or unmark a book as a favorite and actually now when I look at it these

don't need to be suspending functions same reason as before a flow or creating a flow does not require a suspending Behavior what does need to be a suspending function is to Mark as

favorite where we pass in the book instance and to delete from favorites which just requires the ID which is

completely enough to delete something um if we want to Mark something as a favorite this also could return some sort of result because um when we add something in our database it could always be that the storage is full for

example which is not an issue when we delete something but when we add something it is so we want to return it as a result of type unit and data error.

local this time and whenever we return a result of type unit so we don't really have any data that we return we just care about the error and the the fact whether it was successful or not then we

can also make this a so-called empty uh result which is nothing else than a type alas as you can see for result um with the unit data type we can then hop into

our actual implementation and now start implementing these functions so command I enter and in order to get our favorite books super easy we just say we

definitely need the um Dow instance here private Val favorite book Dow like this and we return favorite

book Dow get favorite books um which will return a list of book entities a flow of list of book entities and we now need to map this flow so this whole list

of book entities we now need to take this list of book entities and map every single item again to a book with our mapper and then

we don't have any more errors then whether a book is a favorite or not we need to return that as a flow and the way we can check that is um just by checking whether there is a book with

that ID in our database table because every single book we save in our DB is a favorite otherwise we wouldn't have saved it so if it is contained in our get books query um then we know it must

be a favorite so we can say favorite book Dow get favorite books and we now want to map this so again this list um

of book entities the flow of list of book entities again we get these here as well and we now want to map this to

aoan whether this book entities list has any element whose ID is equal to the ID we passed so is there any book in this list from this um from all of our books that has the same ID as the one we

passed here um in this R we should a Boolean so we map this whole flow to a flow of Boolean as well which will just switch when um when a book is actually being toggled as a favorite or not then

to Mark a book as favorite we simply need to take this book instance and insert it as a new element in our database table so since we want to return that as a result we can

say return try want to try taking our favorite book Dow upserting a book and we want to take our book and convert

that now to a book entity if that fails it might throw an SQ light exception on KP it seems like there is no sqid full exception which was the specific

exception that was thrown on Android when the database was full and we just get a generic SQ light exception um so I would just say if something fails here about inserting that that will very

likely be due to storage um because all the other typical SQL exceptions are rather a hint towards um a developer fault so you have an invalid migration

there is no permission to read this file your schema is correct or so so all things that we can um solve with a right design in the first place the only thing that would be a user's fault um would be

if the if the table is full if there's no more storage so let's just say um we would take the success case and just return a result of unit in that case and

if something fails we would return result error and the error would be data error local dis full and lastly to delete a book from favorites we use our

favorite book Dow delete Le favorite book and we pass in the ID that's really it one little extra adjustment we need to make in this repository since as I said um the job of a repository is to

coordinate the access between multiple data sources which we have we have our da which is a data source as well as our remote data source and when we now get a book's description then we can check if

there is already a book in our local database with that ID because then we can just load it from local storage and we don't need to make the API call so we can say our local result is equal to

favorite book Dow get favored book by the book ID and then we can say we return if the local result is null so if there is no book that we saved locally

there is um we didn't mark this as a favorite then we return the book from the remote API rather the description otherwise we will return the

local result. description but we actually put

result. description but we actually put this in a result.

success local result. description like

this so that way we really add this offline first behavior that we first try to load this information from our local DB if it's available we'll take it from there otherwise we load it from remote

okay that's everything for our repository the next step to um finish this favorite functionality is to go in our book detail view model and actually respond to clicks on our favorite button

here um let's do this in here I think this is not a lot of code we launch a view model scope and we also I know we actually already have the repository here so we can already work with that so

what we want to do here is we want to check if our book is currently a favorite book because if it is and we clicked on the favorite button then we want to um delete it from favorites

again so we would say Book Repository delete from favorites and we pass in our book ID

otherwise we say we mark this as a favorite and we pass in the book from our state um this is nullable I think so

let's say state. value uh

book get the book in here and then remove this thing so this is one way of our implementation that if we click the favorite button we also

change that in our database the other way around would be that if something in our database changes we also reflect that in our UI so and we update our is favorite state here that's something we currently don't do and we can do this

again by just observing this thing uh the flow we get from our repository with a private function observe favorite

status or so where we say um Book Repository is book favorite pass in the book ID for our book that we're inspecting and you can then say on each

so whenever that changes we get the information whether that's a favorite or not and we update our state with that information so State update is favorite is equal to is favorite don't forget to launch this whole thing in view model

scope otherwise nothing will happen we can then take this and also call this function here in onart observe favorite status and that should be it for the detail view model

in order to change a favorite status of a book in our list view model we also need to change something so book list view model because here we also need to of course display all the books from our

favorites so in here we might also have a function to observe private function observe favorite books

which will take the Book Repository and say um get favorite books and with every emission so whenever something changes about our table um we get the favorite

books and we just update our state with these and we say favorite books is favorite books we can then again launch this in view model scope and this

specific flow here we should also actually save in a job so a cortin job just like we did for the search job um I will explain in a moment why um so

favorite book job or observe favorites job something like this we then go down and we say observe favorite job is this

and before we cancel this because if we then call this here also um in on start observe favorite books and this should

be pretty independent of whether our cach books are empty or not this might actually um be called multiple times because if we are on on our book list

screen then the unstart function will be called we will start observing and listening to our favorite books if we then navigate to the book detailed screen then this will um keep on

observing the books actually for longer than these 5 Seconds since that only relates to the to this flow chain but not to the flow that is launched in here independently which has its own view model scope and since the view model

stays active um when we when we navigate somewhere else we would still keep on observing those favorite books but the thing is if we would then go back from the detail screen after more than 5 Seconds this onstart function would Tri

trigger again and therefore add another Observer to listen to these favorite books since the previous one was not removed after these 5 seconds so we just really need a single Observer here um and that is why we make sure to to cancel the previous job if there is one

alternatively as I said you can of course also use the um init function of the view model and then you don't need to take care of that okay let's try this

let's launch this there we go go it is launching and it is crashing again let's take a look why class not

found exception didn't find class a favorite book database seems like some kind of compiled time generation thing maybe it is because of an issue I noticed

earlier that we did not add the room compiler dependency because it was grayed out um was that here yeah the Android room compiler was grayed out that must be why and um for you probably

that did not happen because I will adjust this in the initial code um but I will quickly fix this we need to apply this for all of our source sets for all of our um different modules here for

desktop main native Main and so on so we can say dependencies and we want to add a KSP dependency so the um Cartland symbol processing which gen generates the code for room and here we can say

lips Android X room compiler and this should fix it because I yeah we applied the room uh the KP Gradle plugin as well as the room plugin so doing that should hopefully get rid of that issue that the

class is not generated as I said um similar to the issue earlier I will adjust the initial code of the repository so you will already have this launch this

again there we go now everything is loading nothing is crashing we do see our books that's cool you can see the arrow is now invisible

if it's a white background um our favorites are are currently not existent because we don't have favorites if we add a book to favorites we click here click on that yes it actually toggled

and that means that it must have also inserted that into our database because we only observe that Boolean whether I want to show this favorite icon or not we only observe that from the database um this does not originate by clicking

on this but it actually has to be applied in the DAT in the database to see this and we should also see this if we now go to favorite oops not that one favorites there is our favorite book if

we turn off or turn turn on airplane mode now so we can't fetch any more books and we reload our app we should still be able to see the book information for that here something went

wrong but here we do see our favorite book which we can still open um there's no description but if we actually Mark a book as favorite um that has a

description like or Harry Potter again and take a look in here then go there here we have a big description mark this as a favorite we turn on

airplane mode go back back go to favorites and now we also Sav this big description we can delete it from favorites again and then it will also be hidden here very cool that works next up

and the very last thing of this course will be animations so I've shown you that before we have this little PS animation um we have some navigation transitions that we can add um that we don't have this default fade animation

but rather a slide in animation for example for the new screen and there's also a little animation that um that I've added here for these book covers when they actually load it so just in order to show you again what we will

build here for this book puls animation I will relaunch my sample app here so you can see this for a moment um so you then know what we are trying to replicate there we go an unknown error

code okay obviously I'm still on airplane mode uh let's turn this off and relaunch the app um our books are loading and when they loaded uh okay

they were actually quite quick um here this puls animation is what we will build so you just have an idea of that again where do we put this um that is animation so obviously part of our UI layer we could argue about putting that

in core presentation um since that is a composable that we might want to reuse across features even though we don't have multiple features here let's put this in there so we just call this pools

animation since this composable is really pretty much stand alone and not coupled to our book feature this will be simply a composable called book animation we'll just receive the modifier so we can align it if we want

and as you've seen this is really an infinite animation at least as long as we show this composable so the way this works is we have a transition which is remember infinite transition that is how

we um build infinite animations in jetp compos and then we can have the the progress that we extract from this transition so we can say progress by

transition that animate float since we now want to animate a value from 0% to 100% And depending on that value we scale up our um this little circle that

we draw so we're want to animate float the initial value is zero the target value on to animate towards is one so

100% And the animation spec so how this animation should work will be a tween actually no not a tween but an infinite repeatable infinite repeatable so it should be an infinite animation we can also Alt Enter to import this whole

thing to get rid of this issue um on the one hand the animation in here that will now be a twen that lets us Define the duration I want to pick a second here so

that one um one pull P FL kind of takes a second to complete and we want to set the repeat mode to repeat

mode restart so that's also the default that means if the pulls animation finished one iteration so um after 1 second then it will just restart from the center again and expand towards the outside if you would want to use um

reverse here then it would go to the outside back to the inside to the outside and so on that's not what we want for this pulse animation and the actual animation we can then just buildt with a simple box

composable which really just needs a modifier or we can then say Graphics layer so this is the typical modifier we use for animations since with Graphics layer we can change the translation of

the composable so where it is relative to where we where it was composed we can change the alpha value we can change the scale we can change the rotation with that and this is just the the most

efficient way to um handle animations in compose since if we have this progress which updates very frequently here and we use this inside of this Graphics layer Lambda this will not trigger recompositions of this

composable so if we would actually make use of something like scale here and if you would assign the progress here um since this is now considered as a real state that is not used inside of a

Lambda this would trigger a lot of recompositions um per second so we want we don't want to do this but rather change the scale here since that won't really have an impact on also on the on

the boundary of the composable it really just visually scale this up but not the um the invisible box around the composable so you want to say scale X is equal to a

progress as well as scale Y and we also want to change the transparency depending on our progress because in the very center of our pulse animation we

want to um have a very high alpha value so want to have it fully visible and the more we transform or the more we move with this pulse to the outside the more we want to fade out

um the alpha value and this works by saying Alpha is equal to 1f minus progress so this means um the progress which starts at zero will therefore result in an alpha value of 1 F at the

beginning of our animation so at the very center of the the pulls and if our progress then animates towards one that means this will result in an alpha value of zero so to the end of the animation

where our circle is at the um the edges of this composable and then it will fade out that way in order to really give this also a visual look we want give this a border so this will simply be our

width um of 5dp this will be our border I mean um the the yellow circle that expands we want to give this a color which is our sand yellow and most importantly this the shape should be a

circle shape and this should already be everything we need in order to draw such a pulse animation um so if we want to now um draw this in our book list item

here book list book list item scroll down to where we currently using the circular progress indicator when there is no image load result yet here we can then use the pulse animation instead and

um give it a modifier to give it a size since um I think yeah we did not use weights in weights for this box so it does not have fixed width I think so we

should just give it a fix size here of modifier size 60 DP um the same thing we can then apply our book detail screen if

the image hasn't been loaded in here book detail screen where is our image it's actually in the in the blured image background here I don't know if we are actually already drawing the circular yeah here here's the circular progress

indicator so here instead we want to use the pulse animation again with a modifier of modifier size 60p and if we launch this that way we should already

be able to see this result there we go we are loading our book covers and now when I try to show this um it load instantly but there it

is here's our book pulse animation if we click on that and then we also see it here oh we actually need to center it um and that also looked a bit weird um we probably need to give our card some

background before the cover has been loaded and we need to make sure that the animation here is also aligned in the

center of our box um are we using a box here no so we need to use a box to cut this out have a box

modifier give this a modifier of filmax size want to Center this content alignment and in here we can then put in

our pulse animation and let's also switch this to I think we don't need to specify this at all so it would just default to white or so let's try this

again so there we go it's loading scrolling down a little bit to some kind of cover that hasn't been loaded um um yeah they load so quickly because they already cached but here's

one yeah now it looks much better we're loading the book cover and when it's available there it is the next little animation I want to add is when our book cover has actually been loaded that it

just um rotates in a little bit and also fades in we can also do this in the book item um where is it book item book item book list item there it is here below

where we specify our painter we can actually observe the painter state so we can say painter state by pain do state and we collect this so it's a state flow we collect this um as a life

cycle and then we can also use this to um check whether that was successful and in that moment where we switch from um loading to successful we want to animate the book cover so we can say Val

transition this time it's not an infinite transition because it's just a one time thing want to animate once the book has been loaded this transition can be by animate float as state here we

need to pass a Target value which can switch depending on a certain other state so we can say if painter state is

async image painter. state.

success so once that switches to successful we want to animate the target value to 1 F and before we leave it at zero and we

can also uh give this an animation spec again of a tween and duration milles is let's say 800 milliseconds and then we can scroll down to where we load and

show our image and again make use of Graphics layer to add this animation so since I want to have a little rotation effect here where the book cover rotates from the inside of the screen towards us

um we need to think about the right rotation axis that we need to work with here so specifically U we need to rotate this around the x axis which is the horizontal axis since if we rotate

around that axis um it will rotate towards us so we need to use this rotation x value and we want to set this to 1f minus our transition value

multiplied with 30f so initially our rotation um should start at in this case 30° so it should be rotated um 30°

inside of our screen since our transition starts with 0f and animates towards 1 f um we again get that same effect that 1 F minus 0f would start off

the animation with um 1 F multiplied with 30f so 30° of of rotation and as this transition then animates towards

the value of one um we will we will get to a rotation of zero here same thing we could also do for the scale so scale X

um actually put that scale in some kind of variable here where we say 8f plus 2f multiplied with transition so that means

we start with 80% of scale and then at our um at our remaining % over the course of our transition so scale X is

scale and scale scale Y is also scale if we then launch this we should already be able to see some results we'll probably need to execute a new search so um we get some new books oh no we actually

already saw that you just saw how these books animated in but if we now search for something we did not search for before for example Eragon it is then we

load with this pulse animation and after book covers have been loaded we hopefully see some animations there it is all of these books are kind of rotating in and another last animation

that we can very easily add to our app is just a slide animation when we click on a book um that we navigate to this detail screen we can do this in our nav graph this is very easy and supported

out of the box by compose navigation because for each and every composable we have here we can Define this enter transition so um the animation that should play when um this composable

enters the screen actually for this one we don't even need this because this never really enters the screen since we never navigate directly to this book list composable but we rather get back to it so we rather need an exit

transition which is nothing else than a Lambda and here we can say slide out horizontally oops horizontally this one here um so this will actually play um so the exit

transition of the booklist screen will play when the booklist screen exits our composition so when we are on the booklist screen we navigate on another screen on our detail screen then during

that transition this booklet screen will exit by sliding out horizontally by sliding out to the left the same we need to do for the pop exit transition actually the the pop enter transition um

so when we pop the backstack so we are on the book detail screen we pop the um pop the book detail screen from the back stack and then get back to this screen

then it will enter by sliding in horizontally again so we can say uh slide in horizontally like this and for the detailed screen um it's exactly the

other way around here we want to have an enter transition which plays when we navigate to the detail screen here this should slide in horizontally when we navigate to it and the exit transition

should slide out horizontally um actually inside of a Lambda if we try this and take a look here there we go if we now click on

something ah okay that's actually something is wrong here should be the other way around you can see this is not exactly what we want we probably need to

go in here and make this a Lambda um give this a name of initial offset and map this to an initial offset let me quickly try this um if we have this

Lambda for this one here as well and launch this again here it is we click on a book and yes that looks better so we slide in like that slide out like that um so what this initial offset here

means is um that is just an alternative overload that we can pass the initial offset X which by default you can see is set to minus it divided by two so it is

the full width of this transition and by default it starts at half that full width to perform this transition but we want to start at actually the full width

which is why we um return the the whole full width here in this Lambda rather than just half of that and that way we get exactly this transition here which I

think uh looks pretty cool okay I think that's the app um what we of course didn't do till now is running the app on our other platforms we've so far only

ran this on Android but we of course want to try this out on iOS as well um so if you've if you actually um have a Mac and you're running this on a Mac then you can try this out on iOS it is

pretty simple so on the one hand um let me minimize this a little bit on the one hand in your directory you will find this IOS app folder which contains the X

code project the xcode workspace file here you can rightclick to open this in xcode so this will really just show up if you on a Mac and you have xcode installed and it will show up here this

is xcode the typical iOS and mechos environment um IDE and you can see here we really just have this iOS um entry point in Swift where we call this main

view controller we've worked with in our shared code you'll notice that sometimes it complains about that it doesn't find this compos app module um but once you actually build this uh it will find it you don't even need to open excode by

the way um to run this app I just wanted to show you that we do have this file but we can also go up here to compose app click on IOS app and then it will run this with um the emulator or

actually called simulator on iOS that you've configured here so in my case iPhone 15 with this iOS version if we run this I actually have the simulator open

here um but Gradle is still building um building the errors application failed okay and wrong here did we get an actual error doesn't seem so so tooling is

definitely something that KMP can improve at um because it doesn't really show an error or I'm just blind here is an error um the database class must be

annotated with constructed by oh that is something I forgot in our database we have the database Constructor and we need to link this to our database because by default room doesn't know

which specific DB this is for so in our um favorite book database here we need to annotate this with

database um actually constructed constructed by and we say book database Constructors apparently this is really just for uh the other platforms because

for Android it worked if we relaunch this on iOS that way we can check this another time yes now something is happening um that is looking better our app is launching and we see our app on

iOS there it is we do see all the animations our books are loading we have favorites obviously on iOS not yet we can scroll through these things if we

click on that then oh it no it did not crash some kind of error happened here um but apparently nothing critical because our book was loaded it is a

little bit laggy but probably because of it's a debug build here um but now once we've transitioned you can see now it's much much more fluent um something we do need to adjust a little bit for iOS

specifically is you can see the um status bar here is white would look much better if it would also have our Darko bluish color but this is a super quick fix for that we actually need to go to

xcode and here you can see we have this compos view we ignore the safe area which is um pretty much the equivalent to window insets on Android uh but it it just ignores the keyboard save area so

we can just say want to also ignore the container save area which will also correspond to the the um status bar so if we relaunch this and then take a look here you can see now we actually get

this as as a blue headline as well or status status bar which looks much more natural okay iOS Works let's try desktop we go to our desktop Main and where we

really have this main function that's Al always the entry point for desktop programs you will notice at least as of now if we run this here we will get this main KT file class wasn't found till now

I haven't figured out a way to um really run uh compos desktop applications here from this play button also if we run it from up here it will face the same issue

um but it will work if we actually go to our terminal and run this manually with a Gradle command so if we say um/ Gradle W run which runs the same task this

would actually run here if we click there you can see it actually builds and does not immediately give us this issue um and now it launches a program over here it's loading our books and we do

have a working desktop app as well um obviously here we can swipe but we need to scroll very cool if we click on something and we also get this transition here you can see this is a little bit off since a desktop has such

a wide window so it focuses here on this red part but I think this is really not an issue actually um if we search for something else like Aragon again and then here we are we have Aragon click on

that we see our animation and once it loads we will'll see something um probably because because we load that many images at once um is why it sometimes takes a little moment for an

image to load um but other than that here we have our Aragon book and that works that works so if this course was something where you say I really learned

a lot with that then uh I don't know at this point how long this video will be after editing but I would guess probably around 3 or 4 hours and if you say you've already learned that much with

this free video on YouTube about Cartland multiplatform maybe about Android maybe you're also an Android developer like I am and you now just get into composing carton multiplatform if you say you learn that much with a 3

4our video then really check out my Android Essentials bundle um while it's not a cotland multiplatform course this is still uh the knowledge that you learn there about architecture about multim modules about Gradle management Gradle

config about jetpack compos that is completely applicable as it is to cotland multiplatform project as well and that is a 23-hour course bundle um so guess how much you will learn in that

um there will really build real bigger app with I think six screens rather than just a simple little detailed screen as we did here all these courses really have a 30-day money back guarantee so

even if you say um you're unsure whether these are right for you um if if you get these and you say pH that was not it then you will get 100% of your money back within 30 days so down below you

will find links to that you'll also find links to my cortin master class which we um heavily made use of the knowledge in this course here and other than that thank you so much for watching this whole thing I know this was long I

really hope you could learn something and if you want more such cotlin and compos multiplatform content here on my channel in future then please let me know that down below if you maybe have a specific project idea um that you want

me to make a video about any specific coton compos multiplatform topic that might not be building a full project but just a part of that maybe then just let me that know that down below thanks so much for watching this thing I know it

was long um but I will see you back in the next video at least if you've Leed a subscribe so if you're not subscribing then do that now but then I will see you in the next video which I publish two

off every single week so thank you so much see you back byebye [Music]

Loading...

Loading video analysis...