Fun with LiveData (Android Dev Summit ’18)


[MUSIC PLAYING] JOSE ALCERRECA: Thanks
for coming to this talk. My name is Jose Alcerreca. I’m a developer relations
engineer at Google, working on Android. YIGIT BOYAR: My
name is Yigit Boyar. I’m an engineer in the
Android toolkit team. JOSE ALCERRECA: And today we’re
going to talk about LiveData. LiveData is one of the first
architectural components that we released last year. And in this talk, we’re
going to explain what it is. We’re going to talk about
some of the transformations that you can do, how
to combine LiveDatas. And then we’re going to
talk about some patterns and anti-patterns that
you might want to avoid. So LiveData is a simple
lifecycle-aware observable data holder. We’re going to explain
all these characteristics. But first, we’re going
to start with observable. What’s an observable? So in our object oriented
world, probably the easiest way of communicating one
component and another is by having a reference
from one object to another, and just call it directly. However, in Android, this
might have some problems. As we all know,
components in Android have different lifecycles
and different lifespans. You might be familiar
with this diagram. It’s the ViewModel
scope diagram. A simple thing like
device rotation can actually recreate
the activity. So you probably know
that having a reference to the activity in this
ViewModel would be a bad idea. Because it leads
to memory leaks, even crashes with null
pointer exceptions. So instead of having a
reference to the activity in the ViewModel,
we’re going to try to have a reference to the
ViewModel in the activity. So how do we communicate, how do
we send data from the ViewModel to the activity? Well, instead of
doing that, we’re going to let the activity
observe the ViewModel. And for that we’re going
to use Observable LiveData. Let’s see how this looks
with a little bit of code. In the ViewModel, we
expose our LiveData. You’re going to see a lot
of examples of how to expose LiveData from a ViewModel. And then in our activity, we
make the actual subscription. And we do that by
calling the observed method on the Observable. The first parameter is something
called a lifecycle owner. Yigit is going to talk
about this in a second. And the second parameter
is an observer. This is what’s called whenever
the Observable, the LiveData’s value is changed. YIGIT BOYAR: So Jose
mentioned that you want to reference an
object in the larger scope, like a ViewModel, from an
object in a smaller scope, like an activity. But of course, when
you observe something it has to keep a reference back
to you to be able to call it. So there is a reference. But why is this not a
problem with LiveData? Well, LiveData is an
lifecycle-aware component. What do you mean by this? To able to observe
a LiveData, you have to provide the lifecycle. And when you provide
this lifecycle it basically maintains your
subscription for you for free. So if you’re
observer’s lifecycle is not in a good state, like
it’s [INAUDIBLE] activity, it’s not going to call you back. Or when your activity or
fragment is destroyed, it’s going to remove the
subscription automatically for you. So you don’t have the burden of
maintaining the subscription. So if you go back to
the previous graph, you’re LiveData
observer will only be called if it
is between started and before it is stopped. This makes sure you don’t
need to care about things like fragment transactions. Once you receive
an observer value, you know you’re in a good state. Probably the most distinctive
property of LiveData is the data holder. So it’s not a stream. We keep saying this. But what do I mean by that
like, it’s not a stream, but it’s a value holder? If you go back to our
previous graphs, on the right we have a LiveData
in our ViewModel. And on the left, we have
our activity, or fragment, that’s observing this LiveData. Once you set a value
on the LiveData, just value is passed
to the activity. Similarly, if the value
changes, the activity receives the new updated value. Now, the difference happens
when you change the value, when the observer is not in an
active state, it has no idea. That value, C, is never
dispatched to the activity. And let’s say while
your activity is still in a background thread– oh sorry, in a
background, not a thread– you set the new value. And you’re activity
still doesn’t see this. Now, the data holder
property comes in now. When your activity comes
back, that user, C, is in the foreground. It receives the latest
value from the ViewModel. As you can see, the value
C has no [? error ?] to the activity
because LiveData only cares about holding
a single value, and it’s the latest value. This works perfect for
UI, because you only want to show what it is right now. But if you are trying to
process a stream of events, this is not what
you’re looking for. Similarly, if you change
the value after activity is destroyed, nothing happens. JOSE ALCERRECA: OK, let’s talk
about how to combine LiveDatas and talk about transformations. The library provides
two, map and switch map. But you can create your own
using [? mediator ?] LiveData. We already said that LiveData
is great to communicate a view and a ViewModel. But what if we have a third
component, maybe a repository, but it’s also exposing LiveData? How do we make this
subscription from the ViewModel? We don’t have a lifecycle there. What if the app is
even more complicated, and the repository is also
observing data sources, in this case? Well, Yigit once said
to me that if you need a lifecycle
in your ViewModel, you probably need
a transformation. But this is actually wrong. Sorry, Yigit. So you know, what I say
is that you definitely need a transformation. Don’t ever use a lifecycle
in your ViewModel. YIGIT BOYAR: So
different, [? bravo. ?] JOSE ALCERRECA: OK,
how do we make– the first sample is a
bridge between the view and the repository. How do we get to that LiveData? We use a transformations
map, which is what I call a one-to-one
static transformation. In the ViewModel, we
expose a LiveData. In this case, it’s
called ViewModel result. And it’s the result of
a transformation’s map. The first parameter is the
source, the LiveData source, which is the LiveData
exposed by the repository. And the second parameter is
the transformation function. In this case, it’s simply
converting from the data layer model to the UI model. And this is how the signature
would look like in Kotlin. It has source, which
is a LiveData of X. And it returns a LiveData Y.
So it’s a breach of LiveDatas. And then in the middle, we
have a transformation function that transforms from X to
Y. It doesn’t know anything about LiveData. YIGIT BOYAR: So when you
establish that transformation, the key here is that the
lifecycle is automatically carried over for you. So say you run a transformation
of couple of LiveDatas. And at the end, it is a
LiveData that you hold onto. When someone subscribes
to it, that lifecycle is automatically propagated
to the inner LiveData elements without you doing anything. And it’s completely
managed by us. So it’s completely safe. Another transformation we
provide is switch maps. When do we need this? Imagine you have an
application where you have a user measure that
keeps the logged in user ID somewhere, like in a disk. And whenever that logged in
user ID, when you grab it, you need to talk to
your user repository to get the actual user object. And that probably goes to the
database and also the server to return you this user object. But that repository returns
your LiveData as well. Because user object
might change, right? It may return you the
cache one while it updates from the server. So you’re in a
situation where you have a LiveData of
a logged in user ID and a LiveData of a user. And you need to
chain these things. So map works if you are
chaining from an ID to a user. But how do we change from an
ID to a LiveData of a user? That’s switch map. So if you look at that example,
basically, we call switch map. We provided this
user ID LiveData. That’s a LiveData. And then our function this
time returns the LiveData. So the signature
looks like this. You have a source. And then you have a LiveData. And you provide a function
that converts the value X to a LiveData. What this technically
does is, every time that user ID changes,
it calls your function. You give it a new LiveData. It unsubscribes from
the previous LiveData you returned, and then
subscribes to the new one. It’s like switching tracks. Or it comes from
like switchboards. But it is completely
managed for you. And it’s still lifecycle-aware. You get all the benefits
of using LiveData. Now, we only provide
map and switch map. We don’t have a million
transformations, like some other libraries. But this is very limiting. And sometimes you may
want to write your own. And we actually don’t
want to provide many. But if you want to write
your own, it’s very easy. If I show you our like, little
the code we have for the map implementation that
was talked about, it doesn’t see the source
and it returns the LiveData. And you give it a
function, right? All it does is create this
mediator LiveData class, and adds the given source as
a source for this mediator LiveData. What [INAUDIBLE]? It kind of tells us that the
value of this mediator LiveData is derived from
this other LiveData. So whenever that other LiveData
changes, call might callback. And in the callback,
we basically apply the function to the
value and set as a value on the mediator LiveData. This is like super
simple to write. And there is no lifecycle here. But all of this code
is lifecycle-aware. So if it’s so easy,
let’s create a new one. Let’s say we want
to create something where users filling a form. You have a bunch of strings. And you want to have the total
column showing somewhere. So you have a LiveData
and another LiveData. And you basically have
a LiveData of integer that has the total number of
characters in those LiveData elements. And this integer updates if
any of those values update. So we called our
function totalLength. We receive a list of LiveDatas. And we return a LiveData. What we do here is we
have a sound function. It’s actually very simple. It goes through all
of the LiveDatas and sums their are length. We need to account
for nulls here. Because LiveData allows nulls. So you need to be aware of it. But this is very simple. It’s basically look at
all the LiveData values, and sum the total length. Once we did that, we
add each given LiveData as a source to our mediator. It basically says the
value of this mediator depends on these
other LiveDatas. So anytime any of them
changes, the framework calls back our doSum
function, which calculates the new value
for the mediator LiveData. And this is it. It’s like four lines of code. And you have an operation,
a transformation on your LiveDatas. Now, there are some
common mistakes you can make while
using LiveData. And we want to touch
base on these things. One thing we see a lot is let’s
say you make a web request. It returns you a giant JSON. And then you convert
it to your objects. Using your LiveData
transformation for doing that is not a good idea. Because LiveData
is a value holder. So the long string you
fetch from your server is going to stay in memory. It’s going to hold onto that. So you probably don’t want
to use LiveData for something like that. Just do it as a
one shot operation. JOSE ALCERRECA:
OK, the second item is about sharing
instances of LiveData. At one point, I was trying to
make an app with LiveDatas. And I had a repository
that was a Singleton. And there was only one
observer in the activity. So I said, OK, I can
just save some LiveDatas and share a single LiveData. I had something like this. A repository, it
takes a data source. And then the mutable
LiveData that we are returning in loadItem
is shared by everyone that calls loadItem. Now, this is fine. It works. But there is a very
interesting edge case. And this anti-pattern is about
you thinking which observers are going to be active. And the edge case is
activity transitions. There is this case in Android,
where two activities are going to be active at the same time. So imagine activity one observes
item number one and activity two observes item number two. When we load activity
two, it’s going to load data for item two. But because they are
sharing the same LiveData, activity one is also going
to receive that data. And because it’s in the
middle of an animation, you’re going to see a flash. You’re going to see a glitch. And obviously, that’s a
very bad user experience. YIGIT BOYAR: Yeah,
basically like, if your class is a repository. And you created a field that’s
an instance of LiveData, you’re probably doing it wrong. JOSE ALCERRECA:
Yeah, the solution, obviously, is to create
a LiveData every time. It’s very lightweight. You’re not going to save
much by avoiding this. The third item is
about where and when to create your transformations. And this is all about wiring. It’s similar to when
you create a circuit. You lay down your components. And you wire everything up. And for a known set
of inputs, you’re going to have a
known set of outputs. But you don’t unplug a wire
while it’s in operation, and plug it somewhere else. Right? This is exactly what
this ViewModel is doing. Lots of horrible things
happening in this ViewModel, by the way. For starters– YIGIT BOYAR: We should have like
a don’t do this in this slide. JOSE ALCERRECA: It says
don’t do this literally. YIGIT BOYAR: That doesn’t– OK. Because actually someone
will copy/paste it and then blame us for recommending it. JOSE ALCERRECA: That’s the
standard way of doing it. So first, it’s exposing
itemData, which is a variable. It’s not a val. It’s a var. And also, it’s exposing
a mutable LiveData. You should almost never do this. To weigh data binding is
an exception to this maybe. You should always
expose something that is immutable, so that
your observers can change it. So after subscription,
we call loadData from our activity to
set the ID of the thing that we want to load. And then we are reassigning
itemData to something new. But the subscription
already happened. So the observer is not
going to know that you made this reassignment. The solution– YIGIT BOYAR: Actually,
even if you’re returning the LiveData there and
your observer’s resubscribing to the new LiveData, now it is
subscribed to the previous one and the new one. Because you never
removed the subscription. JOSE ALCERRECA: So
the solution to this requires a little
bit of planning. We have two LiveDatas. One is mutable and it’s
private to this ViewModel. And the other one
is the one that is exposed to the view
from the ViewModel. And it is a
transformation switch map. The source of this switch map
is that immutable LiveData. So that every time
itemID changes, then the transformation function
is going to be called. And data source, which
returns the LiveData, is going to be called
with the appropriate ID. After the subscription to
this item data has happened, we call loadData. We pass the string. This ID might come from
an intent in the activity, or whatever. And then when we set the value,
then it triggers an update. And everything is going to
work as you expect it to work. YIGIT BOYAR: OK,
so [INAUDIBLE] like to think that
LiveData is awesome and it solves all the problems. It doesn’t. It’s designed for a
very specific use case. And we see people trying
to use it in other areas. And they struggle with it. So I want to make it clear. If you’re writing an application
that has lots of like operators and streams, you totally
bought into this reactive idea, just use our Rx Java. Like don’t try to add like a
million transformations on top of LiveData to make it. It’s not designed for that. Just [INAUDIBLE]. If you have things that are not
related to a lifecycle or a UI. Let’s say you are trying to
synchronize the user’s location to your backend service. There is no UI there. There is no lifecycle there. Like, there is no reason to
use LiveData for something like that. Either use a callback,
or if you’re using Rx Java, that will still work. And other use case is having
these like one-shot operations, like mentioned. You fetch some data. And then you convert it. You write into your database,
load back, and return it. For those things,
if you are using [INAUDIBLE],,
coroutines are actually like a new, exciting area. Again, you might use
[INAUDIBLE] concurrent. Or you can use Rx Java. But don’t use LiveData, because
we didn’t design it for that. LiveData works very well as
the last layer for your UI, it’s perfectly OK. It is kind of the best solution. But if you try to scale, it
is just not going to work. So many things we mentioned
in this talk– actually, Jose has blog posts on the
Android Developers Medium publishing. You can go read them. Or check out our
samples on GitHub. We have simple
usages of LiveData, as well as like, a complete
application, the GitHub browser sample with like,
using [? Groom. ?] It has like multiple data
sources, transformations. And also you can look at the
source code for IO scheduler app, which is the same
app in Dev Summit. And if it has bugs,
you can blame this guy. He wrote it. Thank you very much for coming. I hope this was useful. And we will be in the– JOSE ALCERRECA: Around. YIGIT BOYAR: –area like,
around after the talks. Thank you. JOSE ALCERRECA: Thank you. [MUSIC PLAYING]

About the author

Comments

  1. Very good points in this talk!
    It's interesting to see when one can use Coroutines Channel API and when LiveData.

  2. Subtitle says "If you try to scale, it is just not going to work" at 18:18. Is that means livedata can not handle UI very well when the scale is big?

  3. So simply I got confused. If only a written text or a step procedure for tose of us whot are required to set up for the program on a seperate page, which hearing or language program to allow viewing every page.

  4. google is attempting me to upgrade for my photos, for a profit most are those I had received from you tube, i am only taking a few photoes and had not descivbed theem.

  5. They are using Kotlin everywhere. I get that because they provided the first class support. But that doesn't mean to ignore the Java right? I am a Java developer and when they use Kotlin tool for explaining stuff, I get bored. I just don't understand the flow of Kotlin, it's just not easy to read. At least they should use both Java and Kotlin in these kinds of presentation. So many peoples are still depending on Java and Google should not act like they don't care about Java developers.

  6. Telling me loaders are deprecated, and then only using Kotlin to try and explain LiveData, means I can't comprehend LiveData. I will never understand Kotlin because it removes all the syntax and clues my ASD brain needs to work out what code is doing, meaning unlike C, Java, JS, C#, Python, PHP, etc. I cannot make any sense of what the meaningless gibberish on the slides is trying to describe.

    Use pseudocode in your next talk if you think the subject is important enough to be understood by all, because Google has done a crap job at explaining why l should no longer use an AsyncTaskLoader to load JSON over HTTPS that must not be stored or cached.

  7. Hi every one i don't understand transformation map switch it s behind the scene what happened in android studio! thanks

  8. Helloo i have one question.,
    What is the need of ViewModel if we can handle configuration changes by writing a single line of code in manifest file i.e configurationChanges”orientation|keyboardHidden”..

Leave a Reply

Your email address will not be published. Required fields are marked *