 Yes, hi everyone. This is the first talk here at the Kotlin Dev Room. It's the first time for me at Fostam So a little bit excited about that as well My name is Eric Helman. I am Working as a freelance software developer back home in Stockholm, Sweden I've been using Kotlin for quite some time But mostly I've been doing Android development for the last couple of years So this talk is about coroutines with Android I'm not going to go into the very deep The coroutine part they're gonna be plenty of those talks here today This is about how do you use coroutines on Android efficiently and in a smart way? So just quick raise of hands how many here do any Android development? Almost everyone. That's nice. Okay, so that will be useful for hopefully Let's do one more how many here use coroutines in their Android applications A little bit less. Okay, good. I picked the right topic So you can find More information about me the some of the source code I am showing today on my github account I have a blog post that covers part of what I'm gonna talk about today But most of all on speaker deck slash Eric Helman Eric with a K You will find these slides already. I've uploaded them there So yeah, let's get started So you probably heard this one before Kotlin coroutines are lightweight threads. That's how Jet Prince describes it themselves. But what does it really mean for those of you who have been using it? You know that it's very easy and cheap to start a coroutine It doesn't cost very much compared to starting a thread So they have a very good Example that is not very useful in real life, but it looks something like this Where you start a hundred thousand coroutines that just prints a little dot after waiting one second And if you would do this using threads your computer will grind to halt and Probably have to reboot it or something But this will complete in a little bit more than one second because This will run in parallel, you know smart way as coroutines are And hopefully there will be more information about how this coroutine stuff works during the day For those of you who are more interested in the technical details But suffice to say coroutines are cheap. They're easy to use It doesn't cost very much to start one. So But the court the coroutine concept is not new there are other languages that has that as well like go routines in go and Well airline has it et cetera et cetera And it's a great way to to run stuff in parallel or asynchronous But Kotlin also adds something called structured concurrency On top of this and that becomes very very useful Especially in applications where you have life cycles that are shorter than the actual lifecycle of the application like the lifecycle of a view lifecycle of a Class that just is valid for a certain period of time during the use et cetera So structure concurrency builds upon the thing that any coroutine started needs to run within a scope and the scope can Then be connected to the lifecycle There is a great blog post by one of the Kotlin Developers at the head of Kotlin. I think it is Roman is Elizado. He works at JetBrains where he talks about and writes about What structure concurrency is this one is from 2018 and he wrote some follow-ups, but I recommend you read this blog post So coroutines are always related to some local scope in your application Which is an entity with a limited lifetime like a UI element So the lifetime of a button or a fragment or an activity is always shorter than the actual application lifecycle But your coroutines will be started from those so The when they first started the coroutines that didn't have the structure concurrency So they didn't have the console of scope so you could do something like this and it would be probably valid This is deprecated do not use this today Mostly because you can't use it today because it's Probably won't compile anymore But just launching a coroutine out of nowhere is not the way to do it because then you might have things hanging and This the typical thing that you have in Android applications where you actually try to update the UI and you're not in a valid state anymore so instead There is this extension function the first one you that's most useful When you write your own suspending function called coroutine scope, which only can be used inside Coroutine scope the coroutine scope is where the lifecycle management will happen We don't really need to go into the details here. I'm gonna talk a little bit about async in a moment so Coroutine scope. What is it? Well, it's just an interface Which has one? public Property a coroutine context the coroutine context is what runs the threat of story the coroutine It's actually a little thing the manager tells you what dispatch you to run it on it separate sector And you can cancel all the coroutines that get launched by canceling this this context so So that's nice. Now. There is another bunch of core extension functions added on top of the coroutine scope interface For instance launch so launch that's starting new coroutine for me it has Two optional parameters first you can give it a context if you want to and that will be attended to the one It's running inside and you can tell it how it should be started if we start it eagerly or lazily etc There will be more talk about what that means later today And finally you give it a function as a spending function Which must be running inside the coroutine scope and this one will basically be your code So yeah, so this is nothing you have to write yourself. It's already there, but it lets you run The coroutines inside activities fragments any life cycle component in Android and they will be cancelled accordingly at the appropriate time So how does the council work? Yeah, let's go back to this. Let's start a hundred thousand coroutines Let them wait one second each And once we have started all these hundred thousand ones We sleep for a hundred milliseconds and then we call cancel on the scope Which will mean that all of these hundred thousand coroutines will be cancelled so very theoretical example, but I hope you understand the concept there Now Android All of you who have developed Android applications have probably seen one of these two images I'm sorry for not being able to blow them up, but you know why because it's impossible these are horrible diagrams and I hope that you don't need to look at them anymore because thanks to The updates that Google has provided with us over the last two years with a jetpack library There are not really that important anymore But we still have a life cycle. So how does it work? How do we take the life cycle in Android and how do we combine it with a coroutine scope? Well, as I said Sorry went to cancel is a question here because that's that's the thing you could write these coroutine scopes yourself Add them to your activity your fragment and then call cancel at the appropriate time But that kind of like defeats the whole purpose you we would like to have something out of the box so Android jetpack So if you don't know what Android jetpack is I'm sorry I'm not going to go into the details there, but these are the default libraries that you should use in any Android application today There are a bunch of libraries that takes care of different things In this case, we want the life cycle libraries from the jetpack libraries So what we do we? Add these dependencies the life cycle runtime runtime KTX view model KTX and live data KTX It's important that you add the KTX to them. Otherwise, you're not getting the coroutine stuff And you will just get the standard stuff that doesn't have to do with Kotlin's per se These things I'm going to show today were added in two point one point zero I think or maybe two point two point zero for some parts of it But the latest stable version now is two point two point zero. So use that one so the first extension function they added that are the interest of us is Extension property they basically just added a property property getter on the life cycle Interface Which is a cold cold coroutine scope which combines the life cycle of a component and Android component an activity a fragment Sepra with the coroutine scope that does that all that job for us You can read this code later It's actually very simple once you get once you have a look at it But it basically stores some stores a reference to this coroutine life cycle coroutine scope inside atomic reference class That's part of them the life cycle class so Then it has another extension property on the life cycle owner interface Which basically is your activity or your fragment And this is the life cycle scope and it's just a getter that gets the coroutine scope from the our other previous life cycle Coroutine scope So these are the two things that enable Coroutine life cycle management automatically for us So how does it work? Well simple now you can just Do something like this we have a button with when you click the button you want to log in And then we call life cycle scope the extension property that is available on activity and fragments and you call launch there which will start the coroutine and Now asynchronously it will do this and you can call Do login Now this is not the perfect way to do this because well For various reasons probably do login wants to run on a different dispatcher etc. Etc. But besides a point in this example If you want to do coroutines in a view model and there are reasons why you want to do it there instead You can do that as well. So they have a life cycle management for that The view model class has un-cleared Function which gets called once the view model is no longer used or no longer necessary to keep around So what this extension property does is basically adding a coroutine scope? That is getting cancelled in the un-cleared function for you. So now you can do view model scope launch This will allow you to do like not expose coroutines up to your UI components Which might or might not be a good idea. It depends so first Pattern or advice is to always use a life cycle KTX libraries for Android when you're doing coroutines And I do not try to reinvent the wheel. Everything is there for you. It's getting proper testing etc. Etc. as with any Concurrency or threading or things like that. You don't want to you don't want to reinvent stuff yourself Now, where would you want to call launch for the stuff you're gonna do? Well, you can call it in your fragment in your UI the top level you are component you have Like you have a login fragment that might be a valid thing to do But what happens if you would press back there or something? So maybe you maybe you want to move it down and you want to do Launch your coroutines in your activity Well, that might also be a good idea But what happens if you press home or you leave the leave the activities? So maybe a better choice is to do that add a suspending function in your view model So it survives when user goes in and out of the let's go Well, also the view models also cleared. So maybe depending on what it is you want to do Maybe you should put it in the single ton object you have that is the repository for talking to your back in API. So It depends you need to make these choices yourself depending on the use use of the API is et cetera Just remember that how things are cancelled is Is what will affect the completion of your coroutine So you already saw a sink before in the one of the first slides and there is also launch These are two of the basic ways of starting coroutines so Let's see the differences here what they what they mean So let's say we have our login API which has a suspending function named login Whatever we do there, it's besides a point you might use retrofit or ktor or whatever firebase or something But to get an auth result back so In our fragment we have a reference to our login API So we call life cycle scope to launch we call login we get the result we check the result and we do something there this is perfectly valid and It's also, you know It might be actually a good idea to use that one if you if you're want to make sure that the user stays in the login Fragment and if they leave they you would actually cancel whatever login operation you had Now another way to do it is to use async So when you call a sink and you return something from an async function, you're no longer getting a job You're getting a deferred So the deferred result it's like a future in Java What that means that you can take this deferred result and pass it into another coroutine elsewhere in your application So in this case here, we just start two different coroutines and the second one will wait The await call there will will then suspend the function until the login operation is completed in the first coroutine So again, which one should use it depends I can't give you an answer that is 100% valid every time so you have to consider what is your use case here Maybe you want to take the deferred result and pass it on to another fragment. I don't know So that is launch an async returns like a single result or do a single thing here But what about streams of events? That's usually what we're working with today. Whatever it's where it's coming from It might be database It might be outgoing events might be incoming events might be data coming from an API Etc etc. It might be updates from your GPS There are many types of events and we treat them like streams these days So if you know react Rx Java or something like that, you probably worked with that before It's a nice pattern to work with And we've got that with coroutines as well I'm not going to go into the details of reactive streams for Kotlin I'm going to give you one example. So you have a feeling of how to use it in an Android application So instant search is probably familiar to most of you you start typing And for every letter you do you make a new search search query yeah, so The challenge here for you as a developer is that you don't want to spam your API So you don't want to you don't actually want to call the search API on every single key press because We've just be waste of resources So you want to have a certain threshold where you like what the user stops typing after a certain amount of period time Then you send the request and if the new search happens while the old one is going on you want to cancel the old one and Make a new one Get it So that's the challenge with instant search now. This is an application that you can find on my github I'll show you a link to it later as well but Conceptually it works like this You have a stream of search queries going down into your application from your fragment into your view model into your Repository class and away to the the API and Then you have a stream of results coming The research results coming back up to your application. So here you have two streams going in both directions We start with our basic search repository just perform search we get a query we pass in a query We get a list of strings back our review model has a query channel and The query channel is of a type called conflated broadcast channel, which is a very strange word if you haven't work with this before but I will show explain shortly what it means what we do that is we publish a search result property I see here that I didn't show the type of this search result, but it is a live data in this case. So Bear with me here But how we get this search result property is that we call as flow So we convert our channel our conflated broadcast channel into a flow and Then we call the debounce operator, which is the key here to like keep down so we don't spam our API So the search delay milliseconds here. I put 300 milliseconds So when the user haven't typed a new character for 300 milliseconds, we perform a search And the way we do that is we take Because now we're getting the string from the query into this flow here into the map latest function And then I call search API perform search Now the the nice thing with this one is that if the user will type another character while the search is searches Sorry, the search is going on This Coroutine in the map latest function because it is another coroutine that will be cancelled automatically for you So it will never return a result So then you will just pass on a new one So you will not get cancelled operations or circuits that have started what was never completed And the final one is Aslay live data. So we convert this into a regular Android live data, which is easy to use in the fragments So first thing conflated broadcast channel if you have used Rx Java, this is basically the same as a behavior subject You can come be configured a little bit more Which means that it retains the latest value basically So a new subscription to this search result here will get the last value now the last live data is An extension function in lifecycle KTX another reason why you want to use that library That basically wraps a Collection of this flow into a live data object But not only that it also makes sure that it is kept around for a little bit longer than the lifecycle of whoever is Observing this live data. So if your your fragment goes in an autoscope and back It will still be the same object So I recommend you checking up the documentation for the ass live data function Now in our activity we do the following we we pass We use extension function from the Android KTX that it's on a edit text That's called do after text change. So whenever the text is changed I get the query channel from my view model and I call offer offer is not a suspending function So it's but it's inside it wraps It launches a new coroutine inside there But this means that we don't need to call offer on our channel is inside a coroutine And that is one choice you can do there is there is another option here as well I'm gonna talk about in a moment, but basically we just give this channel here's the latest string Offer will return a boolean if this worked or not But in this case, we're actually not interested whether it works or not. It doesn't work We're going out of scope anyway. So We can just ignore that now when we want to listen for the results, I have my regular recite review adapter and What I do there is take the research result, which is the live data I observe that one with the life cycle of this activity and I call submit list on the adapter when whenever I get a new search result That's all you need to do to basically implement this instant search Now coroutine channels if you do offer you can do something like this, but I say you want to pass in three strings or three ins into a channel if The last one was successful. It will return return true and then you can do the next one and you can do the next one This isn't the practical real-world example, but it just shows how offer works Now if nobody is listening to our channel in collecting data, it will return false. So it will not Pass that go on to the next one If you use a suspending function, you can use send instead So then you can call these like Just like this and when send is invoked it will suspend until someone is collecting Those are the differences here so again Depends on which one you want to use Okay, so let's see how this was solved. We convert the flow in our That comes up in our view model to a live data for simplicity You don't need to expose coroutines up to your fragments or activities most of the time You can do it if you need to Also, we passing down events using a channel So when you want to send generate events in your application code You usually use a channel and when you want to listen for events that coming in you use a flow You convert stuff to flow or you create your own flow So coroutine channel and flow for events error handling in its importance in our example we We need to do something like that and the way to do that is nothing to do with coroutines But more about Kotlin use the sealed class something like this You create a type which is what your flow or your live data will always emit to the UI And then we have the we have the first class that extends search result the valid result Which contains a list of the search results? And then we have two objects that represents an empty result when we didn't get any hits or an empty query When we decided that this query is too short We don't want to return we don't want to make a search with this short query And then we have error and terminal error and the reason I have these two will be obvious in a second here So what we do in the map latest we just expand that one we wrap it in a try catch Inside it. We make a check if we if the Search string was long enough if it wasn't long enough then we return an empty query If we didn't get a search result we return empty result if we got a valid result We return that if we got an IO exception. I mean a network error We return an error result with that exception And then finally after map latest we add another operator called catch This will be the terminal catching for any other exception that we didn't catch in the map latest and There we emit and a terminal error because if we get another exception inside the map latest that we are not catching That means that the coroutine the whole flow will be cancelled So you need to like restart everything But this will be like a terminal error that it means that okay Now I have to close this view and go back or show the user some error message that was completely unexpected So in our handle search result We we do the following I see I misnamed the functions there, but I think you get the point So we check the type basically of these and then we call the appropriate function Okay So for error handling or actually dealing with Having a nicer way to deal with the results coming from your flow or your live data that you converted from a flow Use seal classes for everything You should of course do an exception handling properly as well, but it makes the error handling much much easier now There is there is a blog post where I wrote about this you can read more about it I'm going to details. There is a source code example here as well. You can run This example is also contains some code showing how to test co-routines, which I haven't covered yet And with that I would like to thank you all for listening and Hope it you find it interesting. I think we have some time for questions So any questions? Oh, yeah Thank you Did we have a microphone for questions? Is there any questions? That's fine. Oh, yeah one there Given the maturity of the new infrastructure of the Android X in Jetpack libraries Do you often get conflicts between the third party libraries you use which also depend on these components and your own application code? Sorry, but the last part I didn't get the dependency conflicts So given the maturity state of the Android X and Jetpack, it's not the recent thing Do you often get conflicts between your own dependencies and other dependencies which one the different version of that? If I do get dependency conflicts with the Android the Jetpack libraries. No, actually, this is I Considered that being a solved issue with once they went to Android X And I know they were expanding on this one and It's getting better every day as well So there's like easier to import like entire parts of the application It does give you warnings properly if I if you you imported one dependency Which actually depends on something else where you have a lower version. So yeah, I would say that it's fairly Good today. Yeah Okay Yeah, no more questions in that case Sorry, I'm not But Sorry one why why do you use this pattern because in my head I So You mean this one why I pass events down and why I pass them up So yep, the middleware that you're mentioning here is the search view model here in this in this example That's the kind of the business logic that drives our instant search logic Now you could place that on the back end instead. Is that what you mean or you have you see class where? Error seats and you send and it's intercepted by this search So we have this one is that what you mean or this one maybe So yeah, the Here's the channel where I this is string that user types in it gets passed into this function here and Then we basically pass it down into the lower part of the application Which will be in our in our view model here So it goes into this channel and it goes through this flow here and Here it gets them passed down to the API. So this is basically just an implementation of the middleware that you're talking about Okay Well, I think we're out of time. We need to set up for the next speaker. So thank you all where Michael coming. I hope you have a nice day