[MUSIC PLAYING]
JOSE ALCERRECA: Hi there.
My name is Jose Alcerreca.
I'm a developer relations engineer working on Android.
YIGIT BOYAR: My name is Yigit Boyar.
I also work on the Android team.
JOSE ALCERRECA: And today we're going
to talk about LiveData again.
We were here last year with a talk titled fun with LiveData.
Today we're going to talk about its integration
with coroutines and with Flow.
So since API we've struggled to understand Android life cycles.
We've all studied the diagrams and more diagrams,
and then [? Fragments ?] came along
and generated more diagrams.
So we Android developers try to isolate ourselves from having
to deal with this problem.
We created a layer architecture in which only the presentation
layer would know about the life cycle,
and then even inside the presentation layer
we found some patterns and some rules that were-- well,
like for example having an object that
would survive an activity or a fragment recreation.
We used this idea in the view model class
in architecture components.
So let's look at that animation a little bit more in detail.
So a view is created and a view model is obtained.
So it's graded as well.
It exposes one or multiple LiveDatas,
and then the view subscribes to these LiveDatas via data
binding or manual subscription.
Then there is a configuration change--
rotation, for example.
The view is destroyed, and a new instance of the view appears.
It subscribes again to the LiveDatas,
and the view model doesn't know what just happened.
The view model doesn't even have a reference to the view.
It just exposes data.
So why are we talking about this?
Whenever you do an operation-- and an operation can be
something like fetching data from the network,
preparing some text--
you have to choose the scope of this operation.
This means when this operation is going to cancel.
If you cancel too late, then you might be
wasting the use of resources.
If you cancel it too soon, you might
have to restart your operation.
But in a real world app--
and this is the Android Dev Summit
app-- we have way more scopes than that.
For example, we have a schedule screen
with potentially multiple instances of the fragmented
schedule, the view model that is scoped
to the screen, the agenda, the info screen with fragments
and view model as well.
Then we have a main activity and our view model that
is scoped to these activities.
And then you can even have custom scopes.
With the navigation component, for example,
you can create a scope for a login flow
or for a checkout flow.
And then we even have the application scope
which is a special case we'll talk about later.
So there's going to be a lot of operations happening
at the same time, and we have to manage their cancellation
somehow.
We need a way to find something that helps
us structure this concurrency.
YIGIT BOYAR: What would that be?
This, of course, our new best friend called coroutines.
Now, we don't want to go through and make an introduction
to kotlin coroutines, but want to talk
about why we like it for this particular use
case of Android development.
Briefly, there are like three main nice things.
First one is it's very easy to get off the main thread.
Like this is a problem, we tried to solve it
like 20 different technologies of yet.
With coroutines, you just launch it
and then you can change your dispatcher.
It's very easy to write.
And while writing that, you don't
need to write boilerplate code.
This is probably the best thing about kotlin coroutines.
You write it as if you're writing book in code,
and then this is all compiler magic and becomes asynchronous.
But these two are nice properties,
but they're not the reasons why we
like coroutines for this case.
The main reason is the structured concurrency
it provides.
It's a little bit complicated, but basically you
can consider structured concurrency as a garbage
collector for your jobs.
So when something doesn't need to run anymore,
it is killed for you automatically.
And this is why we like it.
So if you like coroutines on Android,
how do you launch them?
Luckily, in the Jetpack components
we provide scopes for related Android components.
So for example, if you're in a view model
and you want to run something in the scope of a view model,
there's a view model scope.
You could use it to launch your coroutines.
Or if you're in activity or fragment,
you can use the lifecycle scope which gets canceled
if the lifecycle is destroyed.
Now, in this lifecycle case there is one more thing.
Sometimes you want to run certain operations
when the lifecycle is started like a fragment transaction.
For those cases, you can use this launch
when started, resumed, or created methods.
What this does is it throws your suspend log only
when the life cycle is in the desired state or greater.
And let's say you said large [INAUDIBLE] started
and activity stop, we suspend that computation
until the activity resumes.
Now, the third layer is your application layer
where things get a little bit more trickier.
Assume you want to run something that's not tied to any screen.
In those cases, you need to figure out
whether you should be using work manager or not.
And the question you ask is, does this thing
has to complete?
Like if you're writing a Twitter client
and you need to send it to your server,
please use work manager.
Because if you lose that [INAUDIBLE],,
that would be a shame.
But if you're trying to do something
where you're going to clean the local database, it's fine.
You can use the application scope.
Like if that doesn't complete, it's not the end of the world.
You can do it next time the application starts.
Now let's look at how we can use that view model
scope with the LiveData.
Here we have a common pattern.
You have a private mutual LiveData
that you provide as a public property to your UI.
Inside here we launch our remodel scope,
we do some suspending computation,
and when the result comes we update
our private mutual LiveData.
And notice that I'm just setting the value there.
I'm not calling post value because this
runs on the main dispatcher by default.
Since lifecycles 2.2, there is a much nicer way
to do that pattern which is a name builder
called LiveData This LiveData builder gives you
a coroutine block which is like a scope for your LiveData.
It starts executing when the LiveData is observed,
and canceled when LiveData is not used anymore.
Any inside there you can emit.
Another very common pattern is imagine
you have a UI where user selects some item,
and then you display the contents of that.
A real common way to do it is that you keep that item
ID in a mutual LiveData, run a [INAUDIBLE] transformation
on it.
And again, for these cases you can use this LiveData builder
and just do a coroutine computation.
This LiveData builder also receives a coroutine context.
So you can call it to maybe run on the IO by default,
and it doesn't matter.
You can still call emit, so you don't
need to think about which thread or which dispatcher I'm on.
You can always call emit.
There's one more function called emit source
which receives the LiveData.
This basically says, well, there were this other life data
dispatchers.
Send them as my value.
So how do we cancel them?
JOSE ALCERRECA: All right.
So how do you cancel a coroutine?
Well, I'm not here to talk about how
to actually cancel a coroutine that you just started.
We've been talking about scoping our coroutines so that this
is done automatically for us.
There are some use cases for this,
but normally you won't be canceling your own jobs.
What I want to point out here is what
happens if you are designing a suspend function,
and that function, for example, has an infinite loop?
How does kotlin know when to stop that loop?
Well, turns out there's a little bit of magic here.
All suspending functions in kotlinx coroutines
are cancelable, and delay is one of them.
So when delay is called, it's going
to check if the coroutine is cancel.
And if it is, it's going to stop the execution.
So what happens if you're not calling
any cancelable functions in your suspend function?
Well, you have to cooperate.
We say that cancellation is cooperative.
You have to check if the coroutine is active regularly
with the isactive property.
So before we see some buttons, there's
an important distinction that we have to make.
And that's between one shot operations
and operations that are going to return
multiple values over time.
And we're going to see this with an example
from the Twitter app.
So in order to load the data needed for this screen,
you have to perform some one shot operations.
Downloading the profile picture, the Twitter
handle, the tweet itself--
because tweets are not editable in 2019, Mike?
But there's another thing--
that's an interesting thing at the bottom, number of retweets
and number of likes.
This UI is observing a data source.
Because if you keep this screen open,
those numbers are going to change over time.
So that's the difference.
So how does a one shot operation look like in the big picture?
We use LiveData between view and view model for the reasons
that I explained in the introduction.
And then the view model we're going
to make this bridge between the LiveData world
and the coroutines world of using the LiveData coroutine
builder.
Super easy in the repository.
Because we are in suspend functions,
we can just call suspend functions in the data source.
We get a result. We're done.
When we have to deal with multiple values,
things get complicated.
And we solve this-- we can solve this with LiveData.
We can use LiveData beyond the view model,
and our talk last year was partly about this.
But LiveData was never designed as a fully fledged reactive
streams builder, so it's a little bit awkward to use.
So luckily, .
There is a new API call Flow. flow is
part of kotlin coroutines, and we're
going to see some examples of some code examples,
and we're going to look at view model, repository,
and data source.
So first we're going to see some view model patterns.
We're going to compare two view models side by side.
One is going to subscribe to a LiveData
or consume data from a LiveData, and the other one
is going to consume from a Flow.
So the first pattern is about emitting multiple values.
If we're not doing any transformations to the values,
then we can just assign a LiveData
that we're observing to the LiveData that we're exposing.
That's very easy.
In the case of Flow, we need to convert from a Flow to LiveData
somehow.
So we could use the LiveData coroutines builder.
So we collect each item from the Flow.
That's how you consume a Flow, and then we
admit to the LiveData coroutines builder every item.
Now, this is more or less readable,
but if you have to do this 15 times in your model
it's going to be a lot of boilerplate.
So we created this handy asLiveData extension function
that is going to convert any Flow to LiveData.
And we're going to use it in the rest of the examples.
The next pattern is emitting one initial value,
and then emitting multiple ones.
So we already saw this pattern.
We used emit and emit source from the LiveData coroutines
builder.
In Flow we could use the same pattern.
We pass the Flow converted to LiveData with asLiveData.
But this is not very readable, and well, it's super awkward,
and we're creating a LiveData twice.
So it's much better if we embrace Flow's API,
and we use this thing called on start
that is going to let us define what
the initial value of that Flow is.
And then in the end we just convert it to a LiveData.
And this is where Flow really shines.
Normally you want to do a transformation on each item
that you are observing or that you are receiving.
And if we do this with LiveData, then you
might want to try map first.
But this would be main thread, and it
would be awkward to use a coroutines from here.
So it's much better that we use switch map,
and we return a LiveData created with a builder.
And because we are in a coroutines context,
we can call this heavy transformation function,
which would be a suspend function, and emit its result.
With Flow this is much, much better.
We just call map on the flow.
And because we are not coroutines context,
we can call the suspend function.
And the end, we just convert to LiveData.
YIGIT BOYAR: Right, so how do you
use the suspend functions and Flow
library in our repositories?
Now, a repository is usually where
you have a lot of custom call.
Like you're making APIs, collecting data, filtering,
whatever business as you have.
In these cases, you may want to do out of transformations.
And as Jose mentioned, we never intended
LiveData to do these things.
Like [INAUDIBLE] with Flow, you have all these operators
on your streams that you can use.
So just go ahead and use Flow or suspend functions
in repositories.
Similarly for your data sources, suspend functions on Flow
are great fit, but here things get a little bit more
trickier because this is usually the part of your code
where you're talking to other libraries, remote sources,
whatnot.
So you don't control all of it.
So let's look at all those cases,
see how we can integrate them.
The first case, the best case, is
you have one shot operation--
so you have one shot operation to get some value like, say,
from a network request.
So if you're using retrofit, just mark the function as
suspend and you're done.
Retrofit already supports suspend function
since version 2.6.
Similarly, if you're using Room for your database,
since version 2.1 it supports suspend functions.
You tell it to turn suspend function, done.
You don't need to care about anymore.
But if you're dealing with a library that's
not updated for the coroutines world,
maybe it's a general library and it has callbacks--
it's not super old callbacks--
now you need to do some work.
For those cases, we have this thing
called suspend [INAUDIBLE] coroutine builder.
This thing is basically the adapter
between the coroutines world and the callback based world.
What you do is when you call this function,
it gives you a continuation that you're responsible to call.
So we make our API request.
If the API gives us a value, we resume the continuation.
Or if the API tells us an error happened,
we resume the continuation with an exception.
This all you have to do.
Once you do this, resolve the coroutine mission
it works, and you can abstract this somewhere
outside of your code.
Notice that if you happen to call this continuation much
later, like after it's cancelled, it's just ignored.
It can also change just a little bit
better so that if the coroutine's canceled,
you can eagerly cancel your API request.
Now, the third case is where have a lot of values,
like you want to emit multiple values.
And Flow builder is a great thing.
It's very similar to the LiveData Builder
except you build a Flow and you can emit values.
So we're an example here that like infinitely every two
seconds dispenses a new weather value.
It's also super handy when you're writing tests, actually.
Just like, say, oh, this is this other Flow.
But if you are working with an old library
again that doesn't support Flow and is a callback,
you can use this call back Flow builder.
This is very similar to the previous one we saw.
So they say we have an imaginary API here that receives
an image and a callback, and that callback
has three functions.
It's like next value, on error, and on completed.
We register that callback with our imaginary API,
and every time we receive one of those callbacks we just say,
OK, offer that value.
If an email came, offer it to the Flow, or it never happened.
Basically close the Flow with this error.
Or if it closes nicely, same way close the Flow.
Now, we need to be careful here because this callback Flow
builder, once it returns, flow is closed so you cannot
dispatch values to it any more.
We don't want it to return.
So for those cases, we have this function
called a wait clause, which basically
suspends until the Flow is not collected data more.
There might be any reason you close it here,
or maybe the collector just stop collecting.
And in that case, we simply unregister from our API.
Now, testing.
So we would like to talk about testing,
but we don't have that much time.
[LAUGHTER] No, no, no, no, no.
No, we have time.
Testing is important.
So we have a full fledged talk on testing
by Sean and [? Manny ?] tomorrow at 5:15.
Please go watch the thing to see how you can effectively
use coroutines in your tests.
Before we leave, there is one more thing.
With the introduction of Flow and the live cycle skills
we have shown, you can actually get the benefits of LiveData
without using LiveData.
So you could be doing this where you say life cycle scope launch
when started, and start collecting on this flow.
This will behave exactly the same way
LiveData behaves for your UI.
Now, it's not 100% the same thing because LiveData
has this caching behavior, but it's close to there.
So is this something you may want to consider.
For any questions, we'll be upstairs
for the Q&A. Thank you.