[MUSIC PLAYING] ADAM POWELL: So I'm Adam. CHRIS BANES: I'm Chris ADAM POWELL: And this
is Android Suspenders. Today we're going
to talk a little bit about Kotlin coroutines. But to get started, a little
bit of background-- many of you are probably familiar
with this already-- is the idea of
Android's main thread. And just like almost every
other UI tool kit out there, Android exposes a
single UI or main thread for doing updates
to parts of your UI. So some of the
things that you might find yourself doing on the
main thread, inflating views. You could be doing measure
and layout operations to change the shape of
your view hierarchy. You could be drawing. Or many other
things really, like processing input events,
and so on and so forth. So today, you're
probably familiar with this 16-millisecond time limit. The vast majority
of devices out there have a refresh rate
of 60 hertz, which means that you have just
over 16 milliseconds to do all of the work
necessary in order to deliver a smooth frame rate. So what happens when that
display rate goes up? So 90 hertz means you
have 12 milliseconds. 120 hertz means you
have eight milliseconds. And you have less and less time
to get some of this work done. If you take a look
at a CIS trace, you'll usually find
that the things that cause you to miss
a frame and thereby have kind of jank
in your application, is really app code. If you're binding items
to a recycler view or reading from a data
store, so on and so forth-- These are all bits of work
that it would be really great if we could keep this
off of that main thread and out of this critical path. So how do we fix it? Well, this talk isn't
about achieving eight millisecond refresh boundaries. This is about making use of the
resources we have available. And all the phones that we have
multiple cores in them today. So how do we actually
make use of that? Well, some of you probably
remember this thing. It's been around for quite some
time and has a lot of issues. It composes a little
bit badly, has some issues around rotation. It's really easy to get wrong. There's executors,
which most Java developers are familiar with. And this kind of lets
you share some thread pools, which is nice. But it's otherwise kind
of a really raw API. You can build things out
of it but it's not really convenient to use on its own. Loaders-- loaders
are a thing that were certainly out there
that solved a few problems. But it was pretty
narrowly scoped. It's really now deprecated. So there's not a
whole lot there. We can use futures. There's listenable future,
which has started showing up in more AndroidX APIs. But you may not want
to pull in all of guava and all of the
infrastructure that really helps you leverage some of the
things that you can do with it. And unless you're
working on min SDK 24, you can't use
CompletableFuture either. And really there's a whole
bunch of other reasons why you might not want to
use that to begin with. So there are a lot of
other libraries out there. I mentioned guava. Guava is certainly one that
you can pull into your app. RxJava is super-popular. It's a great library. Does a lot of things well. But if you're sitting
here in this talk today, you're probably here to
talk about coroutines, which is a new feature that went
fully stable in Kotlin 1.3 just this fall. So why should we use them? CHRIS BANES: Great. So when I think about this
question, I'm thinking about, what does your
typical mobile app do? And I think for
the most part, you can describe them as
kind of CRUD apps. And I don't mean
the American slang. I mean the fact that they
create, they read, they update, and they delete data. Usually its some
kind of local data source like a database
or whatever it be. And then going a bit
further, a lot of apps will also have
some kind of sync. So they'll upload data
and pull data back from some kind of web service. Now, apps are actually pretty
simple, computationally. You're not really taxing
the CPU that much, really. Yes, the logic can be
quite tricky to get right. But actually they're
pretty simple. And of course, Android, we
put a lot stuff in your way. Like, we make your life harder. But actually, your
apps are actually pretty simple from a
logic point of view. So why coroutines? Well how will they
actually fix that? Well I think that great
for I/O tasks, especially for a resource-constrained
system, like the phones and tablets
that you're using today. Now, you can't create a thread
for every network request you ever use
because threads take in the space of about a
megabyte to two megabytes of RAM every time you create one. That's why thread pulls
actually cache threads for you. Now, coroutines,
on the other hand, take in the sort of realm
of 10's of kilobytes in terms of coroutines. They use threads underneath,
but they use them in a much smarter way. Also in my mind anyway, they
have an easier development model. It's a lot easier for developers
to come into a new code source and see an imperative
line-by-line coroutine over something like
an RxJava chain. To fail to understand
what's happening there is in my mind anyway, easier. And same thing for callbacks. Everyone knows
about callback hell. And I'm to go line-by-line and
see where you've been called and stuff. And so coroutines help,
hopefully anyway, fix that. So a lot of this talk
was written in mind with a sample app
that I've been writing for the last months on Tivi. It was originally-- I
went all in on RxJava. It was RxJava from
top to bottom. And I've kind of slowly
brought coroutines into it. It's now about 50%
coroutines, 50% RxJava. So it still uses both. And I think they both have a
place in Android development. And so yeah. Go to use both. Now, as Android
developers, we have to care about APK size
and method counts. Now, in Kotlin, in
my app anyway, I'm using free libraries from the
Kotlin coroutines kind of, tree. The core, which
contains the majority of what you'll actually use. Android, which adds support
for the Android main thread, amongst other small things. And then Rx2, which allows
you to interact with Rx Java. So you're not using RxJava,
you won't need that one. It's pretty small anyway. Now, if you actually pull
down those jar files just from Maven Central,
or wherever, they would come to about
724 kilobytes. So that's quite big. And there should be-- alarm bells are ringing. But once you actually
put that in your APK, it actually shrinks down
because it's packed in. And it comes down to
about 500 kilobytes. But that method references
are still quite high. That's 5% of your 64k. As soon as you turn on
Minify, and now this is just tree-shaking. So there's no optimization
turned on here. Or ProGuard, they both
have similar results. You're looking about 113K. So a lot less. And again, the method
reference is dropped. But as soon as you
turn on optimization, and then you go
through all this trying to fix all the ProGuard rules. You're coming down
that magic value which is less than 100 kilobytes. And again, your method
reference is now 834. It's less than 1% or
1% of your references. Now, one thing to note when
you're using ProGuard or R8 is that you need
to use this rule. It's not currently
bundled with the jars, but hopefully it'll
be added soon. But it's a pretty
simple rule to add. ADAM POWELL: All right. So hopefully by
now you're thinking about how you might use
coroutines in your app. So let's go ahead
and talk a little bit about actually writing them. So anything that you can do with
a one-shot callback or future, you can do with a
suspend function. Everything with
coroutines is kind of based around this idea
of suspending function as a basis for creating APIs. Suspend functions can stop
and yield their thread without blocking. And they can be resumed
later like a callback. And they can only be called
from another suspend function. So just to setup all the
machinery involved in that. But the core thing here is
that all of this kind of fits on one slide. There's less to remember. The rest of the language
still works the same in the presence of
suspend functions. And we'll spend the rest of
the talk talking about that. So here's a suspending
method from Chris's app. Actually, this is a data
repository for TV shows. So you go ahead and you call
this update show function with an ID. We get some shows. And we get a little bit more
data from a remote source. And a little bit more
data from a second source. Finally, we merge all that
together and we save that data. So these three main tasks where
we spend the bulk of the time, these are done sequentially. But none of these tasks have
a dependency on one another. So wouldn't it be nice if we
could do this concurrently? So with the async
builder, we can do this. So we're going to start
from the very top here. We start by opening
coroutine scope. And this is important
because this allows us to perform this
parallel decomposition using the async builder. It brings a coroutine
scope receiver in the scope for this
lambda block that we have. So first we build
the async operation. And the second. And the third. And then we await the
result of each one, in turn. So the nice thing
here is that we've launched all of
these things, let them run kind of
independently, and then we bring them back together again. So since all of these things
can be now done in parallel, things should complete faster. So trying to do this
with raw threads by hand would be a lot more
code, which you would need to maintain along the way. So the async builder
is for when we want to run something
and then later await the result after giving it a
chance to finish while we're doing something else. It's very similar to C#
or promises in JavaScript. So what about when you just want
to launch something and forget about it, though? Well, there's
something for that too. It's just called launch. It works pretty
much the same way. So in this case, it
is a lot more similar to executors and just
sort of submitting a task, submitting something to an
Android Handler, for example. When we just want to fire and
forget and deal with it later. So that's a little bit about
the basics of running coroutines in isolation. How do you run them
on Android, though? So in this case, you might have
an arch components ViewModel. And wouldn't be nice if
we had kind of an easy way to put all this stuff
together automatically? We need it to be
able to open a scope. So where do you get
one of those things to begin with that you
can launch things into? Well, in this
case, our ViewModel has a show repository,
which is our data layer. And we have a view state that
the viewer activity or fragment can observe. We can refresh on construction. And when we actually
go to refresh, we launch into this new
ViewModel scope extension property. This is coming very soon
to the AndroidX libraries. So those of you
who like to watch commits in AOSP might have
seen this going already. So the refresh function
launches a coroutine, uses the launch builder,
which then calls the update function on the repository. And our coroutine resumes
back on the main thread after update show returns. So we can safely manipulate our
view hierarchy with the results just on the next line of code. We have this nice, clean,
sequential ordering of operations. So those of you who
want to go check out this thing that's upcoming,
you can go to this link. Take a look at the
change so far in advance of the actual release. This is coming in an upcoming
release of our KTX libraries. So let's ahead and
demystify how some of this works a little bit. But before we can
go too, too deep, we need to start
talking about some of the other primitives that
are at work under the hood here. CHRIS BANES: Great. And so we are going to talk
about jobs a little bit. So what is a job? Here, when you actually look
at this code snippet-- the one we just looked at from Adam. You're actually using
the launch method. Now, when you actually
run that launch method, it returns what we call a job. A job allows us to
keep a reference of that ongoing piece of work. And it has really
one method on it. And it's called cancel. Now obviously in
this example, we wouldn't just call
cancel straightaway after we've launched something. That would be ridiculous. But what it does allow us
to do is handle something like double-refreshes. If you have something
like pull-to-refresh in your app and an
automatic refresh, you don't want them both
to happen at same time. And then you have two things
happen at the same time. So here this code snippet,
you can keep a reference to the one that's
currently running. And if it happens again-- like
a refresh is called-- then you can cancel the first one. And so that's kind
of how job works. It's a very simple
object that allows you to keep a reference of
the ongoing piece of work. So you may have seen
that little scope thing and wondered what it is. Adam explained it earlier. And you can have a scope
and it provides you with all the context you need
to run a launch or an async. So let's take a look at how
they actually work underneath. So coroutine scope
is an interface that allows objects to provide
a scope for coroutines. Think things like
things with a lifecycle. So fragments, activities,
view model even. And they can provide a lifecycle
for the coroutine itself. And start and stop
it as it needs. Async and launch used to be
the sort of global methods. And now a recent refactor
brought them as instant methods on the coroutine scope. What it means is,
mentally anyway, is that instead of just
launching something, you're launching
a coroutine on x. So I'm launching a
coroutine on the activity. You just change that
mentally in your head in that it's tied to the
lifecycle of something else. ADAM POWELL: Right. If you're used to working
with the lifecycle owner in arch components,
so far, you know the lifecycle owner has a
lifecycle that you can then observe and attach things to. Similarly, a coroutine scope
has a coroutine context that carries along
everything necessary to know how to launch a coroutine. CHRIS BANES: Some
of the stuff we're going to talk about in a bit. And similarly, coroutine scopes
provide a default context. So you can provide a default
context for all the coroutines that were ever run on it. So I think things like
what kind of thread pull or dispatch it
runs on, the job-- the parent job-- that allows
you to cancel it later. And other things which
you can add to it. It's basically a
map for context. ADAM POWELL: And it's on again. CHRIS BANES: --again. It's a good day for slides. Oh, we're back? OK, cool. So let's take a look
at another example. This time we're not
going to use view model scope, the automated thing
we've added to AndroidX. We're going to write
it out ourselves. And so here you can see
that we've created a job. And now it's just a
simple instantiation. We're going to keep
a reference to it. And then we're going to create
a coroutine scope using our job. And that means that
anything that runs on it allows us to track back
using that job object. We're also going to give
it a default dispatcher. We're going to talk
more about that later. But basically what it
means is that anything that is launched on that scope
will be automatically run on the main thread-- the Android
main thread in this example. So once we've done that, we
have our refresh method again. And this time, instead of
that view model scope-- the automated
thing-- we're going to use our own created scope. And again, it's
exactly the same code. But it's just using a different
type of scope this time. But this time the launch
will be scoped to the job object we created
earlier, which means that in our uncleared,
which is the callback we have in ViewModel,
to actually not when it's been torn down. We can actually just
call job got canceled. And that means that any
coroutines which are currently running when the
ViewModel goes down will be canceled
at the same time. It reduces memory leaks
or whatever it is. It just allows it to tidy up. So if you actually have a look
at how things are run now, so we've launched our coroutine. And now we're going to go into
our update sharing method. So here we are in our coroutine. So our launch, which is
modeled by that blue thing going around. Now, here in the
updateShow method, which is denoted by the
yellow arrow-- so we're going go past that first
piece of code, which is the async builder. At this point, we have a
first coroutine running, which now racing. And that's doing the
local show still stuff. So it's running nicely
and it's doing its thing. Now the outer coroutine
is going to go past that and go past the second
async which is the remote. Here, again, so we now have
two coroutines running. Well, we have three
coroutines running. But two inner ones,
two child coroutines. So once they launch and they
are running and going along, we fall onto the first to wait. Now, at this point, we're
waiting for that first async-- the local-- to actually finish itself off. And then return a result, which
is what await will return. But at this point, because we're
waiting on that first async, the outer coroutine is
what we call suspended. It's just sat there
doing nothing. But during that time, that
view model has been torn down. I don't know, the
activity has gone away. Or whatever it will be. And we called job.cancel. Now, this point, the outer
coroutine, is canceled. And then the inner
two are also canceled. And now some of that
coroutine scope gives us-- and scoping in general
gives us for free. And that child
coroutines automatically inherit from its
parent coroutine. So if the parent coroutine
has been canceled, anything below it will also be. That some of the nice stuff
that has been added recently. But what if you're not
actually using ViewModels? You know, a lot of us
might not be using it. And then we have
other APIs which later do similar type of things. So it's part of the Android
architecture components. We added a list of
functionality for life cycles. So here's a very quick example
of how you use them generally. You create a lifecycle observer. And in this example,
we're using default. And when you create--
it has methods for each of the different
lifecycle events-- so on create, destroyed, stop,
start, whatever it be. And to actually use it
on like an activity, or whatever it be, you create an
instance and you add observer. Hopefully, you've
seen this API before. So this builds a kind of scope
where lifecycle observer which allows us to scope coroutines
to an actual lifecycle instance. The primary API
we'll use here is that we're passing the lambda. And that's the thing that we
run once we've been started. Which is kind of what
you want, usually. Most of the time. So we'll now look
at implementation. So the first thing we
want to do is on stop. So that's when we'll start
running that piece of code. So we'll create a coroutine
scope using a job. And then we're going to run
it on the dispatches domain. You can choose
what that will be. And then we'll call
script.launch, and then just call our lambda. Pretty simple, really. And then finally, a
nonstop, which seems like a good lifecycle to use,
that we'll call in that code, but will eventually
cancel the job. And that will mean
that coroutine, if it's still running at
that point will be canceled. So you can see that code
isn't actually that complex. It's AndroidX, so
it's not nice to use. But actually, if you look
at the deep down of it, it's actually pretty simple. And then to actually
finish it all off, we'll provide a
nice build function. And you pass it a lambda,
and it will automatically add the Observer for you. And what it allows us to
do is stuff like this. So here, we have
a DetailsFragment and onViewCreated. We will use our liveScope
extension function and then just run something. And that will automatically
start it when we get to-- well, it will be started when
we go start a new fragment. And it will be closed or
ended when we get on stop. All right, brings us
on to cancellation. ADAM POWELL: Right. We've talked a lot about this
idea of cancelling a coroutine. But what actually happens
when this thing cancels? I mean, if you just have kind of
a block of code that's running, what gets torn down? What do you need
to do to clean up? So when a routine
is canceled, if it's canceled while
it's suspended-- so it's waiting on
something else to happen. In callback terms, the callback
just hasn't been invoked yet. It will throw a
cancellation exception, or rather, the coroutine
will resume from the point that it was suspended with
a cancellation exception. So what does that
actually look like? Here's our example from before. So what happens if we
need to clean something up if this is canceled in the
middle of that updateShow function? Well, because it throws
a cancellation exception. This is something that we kind
of know how to do already. Plain old finally
blocks runs expected. We didn't have to add any new
concepts beyond what we already know from the rest of Kotlin. But if blocking code is
running, cancellation requires some explicit
cooperation in order to stop what it's doing. So we can check for
that cancellation explicitly in a couple of ways. Generally, this
means checking to see if your coroutine is currently
active in one way, shape, or form. And there are a couple of
useful patterns for doing this. So one of those
patterns is that if you know that your job
is already canceled, you can call any
one of the stock suspending methods, such as
yield used is the example here, to force a cancellation
exception be thrown, relying on that standards and
implementation that I mentioned before, that if you're canceled
when you're trying to suspend, then you'll resume with
that cancellation exception. So we know that this
will immediately throw if we happen to be canceled. But if you're in
a tight loop, you can also just
check this isActive that's available from
any suspending scope. And you can just simply
stop what you're doing. There's no reason to really
involve an exception here if all you're doing is some sort
of tight inner computation loop that you need to break out of. Which kind of leads nicely up
into how exceptions are handled with coroutines in general. And there are a few things that
are really worth pointing out, especially if you followed
some of the kotlinx.coroutines development leading
up to release because there were some really
significant changes that happened. The first is that launch will
rethrow unhandled exceptions as they happen. So more precisely,
it fails the parent. It cancels its parent job. The parent sees a
cancellation exception with the original exception
as the cause in the exception object itself. So similar to a thread, they
get dispatched to the default exception handler at
the root of a job tree if nothing else
manages to handle that. But your coroutine context also
gets a chance to intercept. You can attach a special element
to the coroutine context itself that you can use to handle
unhandled exceptions. So let's go ahead and
see how that works. Here's, again, our
example from before. And say that saveShow throws a
very domain-specific exception in this case. So in this case,
this will treated like an uncaught
exception at runtime just like anything else that
throws an uncaught exception on your main thread. So async is a little
bit different. If an exception's
thrown while something that you launch with
async is running, it'll hold onto that
exception and only throw when the caller calls await. So coming back to our
example from before again, let's go ahead and
use what we know. We throw our exception from
one of these async jobs. But that gets thrown from
this call to await itself. So we know exactly where
we need to try and catch that exception just kind
of in the normal way and handle that error. But there's a gotcha here. And that's that async
works the same way as launch in terms of how this
nested job tree is handled. The deferred object
that it returns is just another kind of job. And so it'll cancel
the parent if it fails with an
unhandled exception, just like launch does. And it'll do this even if we
did it an await and called it. Now this is kind of important
because if something throws an exception, it's
really important that your app know about it. Like it shouldn't just
disappear into the ether. But at the same time
it kind of defeats the purpose of the code
in this sample here. We caught it. What are we supposed to do here? Well, in this case, instead
of using coroutine scope at the top to open up this
parallel decomposition, we can use supervisorScope. It works exactly
like coroutineScope, except with a
supervisor job, which is a special kind
of job that won't be canceled if a child fails
with an unhandled exception. One more spot-- yep. It will only be thrown from
this await method here. CHRIS BANES: Cool. So earlier, we mentioned
that we can actually decide where coroutine run
and what kind of thread they run on. Because actually on
the JVM and on Android, we actually still
run on threads. It still uses a thread
pool underneath. So we still can decide
where that actually gets dispatched on. Now let's have a look. So here we have our launch,
a very simple launch, which is missing a scope. But just look at the example. Now, default actually
is that the context will be used in what we
call a Dispatchers.Default. That's a default
dispatcher that's kind of given for you for free. And it's supposed to be sort of
default for everything, really. ADAM POWELL: It's essentially
a computation thread pool, if you're used to using
that from our RxJava. CHRIS BANES: So what is
a coroutine dispatcher? Which is what it basically is. Well, it's the thing which
runs and schedules coroutine, as I mentioned earlier. It schedules coroutines
to run on something, on a thread in our case. Now, Dispatchers.Default, which
is the default which you get, is that it uses
cpuCount threads. So your device has
four CPUs in it. You will get a
thread pool of four, which isn't so great
for things like I/O. So most apps will be doing
a lot of network or disk or whatever it be. So it's not so great for that. So yeah, it's mostly more like
a computational type dispatcher. But it's also an elastic
thread executor, which we'll talk about in a minute. But it's the
default. There's also Dispatchers.IO, which was
added fairly recently. And it was designed specifically
for blocking I/O tasks, so things that we
really care about-- network, image loading, reading
disk, database, blah, blah, blah. It uses a minimum of 64
parallelism, which basically means that you can have up to 64
tasks running at a time, which is great for what we need. So yeah, you can
launch it like that. But the really great thing
about the I/O dispatcher is that it shares thread pools
with the default dispatcher. And the point where it
becomes great is this. So here have an example where
we have an async, which is using the default dispatcher. And then we're going to load
an image on the I/O dispatcher. So we can do some disk reading,
some kind of load image, whatever it be. And then we're going
to use that result and then process it somehow. It's a computational task. Now, because this is running
on the default dispatcher, there is no actual
thread switch in there because we're using those
shared thread pools. I/O and default
uses shared threads. Therefore there's no
actual thread switch, which makes it a whole lot quicker. And then we also have
Dispatchers.Main, which we've spoken about
a little bit already. It allows running coroutines
on the UI main thread. A nuance with that is that
in later releases anyway, it uses service loader
to load the dispatcher in your code, which
is kind of tricky when we have things like ProGuard. So you have to be
careful with this. But yeah, just be careful. And the thing you
need to know is that you need to add the
Android dependency, which we spoke about earlier anyway. And then to use it as we said. Just launch in the
Dispatchers.Main. So that brings us a little
bit onto reactivity. So how many blog posts, that
you've seen recently anyway, can be summarized as this slide? [LAUGHTER] And I, myself, have
been guilty of this. [LAUGHTER] Now I'm going to make the
premise and the statement that most devs use
RxJava just because it makes threading easy. So the fact that you
have schedule on, and you can easily switch afraid
on-- switch multiple threads. And that's why most RxJava-- its use is. Now, of course,
there are going to be people who go tell, not
top to bottom with chains and reactivity. But I think for
the 80% of cases, it's just a switcher thread. And that's because the
APIs that we have, and we spoke about them earlier,
aren't so great to use. And because of that,
most people end up using things like Single,
Maybe, and Completable because that's
specifically what they are. They're single, one-shot things. Single allows you to
always have a type. Maybe it's nullable. And Completable doesn't
have a return type. So they're all pretty similar. But in fact, they only actually
exist on RxJava, RxScala, and RxGroovy. They don't actually exist
in any other platform. So as I said earlier, maybe it's
more of a reflection of the API that we have are at
disposal rather than the fact that they're needed. So coroutines can actually
quite easily replace Single, Maybe, and Completable. They do exactly what you think. We said earlier about
replacing callbacks. They also replace
these quite nicely. So here's an example. So we have a Retrofit
interface, which has a GET. And it turns a Single, which it
just returns a list of Shows. And the way you'd use that in
RxJava is you do this chaining. So you'd switch the
scheduler using scheduleOn. And here we're going to use
the I/O scheduler, which is provided for you. And then we're going to do some
calls, too, when it's finished. Now, the nice thing about
the Rx2 interrupt library of coroutines is that you can
actually use that exact Single as an await. So you can actually use it
as a suspend and deferred. So it's really handy for
when you're kind of slowly migrating towards
coroutine and you do want to change
everything from day one. You can actually keep those
interfaces, all those server pools. And you can actually
just call await on them. Receive inside can
be using coroutines. So it's quite a handy
way to slowly migrate. Wouldn't it be great
if we can actually made that Retrofit call
just a suspending function and just remove the
whole Rx from the start? Well, we can. And that is actually
going to Retrofit soon. Jake has a PR, which
is pending review. But he tells me it's soon. So yeah, it's coming soon. And then, if you look
at consuming code, it's pretty much the same. It's just we can now
get rid of that await. And it's just a normal
suspending function that we've called, and it's
just a normal coroutine And that brings us on to
our final section, which is kind of bringing it all
back together and trying to think of two scenarios
where we show you how to use coroutines to make
your lives easier on Android. Both the examples are
all about location. The first one is
about actually getting the last known location,
which you can kind of think is a one-shot callback. So here we're going to use the
FusedLocationProviderClient, which is from the Google
Play Services API. It's actually kind of
cool because it combines all different providers we have,
like Wi-Fi and GPS and mobile and whatever it is, Bluetooth. It provides all those providers
for you into one single API. So it's actually a
really nice API to use. And it returns a Task, which is
a kind of future-y type thing that Play Services library has. And yeah, so you call
client.getLastLocation. And it returns a Task. And from the task,
you can add what we call a complete listener. And then you'll get the
result back eventually. So it's completely async. So when you think about
it, what we're doing is converting a callback API
into a suspended function. That's pretty much
what we want to do. And luckily, the
coroutine library has two builders that
do exactly what we want. The first is suspendCoroutine. And what you do-- you pass
a lambda, which allows you to set up your callbacks. So basically what we'll do
here is call Play Services and go, get me
the last location. At that point, the
coroutine immediately suspends waiting for
the result to come back. And then the callback can
wait you back up, basically. So we'll go through
an example now. Yeah, you're giving a
continuation to later resume. That's how we do. And that's how you
pass the result on. Analyze the calls
and cancel the call. So actually, that's different. Hold on. So there is a newer
version-- sorry. Didn't mean to skip that. There's a newer version called
suspendCancellableCoroutine. It's like a little
add-on on top. And it allows you
to cancel the call. So say your
coroutine's canceled. You can then tell
the underlying API, in this case Play Services,
to cancel its call. So let's build a function. So here we're going to have
a suspended function called getLastLocation. And it returns an
actual location. It doesn't return a future
or anything like that. It's just a straight location. So I'm going to use our
suspendCancellableCoroutine builder. And then we're going
to have a continuation. That's our kind of
callback-y type thing. And then we're going to set up. So we're going to call location
client, which is the Play Services API, last location. And then we're going
to-- we get a Task back. And then we're going to
add our onCompleteListener. At that point, we are going
to wake up our coroutine. So that is how we pass back
to the suspended coroutine the result. And
it then wakes up. And the suspended function
basically wakes back up, resumes. Now, because this
is a cancellable-- I'm actually backing
up a little bit. Because we are using Task, we're
not using the success listener. We're using the
complete listener, which means that the Task
itself can throw an exception. It can fail for whatever reason. So you don't have location
permission or whatever it be. Now it will raise
an exception on you, which means that
you can populate that backup to
the call, which is done with a resume
of exception method. And finally, because we're using
suspendCancellableCoroutine, we need to tell the Play Servers
API that we've been canceled. So therefore it should cancel. So we did that with a callback,
which is invokeOnCompletion. At that point, assume you
know that the coroutine's been canceled,
which means that you need to start Play Services. Now, this API doesn't actually
exist because Play Services doesn't have a cancel method. But imagine it just exists. Now Adam's going to
talk about observing. ADAM POWELL: Sure. So what happens when
you want to observe a sequence of events over time? Now this is the thing that
Rx is really, really good at. If you're using Rx for
anything, it should be for this. And people tend to compare
RxJava and coroutine quite a bit in blogs
and so on and so forth. So what does that actually
look like if we try and emulate this using
coroutine as a primitive? So Play Services' location API,
in addition to letting you get just kind of a "one-shot,
what's my current location," it lets you request location
updates using a callback that's invoked multiple times. So normally this is a prime
candidate to be an observable. You register a callback
at subscribe time. And unregister when that
subscription's disposed. We get this composable
control over shutting down the updates really cleanly. So Rx is a great library. It offers a ton of functionality
to build things like this. So how many similar
benefits can we get if we base this off
of suspending functions? So let's, again, start just
writing a simple function, or at least it's going
to start out simple. Suspending functions
don't return until their work is all done. So there's no disposable
or closeable return by it since the calling scope
itself is cancellable. We just don't return
until we're done. So our observer in
this case can just be a simple lambda that
accepts a location result. And we'll go ahead and call
it whenever a new location is reported without
actually returning from observed location. So if you take in this giant
pile of code here, some of you might notice that it
looks an awful lot like an observable.createCall
from Rx. So let's go ahead and take
it apart piece by piece. And we'll go ahead
and start off. And we'll just create
this done signal that we can await on later
to know when to finish normally and clean up. So this is like our
observable completion. In the case where you have a
stream with a well-defined end signal, you can complete this to
let the observer function clean up and return. And we'll see that
in a little bit. So the next piece
here where we're creating a location
callback, this should really be no surprise. We need one to receive updates
from the location provider. But what's interesting
here is that we use launch to call the observer
function on the same coroutine dispatcher that observe
location was called on. Remember, the coroutine
scope that we opened here carries along the
dispatcher with it that it was called with. So we always know
that we're going to call the observer
in the same place that the caller wanted
the information reported. So we cancel the old job from
before and start a new one. We make sure that we call
the observer while we're holding the suspending mutex. And this keeps things
serialized to make sure that we don't have two calls
to an observer active at once. We assume that onLocationResult
won't have multiple calls active at once either. So this is kind of an
example of some of the things that if you're building one
of these things yourself, this is one of those
comparison things of-- RxJava kind of does a lot
of these things for you. But with coroutine, we have
all the primitives to build it. We just need to do a
little bit more of it by hand if we're putting one
of these things together. So we register our callback. And then we await
on our done signal. And since we never
complete it, I mean, when does the location
stream really complete anyway? This will go ahead and wait for
the calling job to be canceled. So we remove the callback
in the finally block. So it'll happen on cancellation. And then request
location updates normally takes a looper
instead of something like an executer, which
is kind of unfortunate because this pattern
really shines when you can avoid an extra hop. So if you use a direct
executer that'll just go ahead and
run the observer wherever the update happens
on the incoming binder thread or whatever it is. It really kind of shines when
you can avoid those extra hops. But you get the idea. So here's what it
looks like in use. And if you take a look,
this looks an awful lot like just a for-each
call on a collection. And it behaves
exactly the same way. So since we use launch
to call the observer block in the first place,
if the inner block throws an exception, that means
that the observed location call itself will
throw an exception into that outer scope. We can just wrap this
whole thing in a try-catch. And that's all of our error
handling that we need to do. So it's a child job. So it'll cancel the parent
scope with the exception as well, the same scope that
wraps the whole of the observed location function body. So it'll resume from the
await with the cancellation exception, unregister
the location callback from the finally
block from above. And all of this just composes. So you, again, can lean
on all of these constructs of the language that you
already know by just adding some of these suspending primitives. CHRIS BANES: Cool. So let's wrap up a little bit. So what's next? What are some action
items for you to do? Well, the first is that, as you
saw in the key note earlier, we actually have a code lab
specifically for coroutine. And that was released about
three weeks ago, which is really good introduction. It's coroutines and how to
actually use it in your app. And secondly, read the manual. I don't like to do that usually,
but the docs are actually really, really good. They're on Github. You can look at them. You can even edit
them if you want to. But they're all
very use-case based. They're all, I need to do x. How do I do it? So make sure to go and check it
out if you are using coroutine. And that is it. Thank you very much. [MUSIC PLAYING]