Understand Kotlin Coroutines on Android (Google I/O'19)

Just another WordPress site

Understand Kotlin Coroutines on Android (Google I/O'19)

YIGIT BOYAR: Hello Welcome, everybody My name is Yigit Boyar I’m an engineer in the Android team SERGEY VASILINETC: Hey, my name is Sergey And I work in the same team SEAN MCQUILLAN: Hi, I’m Sean I’m from Android Developer YIGIT BOYAR: Today we are going to talk about coroutines on Androids, but before we talk about that, let’s try to figure out why do we even need some coroutines And to understand that, let’s forget how you write AI code on Androids So this is the dream code we want to write We’re like, calling function makes a network request, whatever You get the user, set it on the text field This is what you want to write, but if you write that, you get an exception, because you cannot make a network request on the main thread Easy We just put it inside a thread and run the code Now you’re going to complain is the textView, which is like, you cannot touch the UI thread from a background thread And OK, we’ll write this kind of code where you make it asynchronous You provide a callback, it draws on the background thread and calls your callback on the UI thread And this code works fine, except, if you’re writing code like this, you’re going to receive out-of-memory exceptions, because you’re going to be leaking those callbacks left and right There is a solution to this as well, where we can have an understanding of a subscription that keeps this chain And whenever you are a stop, we can just cancel the subscription That one works, but then you end up with something like this And I’m not making this up, by the way Three years ago, when we started Architecture Components, I was looking at the Google App codes And I found one application that had 26 lines of [INAUDIBLE] on register So if it happens at Google, it happens everywhere We don’t always write the best code But there’s a solution to this as well, which, if you’re using something like RxJava and this function returns an observable, you could just use the AutoDispose library Associated subscription with your [INAUDIBLE] lifecycle and safely subscribe And this works perfectly Similarly, you could be using LiveData, which enforces you to have a lifecycle to observe, and this also works So it’s this old problem, right? Why are we even talking here about this if there is already good solutions? Every year, we run these developer benchmark surveys We ask developers how are they doing, what are their problems And the surveys we ran last year, one of the top complaints was threading and concurrency Developers say this is hard on Android And one of the top requests was this, what we call, LiveData++ People want us to extend LiveData, make it more like RxJava And we’re like, why? Why do you want this? We have good solutions Just use one of them So we did what we do best when we don’t know We did UX Research– this is User Experience Research– on concurrency So we did in-depth interviews with nine developers What they do is they do their regular work for a couple of weeks in their own company And every time they see a problem about concurrency, like this observable, they just write it down This was the problem This is how I solved it And this is how I feel about it And in this study, we focused on three main things Focused on LiveData, which is our observable data holder We focused on RxJava We saw the Reactive Extensions library And coroutines, which provides suspendable computations And in the result of that study, this was the conclusion For LiveData, people say, we love it, but we want the complete solution In fact, it’s funny LiveData doesn’t even support anything but the main thread, but we talk about it in a concurrency study For RxJava, it is amazing People love and hate it They love how powerful it is, but the common complaint we always heard was it’s always misused It feels like an overkill And for coroutines, this was like, it really looks like the best solution, but I’m not sure It’s very new It’s not mature So this was the overall conclusion We said, we need a solution that is simple It shouldn’t be hard to learn that solution It should be comprehensive So it should be able to scale to different use cases And it should be robust There should be a built-in testing story So we made two decisions We said, OK, we are going to have first-class quality support in Jetpack And we are going to have more support for RxJava in our documentation But, today, it is all about coroutines So Sean, why don’t you tell us a little bit more about what coroutines are?

SEAN MCQUILLAN: Thanks, Yigit So I want to just take five minutes and talk a little bit about what problem coroutines are great at solving So, in a sentence, the main problem that coroutines solve is simplifying async programming by replacing callbacks, which is quite abstract So let’s look at some samples and see what that looks like I’m going to make an imaginary network request three ways The first style is what’s called blocking style This is where the result is returned directly from the function Let’s see how that executes And, for fun, I’m going to run that on the main thread When called, a blocking network call will block the thread that called it So the entire time that network request is running, the main thread will be blocked And that’s the thread that has to update the UI and handle user touches, so the user will see your app is frozen, or it might even crash Now, I do want to pause and say there’s nothing wrong with a blocking style of network APIs, but it’s not what we want to do on Android So, to fix that, as Yigit already talked about, we commonly introduced callbacks So let’s see how that executes We’re still going to call this from the main thread, but now, when fetchUser is called, the main thread is free to perform other work It can handle onDraw or respond to user touches And the networking library is responsible for finding another thread to actually run the request When the result is ready, the network library can then use another callback I gave it to call back into my code and let me know that it’s ready Let’s rewrite that exact same code with coroutines It looks just like the blocking style The result of fetchUser is available immediately And I don’t have to introduce a callback To tell Kotlin I want to execute this with coroutines, it has a suspend modifier on the function And when we run it, still on the main thread The main thread is unblocked just like with callbacks And this is a key concept of coroutines The networking request still runs on another thread When the result is ready, it resumes the coroutine where it left off This code is much simpler than the callback style while still ensuring that I can write my Android app and make it never freeze for the user This is the key mechanism here of coroutines– suspend and resume When a coroutine is suspended, it’s not running It’s paused And when it resumes, it picks up from where it left off You can think of suspending a coroutine as taking a callback from the rest of the function So you’ve put it together– suspend and resume replace callbacks We can even visualize that The callback version and the coroutine version execute almost exactly the same way Let’s switch back and take a look at fetchUser How can we call a function that makes a network request from the main thread? To start, we’ll need to make fetchUser another suspending function This tells Kotlin that it works with coroutines And inside, we’ll call another suspending function called withContext We’ll pass it dispatchers.io Zooming in on those dispatchers, Kotlin gives us three dispatchers– default, IO, and main And they’re used for different things Default should be used for CPU-intensive work– things like transforming a list of 100 elements, calling DiffUtil, or precomputing text Anything that takes too long to run in the main thread should run on the default dispatcher IO is a dispatcher that’s optimized for blocking network in disk IO You should use it anytime you need to write code that blocks an API, like writing a file or reading from a socket And main– this is the main thread on Android And surprisingly, it’s our recommendation as the right place to start coroutines in response to UI events Since you’re usually starting coroutines from the main thread, staying there will avoid extra work for simple operations Then, when you need to transform a list or write a file, coroutines let you switch to one of the other dispatchers by using withContext withContext will run the block that you pass it on the dispatcher you tell it to So this block here is going to run on dispatchers.io, and I’m free to make blocking network calls This allows us to provide main-safe APIs You can just make a function that reads and writes from the network like this and call it from the main thread This is a huge benefit on Android Now, I don’t have to worry about what every single function– what thread it needs to run on Instead, I can just call it And the function itself can ensure that it’s safe to be called from the main thread To finish up introducing coroutines, let’s take a look at how Kotlin implements them Every thread has a call stack It’s what you see in the debugger or a stack trace It’s how Kotlin keeps track of which function is running and its local variables When you call a suspend function, Kotlin needs to keep track of the fact it’s running a coroutine instead of a regular function I’m going to represent this as a suspend marker Everything above the suspend marker will be a coroutine

and everything below will be a regular function Then Kotlin calls loadUser just like a normal function It’s going to put a stack entry onto the call stack And this is where any local variables for loadUser would be stored And then it just executes until it finds another suspend function call Now Kotlin has to implement suspend How does it do that? It’s kind of simple once you figure it out All Kotlin has to do is copy the state of the function from the stack to a place where it can save it for later It’ll put all suspended coroutines out here And it’s not structured like a stack Then Kotlin will actually call fetchUser, create another stack entry, and when it calls withContext, suspends that as well So at this point, all of the coroutines on the main thread are suspended And this means the main thread is free to do other work like handle onDraw or respond to user touches And this is really, really important When all of the coroutines on a thread are suspended, the thread is free to do other work If we fast forward a few seconds, the network result will be ready And Kotlin will have to call resume In order to do that, it just takes the save state and copies it back over, puts it on the stack, and resumes the function When it resumes loadUser, it’ll just go ahead and continue executing just like normal If loadUser had errored, it would have thrown an exception right there The suspend and resume mechanism is the magic behind coroutines And we wanted to show it to you so you could understand how they work as you start using them in your code That wraps up the coroutines intro Coroutines on Android offer us the ability to simplify our code by replacing callbacks and allow us the ability to create main safety to ensure we never block the main thread Now I’m going to hand over to Sergey who will talk a bit about libraries you can use today with coroutines SERGEY VASILINETC: Thanks, Sean Yeah, those threads [INAUDIBLE],, but we really want to benefit in our real application for that And despite the very young age of coroutines, there are libraries that already support them in their stable or better artifacts And I want to start with WorkManager that is brand new for us in AndroidX, because it already supports coroutines in its stable release And you can use with coroutine work But let’s make a step back and try to figure out why we do that, we use it So this is a typical flow of workers So if you are not familiar of WorkManager, you can think of worker just as something that does long background job It may have some constraints, but that’s very simple– just some work And typical use case for that, you need to synchronize some local data with your web server And this flow would look like you query your new nodes from your database, then upload it to the server Lastly, you just mark those nodes as successfully synced Well, you see, no need for coroutines Well, actually, we didn’t start to talk about cancellation, because cancellation may happen due to a variety of reasons For example, constraints for this worker aren’t met anymore or user explicitly canceled this job if you provided with UI So how you would support cancellation? Well, you can try to do something like that You try to put every other line with ifCheck, and it starts to look silly And even more, it doesn’t actually work, because this call, which is probably most expensive call because it goes to a network and does some work there, doesn’t have any cancellation signal propagation, because if it was started, it will run to its end no matter what And this actually will cause the worker to help us with that We didn’t talk about that yet, but coroutines don’t only grab callbacks nicely It also provides nice cancellation property So every suspend function can be canceled It can react on this cancellation And, also, it propagates the all inner calls– this cancellation signal You may say, our code inside those calls are still blocking We don’t benefit from that anyhow This is true However, if you use Room as your database solution, you can mark your queries as suspend functions And then Room will take care of cancellation for you As well as threading, as Sean mentioned that multiple times, this thing will remain safe Room takes care of threading It will run the query on a background thread Then, well, nice– our database calls are cancelable now,

but as we discussed before, the main call is this one And, actually, if you use Retrofit, you can make it suspend as well, because Retrofit already supports suspend identifier for its network calls And I want to highlight that Retrofit isn’t part of AndroidX It’s just Java designed by Android Next time you use Android, Android [INAUDIBLE] embraces coroutines, and we like it At the end of the day, it’s less work for us Nice Now this code supports cancellation And it looks as easy as it looked before So we got cancellation for free So this was a quick look on the things that were available today And Yigit will present you a lot of new guys that we just made YIGIT BOYAR: Thanks, Sergey So, so far, we talked about what you could do with coroutines And for the rest part of this talk, we are going to talk about new stuff So first one is LiveData and coroutines Now, just to be very clear, LiveData is not designed for concurrency It’s an observable value holder And you are expected to be able to access the value from the main thread That’s intentional But that doesn’t mean it should not be interoperable So this is what we’re going to provide you today There will be an easy way to use LiveData with coroutines So the most common use case is you have some value You want to compute in a coroutine, but you want to serve the result as a LiveData So starting today, with the Lifecycle 2.2 alpha01 artifact, you get this new one, new API, called LiveData So it’s a builder function very similar to the sequence builders in Kotlin Inside that, you pass a coroutines block And inside, you can do whatever you want and call this emit function to dispatch values So if you look at this database load function, it is HLS [INAUDIBLE] function Because you are calling the emit with the user in this case, we can infer that type for you, so you don’t even need to specify this So this really simple LiveData API bridges the gap between your LiveData elements and your coroutines So let’s get that API a little bit more in detail So this is three parameters And the first one is a context So why do we need a context? Well, if this data is loadUser function, wasn’t the– [INAUDIBLE] function was a regular function, and you write this code, you are going to receive an IO on main thread exception, because this block, by default, draws on this picture’s main But we can change that We can give it a context, as this picture’s IO, and now this code will work perfectly I want you to notice that I didn’t change any contents of the code, because you can emit from whatever dispatcher you want You don’t need to be on the main dispatcher to change the value Now, the second way is a really awkward parameter called timeout To understand why we needed a timeout parameter, let’s look at the infamous rotation problem on Android So on the left, I have a ViewModel that serves LiveData And on the right, I have an activity disk observing it So while my activity goes to a started state, the LiveData will become active, which means OK, you’re an observer visible to the user You are better off creating some values But, during that time, what if our activity rotates? So it’s going to be stopped LiveData will become inactive, be destroyed, and a new activity will come So right now, there is no one observing LiveData, so there’s no reason to produce results, except after the new one goes start it again, it becomes active again So the problem we are trying to solve here is this gap while LiveData quickly becomes inactive and active in a very quick succession, like usually less than one second So how do we fix that? Let’s look at the detail how we run that code block And to understand it better, we’re just going to write a timer function It basically creates a timer for LiveData It gets the current time, returns a LiveData builder, and in an infinite loop, it just emits the time, delays one second, emits the time, delays one second, and never ends And this code I’m showing is 100% OK to write How does it actually work? When the LiveData returned by this block becomes active, we check, OK, did we run this block

And if we did not run that block, now we start executing it While we’re executing it, if that block becomes inactive, if LiveData becomes inactive, we check, OK, is this block still running And if it is still running, we give it some time to finish But even after the timeout, if it is still running and we are inactive, this basically is unnecessary computation There is no one observing the LiveData, but the board keeps running So we just cancel the continuation– the coroutine So if the LiveData becomes active again, we’re just going to restart it You only do it once So if it finished to completion, there is no reason to restart it Now, you can also emit more than one value So let’s put some structure around the sample we had before where we have a repository that has a getUser function, a loadUser function, and the loads from the database, and emits that value Now, most of the time, this is not the code you write You need to go to the web service, fetch an updated user, update the database, and emit that value again So you can call emit as many times as you want as long as you are inside that quarantine block But you might say, well, most of the time the database doesn’t return your user, it returns you LiveData for user, because you want to be notified about the changes Well, all you can say is you could just call emit source If you ever use MediatorLiveData, this is very similar to emitSource where it says whatever value comes from the LiveData, just make it my value You can run things like transformations here Oh, also, we don’t need this extra emit, because we’re already observing the database, so you can get rid of it So this LiveData API basically provides us a very nice way to make LiveData work with coroutines But how about ViewModels? SEAN MCQUILLAN: Thanks, Yigit So let’s talk a little bit about how to integrate coroutines into your ViewModel But, first, I want to talk a little bit about leaks– specifically, coroutines leaks And these are a very serious problem They’re kind of like a memory leak that we’re all familiar with, but way worse A coroutine can resume itself And in addition to using memory, it can use CPU It could write a file It could make a network request that doesn’t need to happen To help us deal with coroutines leaks, Kotlin introduced this idea of coroutine scopes So what is a scope? Well, it’s really just a way of keeping track of your coroutines All coroutines must run in a scope And a scope gets the ability to cancel all of the coroutines inside of it In addition, they’re also the place that uncaught exceptions from a coroutine get shuffled off to You put that all together, and you can use scopes help ensure that you never leak a coroutine WorkManager that Sergey talked about provides a scope So does the LiveData Builder that Yigit just talked about viewModelScope is a scope It’s an extinction property on ViewModel from the KTX library I’m going to do another one of those scary infinite loop things that Yigit showed, but this time in a coroutine that I start myself in a ViewModel It uses viewModelScope to launch a coroutine in the scope And by default, this launches on main Then it starts an infinite loop that doesn’t know how to stop itself And every second it’s going to go ahead and write a file Now, that’s pretty expensive Coroutines don’t make writing files faster or cheaper, and we definitely don’t want to leak this work ViewModelScope lets us write code like this safely When the user navigates away from the screen, the scope will be canceled, which guarantees this very expensive work won’t leak So viewModelScope can help you avoid coroutine leaks by guaranteeing all your coroutines are canceled whenever a user leaves the screen I’m going to pass it over to Sergey who’s going to talk about some other scopes we’re adding SERGEY VASILINETC: Yeah, thanks, Sean Yeah Another thing that very naturally provides scope is Lifecycle because, as you can see from its name, something that has a start and the end And if you think, yeah, that’s familiar with this Lifecycle owner interface, you actually are, because it is your activity It is your fragment And don’t forget that fragment conveniently has two different lifecycles And the second one is associated with– we use inside of it Unfortunately, for me, I now have to talk about that But let’s define scope more precise there So as you know, your fragments get recreated over your configuration changes So its lifetime can be shorter It can be longer And lifecycleScope just mirrors that, meaning that once your Lifecycle owner receives destroy event, lifecycleScope gets canceled

And all its inner jobs are canceled as well So, as you can see, the lifecycleScope is very tightly coupled with UI And it works best in situations like that So previously, you would do something like this when you decide to show some UI with delay And, well, this looks pretty simple, so we can make it a bit harder And if we have two steps, it becomes to look very ugly because of this deepness thing And actually, if you take a closer look, you have some real issues here, because this mainHandler And those functions that touch UI don’t really work nicely together, because mainHandler is kind of a GlobalScope It doesn’t care about your Lifecycle at all And those functions have reference to fragments or activities So if your delay is long enough, you can easily leak a lot of them and receive out of them their exception While lifecycleScope will cancel those codecs, they are showFullHint for us It’s kind of a callback because it’s a suspend function It will cancel it off the [INAUDIBLE] once your Lifecycle is destroyed So this code looks nicely, because it’s very sequential And it is actually safer However, I have to say that lifecycleScope is a bit of a danger zone So let’s rewind a little bit I was the one who showed you that Retrofit and Room supports suspend functions Yigit showed you something that– very familiar– looks like that When you say, OK, I’ll combine those functions to network and database into some repository pattern, you now have just one function, which is the suspend function that orchestrates all of this work So I just need the scope to call it So why wouldn’t I just call it in my lifecycleScope? It’s actually not the brightest idea, though Why? And don’t get me wrong Yigit and Sean sold you everything correctly It won’t lock main thread It won’t leak [INAUDIBLE] However, do you remember this picture? lifecycleScope get canceled, and every configuration change, meaning that your network request gets canceled every time, so it is just wasteful You’re wasting user resources, better resources It’s just bad for environment So how you would do it properly Well, one of the things actually was presented by Yigit Like this LiveData builder will work very nicely in these kind of situations I’ll present to you in our way how you can approach this So your starting point for this kind of task is a ViewModel scope So you just run this loadNote function in this ViewModel scope Then we introduced a function in ViewModel that will connect our UI in the ViewModel when you grab a note Well, as we discussed, it’s a network call somewhere inside of its loadNote So it’s a synchronous operation So it should be suspended And, well, now we need to somehow connect this node that is loaded in one scope And loadNote function will be called in some other scope Well, I will use CompletableDeferred Well, it sounds a bit scary, but it’s actually a very simple thing You’ll see in a second So how we use it– we complete our deferred with a note that we loaded It just put the note into this object Nothing happens And readers request the note with a weight function from this deferred If a note isn’t ready yet, then the reader will be suspended If it is ready, reader will resume right away So this is how we implemented our ViewModel And last step, we just call that in our Lifecycle scope this loadNote function that we introduced in the ViewModel And our network call is properly executed in the ViewModel scope, so it’s not affected by configuration changes And our update UI function doesn’t leak once your Lifecycle owner gets destroyed However, once we add the fragment into the picture, things get complicated as always So we decided to run the fragment transaction And you will get a legal state exception, because nothing guarantees you that you are in the correct state that allows you to execute fragment transactions And we did something smart and introduced some special functions that help you to deal with these kind of situations And this is going to be a bit tricky, because it’s actually a fairly complicated thing But what it does, this block will run only

when your application is started or resumed, meaning that it’s in the foreground And this block will be suspended when your Lifecycle is just created So let’s take a look on the example of what it actually means So you have this function It is called, probably in the beginning Your block will be suspended, because note is not ready Then, once this is ready, in usual situation, we would resume execution and proceed to the next line But with launchWhenStarted function, we are going to go and check Lifecycle If it’s not started, we are going to suspend further until the Lifecycle will become started again And once it is started, then we are going to proceed to the next line and easily execute this transaction So we won’t run into this exceptional situation So one thing I want to highlight that this block is suspended during creation And it is a different thing from being canceled, because cancellation is still provided by lifecycleScope when destroyEvent happened And now, as you can see, it is something that we definitely need to test, and Sean will help with that SEAN MCQUILLAN: Thanks, Sergey So we talked to you a lot about coroutines today We talked about how they can help clean up APIs by replacing callbacks to suspend and resume We talked about different ways they can be used in different situations And that’s all great That’s awesome But if they were difficult to test, that’d just be a non-starter That wouldn’t be something that I would take very seriously as a thing to use So what I want to talk to you about right now is Kotlinx-coroutines-test It’s a new library that came out about a week and a half ago It’s currently marked experimental coroutines API, because it needs more feedback before it makes it all the way to the stable It’s a collaboration between Google and JetBrains to make testing coroutines on Android very easy So it’s not coupled to any testing libraries So you can use JUnit 4 You can use JUnit 5 You can use your own custom test runner that you’ve built And this library is going to help you test coroutines So I’m going to focus in on that LiveData Builder that Yigit showed And we’re going to talk about how to write a test for that So I’m going to emit one I’m going to wait a second And then I’m going to emit two This is a relatively simple LiveData so I can focus in on how to write the test for it So to get started, we need to mock out that main dispatcher The LiveData Builder uses dispatchers.main by default, which is the actual main thread on Android We can replace it with a test coroutine dispatcher This is a special dispatcher designed for testing coroutines And we can make a test coroutine scope This is a scope designed for testing coroutines So then in Setup, you can switch out dispatchers.main for a testing dispatcher This will change the global value for dispatchers.main immediately, so the LiveData Builder will use the dispatcher we give it And then in tearDown, resetMain to the default value And then this last line here on the bottom is really, really important It says testScope.cleanupTestCoroutines If you think about what a dispatcher and a scope are doing, they’re very stable, right? They have to keep track of your coroutines and actually run them If you don’t call this, it’s very easy to leak state between tests So that’s a lot of boilerplate So you can go ahead and put that together in maybe a JUnit 4 rule This doesn’t come in the library, but you can write all of that code into a rule And I would expect to see a library that does this relatively shortly So whatever testing framework you’re using, however, you should build an abstraction that’s appropriate for your testing framework to do that code The rules that I’m defining here exposes testCoroutineScope interface, which lets me call runBlockingTest This is a coroutine builder that’s optimized for testing It works kind of like runBlocking, but it makes writing a lot of tests easier Oh, and it returns unit, so you can use it in single expression style in your test Then we get the subject And then we need to start observing the LiveData so it will execute Remember the LiveData Builder won’t run until someone’s observing it I’ll define a little test helper called observeForTesting This is just my test code It’s not in a library anywhere It’s just going to start an observer and then call the block that I passed in And back to the test The first value has already been emitted, because I’ve made everything deterministic with this test rule that I’m using I’m going to use [INAUDIBLE] assertions to check that the value should equal one, and then I’m going to advance the time by one second This is one of the big advantages of testCoroutineDispatcher

You can control virtual time So advancedTimeBy will cause that delay to return immediately And I have control over it in my test So the second emit is already done when I get to this line of code There’s no need to spin and wait for a result And this test won’t be flaky I can just say subject.value should equal two And if we run it, we see that our test passes The test runs instantly instead of taking an entire second So go check out the library Be sure to file any bugs that you find It’s currently marked experimental coroutines API until it’s had enough feedback to elevate the stable And now, I’m going to hand the mic back to Yigit to suspend the talk YIGIT BOYAR: Thanks, Sean OK, so much of this stuff– what is next So today we talk to you about how you can already use coroutines in AndroidX and other Android libraries We introduced a new LiveData Builder that lets you integrate live data with coroutines And the new Lifecycle skills for your view model So coroutines scopes for your view model and your life cycles And then we also introduced this new functionality we started which allows you to run coroutines based on your lifecycle state And last but not least, we have introduced a new testing library for coroutines So earlier today we announced Kotlin first, and for Android [INAUDIBLE] and Jetpack it’s more like coroutines first This is a recommendation We believe coroutines provide the best functionality and ease of use for concurrency on Android But we acknowledge that this is work in progress Most of these libraries we have shown are either experimental or alpha one, but we want to develop this with the community the same way we do with architecture companies and other Jetpack libraries So you can either join us or wait six months and then start using them And as part of this, you will see more and more of Kotlin and coroutines coming out of Jetpack So all of these are available in lifecycle 2.0, offer 01 starting today, so please take a look at it and let us know how you feel about that Also, we really, really like coroutines Thank you [MUSIC PLAYING]