Kotlin Coroutines 101 – Android Conference Talks

MANUEL VIVO: Hi I’m Manuel Vivo I’m an Android engineer in the Developer Relations Team at Google This video is about an introduction to coroutines We will talk about the problems that coroutines are trying to solve, how to use coroutines in Android, how to test them, et cetera The first thing we want to talk about is, what programs are they actually trying to solve? They basically simplify asynchronous programming on Android So whenever we talk about asynchronous programming, we cannot forget about synchronous programming Here, we have the function loadData, which is going to display on the screen the result of a networkRequest And because this is synchronous, it means that it will block the thread where this has been running on So imagine if we call loadData from the main UI thread, networkRequest will block their thread whenever it is waiting for that networkRequest to happen And so in this case, if the operating system is trying to call onDraw, that is not going to happen And therefore, the user will see a frozen UI and unresponsive application It will get unblocked whenever the networkRequest finishes and it calls show, as we can see Let’s see the definition of networkRequest here networkRequest is just a function that returns data And inside it, we have the blocking network request code Blocking the main UI thread is something that we shouldn’t do in Android So how can we move this networkRequest to a different thread? How can we make it asynchronous? A solution for this is using callbacks We have the same version of loadData We’re just going to make the networkRequest on a different thread Here, as we can see, if we execute this code, we can see that networkRequest will be called, and we will see later that the function networkRequest is going to be moving the execution of the networkRequest to a different thread And in this case, the main UI thread is free, and the operating system can call onDraw and refresh the screen as much as it wants Whenever the networkRequest finishes, it will call this lambda, a callback, that is going to display the result of the networkRequest on the screen If we take a look at networkRequest, now it’s just a function that doesn’t return data anymore Returns unit And instead, it takes in a callback, a lambda that we call in this case onSuccess, with what to do after the networkRequest finishes We can call DefaultScheduler.execute to move the execution of the networkRequest to a different thread And after that finishes, then we can post to the main thread calling onSuccess So while callbacks might be OK solution for some use cases, it has some problems First thing is that now this is a simple function that it just makes another request and displays something on the screen But now, the logic can get complicated We start adding a nesting networkRequest after the other one happens And now we can add more, and more, and more, and more stuff This is what becomes the callback hell– whenever there is a lot of indentation, error propagation might be difficult You have hard-coded posting to main thread wherever something happens, and you might not want to do that It’s complicated What if we could have the best of both worlds– the simplicity of synchronous code with all the power from asynchronicity and moving things between threads? And this is where current things come in Here, we have the same function, loadData, written with coroutines It might look a little bit suspicious, and you might think it will block the main UI thread, but it won’t The only difference that we have is this suspend modifier in the function definition That’s basically telling the Kotlin compiler that this function needs to be executed within a coroutine So how does it work? How can networkRequest not block the UI thread whenever it moves to a different thread? Well, coroutines can suspend execution without blocking the thread And that’s going to happen whenever we move to execute something to a different thread So that’s what we call the suspension point But also, whenever that networkRequest finishes, then it will resume execution So whenever the networkRequest is done with whatever it has to do, loadData can resume execution and continuing with the result of that networkRequest If you take a closer look, you will see that apart from networkRequest, the rest of the function is that what we used to have before as a callback So what is happening with the callback? The Kotlin compiler will write that callback for you under the hood when the computation can suspend And actually, coroutines call those callbacks a continuation Continuation is just a generated callback interface with extra information in it So how is the compiler going to modify the function loadData?

Well, it will take the suspend modifier and replace it by a parameter of type continuation And continuation form this state machine in which it will be executed depending if the function is suspended or not So we can say that when it starts, the function start, it will start with a state 0 When it will suspend, it will change state When it will resume, it will be back to a different state, and then we will finish This is what it’s called continuation-passing style And because it’s quite a complicated topic, it is not going to be covered in this presentation But as you can see, just a fancy way to say callbacks So with coroutines, computations can get suspended without blocking the thread, as we said before Back to our function loadData, we’re going to see what networkRequest looks like, in this case We will see that networkRequest is another suspend function, but now it returns data as the synchronous version of it How can networkRequest the execution to a different thread? Well, it uses with context With context is a suspend function from the coroutines that takes in a dispatcher as a parameter Dispatcher is basically a way to say, hey, I want to run this computation in this particular thread And in this case, in IO And inside with context, we can have our blocking networkRequest code It doesn’t matter if it’s blocking the IO thread What is important is that it’s not blocking the main UI thread What other dispatchers do we have available? We have Dispatchers.IO, but also Default and Dispatchers.Main IO is optimized to do network and disk cooperations They use default for CPU-intensive tasks and main for UI code or non-blocking code that executes quite fast So here, networkRequest, we can say that this is main safe You can call networkRequest from the main UI thread, and it will be OK, because it will be in chart to move the execution to a different thread So now, we saw what problems coroutine are trained to solve, which is simply find asynchronous programming in Android But what is a coroutine? You can think of a coroutine as a runnable with super powers If you do think about it, it is going to take a block of code, and it will be able to run it in a particular thread What I like about coroutines is that asynchronicity can be expressed in a sequential way And that’s going to be easier to read and understand Also, it comes with other perks, such as exceptional handling and cancellation That is typically more difficult to do with other versions– for example, callbacks Back to our function loadData Imagine that we want to execute that whenever the user taps on a button For example, we can have this function onButtonClicked that will trigger loadData But this is not going to compile, because suspend functions must be called inside a coroutine We don’t know how to create coroutines yet We will see that later But in a nutshell, you can see we can use launch to trigger a coroutine So imagine that this works Here, some problems may arise For example, who can cancel the execution of that coroutine? Does it follow a particular lifecycle? For example, if the [INAUDIBLE] is moving away from the screen, can you automatically cancel it? Who is going to get the exception if that fails? These are the questions that a structured concurrency is trying to solve So structured concurrency is a design pattern system in coroutines that tries to solve memory leaks And structured concurrency forces you to think about those questions whenever you are working with coroutines And it does it by introducing this new concept, which is a scope– a coroutine scope Coroutine scope is going to keep track of the coroutines it creates It’s going to give you the ability to cancel them And it’s going to be notified whenever a failure happens So now, back to our function onButtonClicked, that will actually give another compiler error That’s because launch must be called within a scope How can we create a coroutine scope? A scope is just a simple variable that is really [INAUDIBLE] to credit It is not going to hold references to heavy objects So whenever you want to control the lifecycle of a coroutine, you can create a coroutine scope So in this case, we can use this scope to trigger the computation And that computation will follow, that coroutine will follow the lifecycle of that particular scope So in this case, if loadData throws an exception, the scope will get that exception and will handle it in some way Coroutines also create this hierarchy in which the root of the hierarchy, it’s

going to be the scope, which is going to be the parent of the other coroutines that it creates So in this case, for example, whenever we don’t need the scope anymore– for example, if we are in a view model, whenever we call onCleared, we can call a scope.cancel And cancelling a scope means that it will counsel all the children coroutines that it started, and it means as well that you cannot start more coroutines with that scope So with our function loadData, we can see that this suspend function, because this will suspend, means that it has to be run inside a coroutine It will run in a scope And this is quite important If we take a step back and we think about synchronous code again, we can see that when a function returns, it means that it has completed all work So imagine about the synchronous version of loadData Even it block the thread, it’s OK, but it returned whenever it completed everything it had to do With coroutines, it’s kind of the same When a suspend function returns, means that it has completed all work And this is a very nice contract to have in asynchronous operations So now we’re going to see how to handle exceptions Scopes can also take a job And a job is going to define the lifecycle of either the scope, and also the coroutines And whenever we passing a job to a scope, that means that it’s going to handle exceptions in a particular way When a child fails, it is going to provide the cancellation to other children And when a failure is notified, the scope is going to cancel himself and propagate the exception up That means that in this case, whenever loadData fails, it will notify the scope that it failed The scope will cancel the other children that it may have created before it will cancel itself and will propagate the exception up In this case, because we are in a view model and there is nothing else in the hierarchy, it will make your application crash But this might not be desired in every situation For example, here we are in a UI-related scope, and therefore, we might not want that behavior So you can use the alternative, which is SupervisorJob And with a SupervisorJob, the failure of a child, it is not going to affect other children And so when a failure is notified, the scope is not going to do anything So in this case, loadData, if it throws an exception, the scope will say, OK, I have these exceptions, but it’s not going to cancel other children But FYI, the exception can be propagated still, so you might have to try catch it TL;DR for a structured concurrency When I suspend function returns, it means that it has completed all work When the scope is canceled, it means that the children will be canceled too When the coroutine errors out, the scope will get notified, and depending on the job, it will behave in a way or another We’re going to see how to create coroutines now Before, we saw that we can use launch to do that, but it is not the only way you can do it You have to launch and async as well We are going to compare them with the similarities and differences between these two different approaches As I said, the first similarity is that they can create a new coroutine They can start a computation where you can call suspend functions But they create coroutines with a different purpose So launch is going to create a coroutine that is meant to be fired and forgotten So imagine that we have our loggingService where we can upload logs that happened in our application So we can use this scope.launch to trigger that computation, and that’s it We don’t care about it anymore That’s launch Async is going to create a new coroutine that can return a value So for example, we have this function, getUser, that takes in a user ID as a parameter, and it returns a user object Because we are in a suspend function, we don’t have a scope available, so if we want to create a coroutine, we can use coroutine scope And inside here, we can fetch our user with our user service And that computation can be happening in Dispatchers.IO, for example So we have that And that is going to– async is going to return a deferred object And you can think of a deferred object as a future or a promise in Java And with that future, or that deferred object, you can call await And await will wait– actually, it will suspend execution of the coroutine until async has finished doing its computation, and it will return the value of that coroutine And this is what we return back to getUser Another similarity, as you saw, is that both of them take a dispatcher Where do you want to run that computation?

Also, both of them are executed in a scope Launch and async are extension functions on the scope On coroutine scope, in this case And so in order to create [INAUDIBLE],, you need a scope They are not a suspend functions, so launch and async are the entry point to coroutines How you can create a coroutine so that you can call suspend functions, but they are not suspend functions And they differ on the way they handle exceptions So launch is going to throw the exception as soon as it happens, and async is going to hold on that exception until you call await We’re going to see more of that in a second So how are you going to handle those exceptions? Basically with a try-catch Here in our version of launch we had before, because loggingService can throw an exception, you can grab it inside a try-catch, and that will handle the exception thrown by the logging service With async, it’s a bit different As we said, async is not going to throw the exception Therefore, you don’t need to grab it inside the try-catch You have to do it with await Await can throw the exception that happened in the async block of code Therefore, you have to wrap it inside try-catch And there you will handle that exception Now we are going to move on to a different topic I have this coroutine on the screen that is called with launch, and it’s going to happen in an IO thread So we have a for loop, which is going to read files from a list What happens if we call scope.cancel? Is that going to cancel the execution of that coroutine or not? Well, the fact it is not going to do that, because cancellation requires cooperation Whenever you are doing something very expensive– in this case, we can spend a lot of time reading the files– you have to cooperate and make this canceller work If you think about it, the coroutines or the thread is going to be really busy reading files, and it is not going to be listening for cancellation So in this case, you have to cooperate and check if the coroutine is active or not And you can do that by checking, for example, or calling ensureActive or yield Whenever the coroutine is cancelled and that function is called, then it will stop the execution of this coroutine So again, if you are doing a heavy computation, make sure that you check for cancellation We created a lot of functions throughout the presentation And some of them were marked with suspend, some others weren’t When do you actually have to mark something as suspend? Well, this is easy Whenever it calls other suspend functions So imagine our loadData function we had before It is a suspend function Why is that a suspend function? Because it calls networkRequest That is also a suspend function Why is networkRequest a suspend function? Well, because it called with context And with context, it is a suspend function that comes from the coroutines library This is the reason why But now, when don’t you have to mark it as a suspend? Well, whenever it doesn’t call other suspend functions So onButtonClicked is just a function that triggers the coroutine It calls launch And because a launch is not as a suspend function, onButtonClicked doesn’t need to be either So the tip is don’t mark a function suspend unless you are forced to And in this case, you either mark it as a suspend or you start the coroutine, as we did with onButtonClicked We’re going to see how to test coroutines now So testing asynchronous code is quite difficult, because you want a deterministic behavior You want the test to behave always in the same way And we’re going to see different use cases for the different ways that you have to do that The first use case, for those tests that don’t trigger the execution of a new coroutine So for example, let’s say we want to test loadData loadData, it is not calling async or launch It’s a suspend function It’s not triggering new coroutines Even though it is moving to a different thread with context– that is what networkRequest is doing– it is not creating a new coroutine, so we are OK here This is the first use case You can test these using runBlocking runBlocking is a method from the coroutines library that is going to start a coroutine, and it’s going to block the thread where it’s been called until everything finishes, until the block of code finishes So in this case, we can create a viewModel instance called loadData, and loadData is going to be executed synchronously And so we can make sure that the next line after loadData means that loadData has finished doing everything So now we can assert that show did something That was quite easy, right? The second use case is more complicated, and it’s for tests that trigger new coroutines So for example, we have MyViewModel,

and we want to test onButtonClicked onButtonClicked is triggering a new computation, is triggering a new coroutine calling launch Can we use the same way as we did before runBlocking? Well, if we think about it, whenever you call onButtonClicked, it is going to run a new coroutine And that coroutine might be potentially running on a different thread And so it is going to happen asynchronously So onButtonClicked is going to call launch, and it’s going to move on It’s going to end the function So whenever you call assert, it might be happening that the other coroutine is still running Therefore, it’s not reliable You cannot use it Another way is waiting for something to happen So imagine that you update some result somehow, and you wait for that to have a new value Either having a CountDownLatch, or using LiveDataTestUtil if you’re using live data, or with Mockito await You have different ways to wait for something to be there But that’s a code smell That’s because the test is not going to be running fast Might take a couple of seconds for something to be there And that’s kind of a bad practice The thing is that you might have to run it sequentially, as we did before with runBlocking And how can you do that? Well, basically, we can do that by forcing the coroutine to run in a particular dispatcher And a good practice for this is injecting dispatchers So in this case for the viewModel, you can inject the dispatcher in the constructor, and then use that dispatcher to trigger the coroutine inside onButtonClicked And now we know our tests, we can use a test coroutine dispatcher We create this test coroutine dispatcher instance that we are going to pass into the viewModel So that coroutine is going to be executed in this test coroutine dispatcher And in the test, instead of using runBlocking to wrap our test body within it, we are going to use the function runBlockingTest from the testDispatcher That means that every coroutine running on that dispatcher is going to be executed simultaneously In that case, whenever we call onButtonClicked, it is going to execute that coroutine simultaneously That means that whenever onButtonClicked finishes, means that the coroutine has finished too And now you can assert whenever show did something without having to wait for that to happen But now, imagine that onButtonClicked has to do something else before running the coroutine How can we test that? Well, testCoroutineDispatcher comes with nice functions to be able to post execution of coroutines, and to resume them So in this case, we can call testDispatcher.pauseDispatcher to assert that something happened before the coroutine, and then call testDispatcher.resumeDispatcher to test what happened after the coroutine run So for testing, use runBlocking when the test doesn’t create new coroutines And as a good practice, inject dispatchers and use TestCoroutineDispatcher in tests whenever you want to test something that is going to trigger new coroutines That’s going to be the end of the video We went through a lot of stuff So we said what problems coroutines are trying to solve in Android, which is simplifying asynchronous programming How we can move the execution to a different thread using dispatchers and withContext What a coroutine is Basically, you can think of it as a runnable with super powers How coroutines work under the hood The Kotlin compiler will rewrite the suspend functions to use callbacks The principles of structure concurrency that is going to avoid memory leaks and is going to force you to think of who is going to manage the lifecycle of that coroutine whenever you are working with them How to create coroutines, and the differences between launch and async How to handle exceptions When to mark a function as a suspend, or not And testing coroutines, and the usage of TestCoroutineDispatcher If you are interested in more things about coroutines, I suggest that you go through the different Codelabs that we have available We have two of them– a basic Codelab and advanced Codelab that uses live data, coroutine builder, and more advanced topics If you are more interested in testing coroutines, there is a video at Android Developer Summit 2019 where I give a talk with [INAUDIBLE] And now, for cancellation and exceptions, another video in KotlinConf 2019 with Florina Muntenescu Thank you for watching Bye