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]