MANUEL VICENTE VIVO: With Coroutines
growing in popularity on more and more platforms
and recently becoming our recommended way of handling
asynchronous work on Android, the ABC of Coroutines
is not just another awesome basics Coroutine video.
I would like to go with you through some
of the most common classes and functions just in Coroutines.
We will cover concepts such as what's Coroutine context, job,
dispatchers, and more.
If you learn something new, like the video
and subscribe to the channel, but only if you think
we've earned it.
Let's get started.
[MUSIC PLAYING]
The first topic to cover is CoroutineScope.
A CoroutineScope keeps track of any Coroutine
you create using launch or async Actually,
these are extension functions on CoroutineScope.
In Europe, you should create a CoroutineScope
whenever you want to start uncontrolled the lifecycle
of Coroutines in a particular layer of your app.
For example, in this class that has a well-defined lifecycle,
we can create a CoroutineScope by calling the CoroutineScope
function.
With this scope, we can create new Coroutines.
And these Coroutines will follow the scopes lifecycle.
We recommend using launch to get into Coroutineland,
are opposed to async.
Async isn't meant to return a result, which
could need another Coroutine, and treats subsections of it
differently.
As new Coroutines created by this scope
follow its lifecycle, whenever the example class instance
needs to be cleaned up because it's going to be destroyed,
you can cancel all ongoing work automatically.
Do this by calling cancel on the scope, which
cancels and stops executing all active Coroutines.
As creating and managing scopes manually
might require some boilerplate, in Android,
we provide KTX libraries that already
provide a Coroutine scope in certain lifecycle classes,
such as viewModelScope for the View Model class
and lifecycleScope for lifecycle owners.
The View Model Scope property is available in the new model
class.
Any Coroutine launched in the scope
is automatically canceled when the view model is cleared.
Coroutines are useful here for when
you have work that needs to be done
while the view model is alive.
Similarly, you have lifecycleScope
for lifecycle owners, for example,
in this App Compat Activity.
These two scopes that we have seen
run the Coroutines on the main thread,
as the scope is configured with Dispatchers.Domain.
To change where the Coroutine runs,
us a Coroutine dispatcher that I'll cover in a second.
Here we are passing Dispatchers.IO
to load the asset from an IO-optimized thread rule.
What does launch take as a parameter to allow this?
Launch takes a CoroutineContext as a parameter.
But wait, CoroutineScope does it, too.
So what's a CoroutineContext?
A CoroutineContext is an indexed set
of elements that define the behavior of a Coroutine.
There are four elements you can use and combine
in a CoroutineContext.
We've already seen the first one.
A CoroutineDispatcher dispatches work to the appropriate thread.
Dispatchers.IO is optimized to do I/O, disk,
and network operations.
Use Dispatchers.Default for CPU-intensive tasks.
It induces a thread rule with the number
of calls available in the device or machine.
And main for everything else, new IU or a non-blocking code.
Another element that can be added to the CoroutineContext
is the CoroutineExceptionHandler,
which is an optional element that allows you to handle
[INAUDIBLE] exceptions.
CoroutineName gives a name to the Coroutine, which
might be useful for debugging.
And the last element is of type Job,
which controls the lifecycle of the Coroutine,
not only a Coroutine, but also a CoroutineScope.
As the CoroutineScope function takes a CoroutineContext
as a parameter.
When creating a CoroutineScope, you
can pass in a job to control its lifecycle.
But if you don't do it, the function will do it for you.
So now you can cancel the scope itself as [INAUDIBLE]
before by calling scope.cancel, or you can cancel the job.
Both options are functionally equivalent.
You can combine multiple CoroutineContexts
using the plus operator, as the CoroutineContext
is a set of elements.
When combining them, a new CoroutineContext
will be created, with the elements
of the context on the right side of the plus overwriting
those on the left.
As what we have on the screen are elements
of different types, the resulting CoroutineContext
will contain the three of them.
What's important is that new Coroutines created
by this scope will inherit its CoroutineContext
and will overwrite the job element
with a new instance of job.
To change the CoroutineContext within a Coroutine,
use withContext.
That is a suspend function from the Coroutine's library.
As jobs take a key role in Coroutines,
let's look at them in more detail.
A new Coroutine returns the job with which
you can control its lifecycle.
In the example, we are creating a new Coroutine
using launch and assigning the return
value to the variable job.
Then we could continue with our business logic
while the Coroutine is being executed.
If for some reason we had to cancel the ongoing Coroutine,
we can call job.cancel to stop its execution.
The job also influences how errors are propagated.
Let's say we create this CoroutineScope with a job
and then launch four new Coroutines.
We could graphically represent them
like this, where it's child, whose parent is the scope,
is a Coroutine.
If one of the children fails, the failure
is propagated to their parent, which
will send a cancellation signal to the rest of the children.
Then the other Coroutines will get canceled.
The scope will get canceled, too.
And then they will propagate an exception up
to their thread's default exceptional handler.
But you may not want this behavior in some cases,
for example, in a UI-related company scope.
If a Coroutine fails, you might not
want the UI scope to be canceled,
and therefore having a non-responsive UI.
For those cases, you can use a SupervisorJob.
With a SupervisorJob, the failure of a child
doesn't affect other children.
They won't get canceled.
Moreover, a SupervisorJob won't propagate the exception,
either.
And it will let the Coroutine handle it.
Here in the same situation we were before,
but with a SupervisorJob, when a child fails,
the rest of the children won't be affected.
But be careful.
Unhandled exceptions are still propagated.
If you don't handle the exception in the Coroutine,
you will need a CoroutineExceptionHandler
in the scope.
As we have seen, a CoroutineScope
can control the lifecycle of the Coroutine created by it.
And the job in its CoroutineContext
defines the error policy.
To have even more control over this
when you're in a Coroutine, you can create some scopes
to logically group other Coroutines.
And this is because the outer scope suspends
and won't resume until all the Coroutines created within it
complete.
The SupervisorScopes inherits the CoroutineContext
used in the outer Coroutine and will overwrite the job element
with a new job or SupervisorJob accordingly.
In this example, we as SupervisorScope in a Suspend
function to run two tasks in parallel.
As it induces a SupervisorJob, if a task fails,
the other one won't be affected.
However, doing the same using a CoroutineScope,
if our task fails, the other one will be canceled,
and the exception will be propagated up
to the Suspend function code.
Well, this was more than ABC.
We also had that D for Dispatchers, J for Jobs,
and S for Scopes.
But I hope you got a broad understanding
of the different concepts involved in Coroutines.
Do you want to learn more about Coroutines?
For the basics of Coroutines in Android,
go to this Coroutines 101 talk.
For more information about cancellation and exceptions,
check out this talk Florina Muntenescu
and I gave at KotlinConf 2019.
And to dig deeper and see how Coroutines work under the hood,
go the Suspend function Kotlin Vocabulary video.
Thanks for watching, and go write better Android apps
with Kotlin.
[MUSIC PLAYING]