[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]