[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.
I am definitely getting confused now. It sounds like we are kinda moving away from LiveData?
As someone looking to start their Android Developer career can someone try to break this down a little for me. Should I learn Live Data? If not what is the alternative?
There are so many resources for learning Android but it seems like they all contact each other or skip something.
Ok, so if I shouldn't use LiveData outside of the UI layer, what am I supposed to do?
Guess android is still a fractured mess sadly. Someone should tell google so they stop doing sessions and putting out documentation that says use this stuff, only to have the community tell people who are trying to do so that it's all broken, years on.
https://developer.android.com/jetpack/docs/guide
so just straight up this thread is saying the architecture guide is sending people off the cliff.
Is there a reference sample, like even one? of how the stuff done above should be done?