[MUSIC PLAYING]
[APPLAUSE]
FLORINA MUNTENESCU:
Hello, everyone.
My name is Florina Muntenescu.
I'm a developer
advocate at Google.
At Google I/O
earlier this year, we
launched Architecture
Components,
a collection of
libraries that help
you design robust, testable,
and maintainable apps.
Since then, we've been
discussed a lot with--
well, with a lot of you.
And then we saw a
lot of questions
that kept on
repeating, stuff like,
OK, I'm already using
RxJava in my application.
Should I start
using LiveData also?
Or, my architecture is
implemented using MVP.
Should I now move to MVVM?
Or, I need to display a
large list in my application.
What should I do?
In today's talk, I want to
tell you our suggestions
of how to handle all of these.
But before we go
into this, I want
to go a little bit over the
Architecture Components,
just to make sure that
we're all on the same page.
And also, I want to
mention a few of these
do's and don'ts of the
Architecture Components.
So let's say that this is
an activity that displays
information about the user.
One of the biggest
problems for us
Android developers is
configuration change.
So for example, when
you rotate your device,
the activity gets destroyed
and then recreated.
So to help with this, we
introduced a lifecycle
and a lifecycle owner.
So an activity or a
fragment has a lifecycle.
Therefore, they are
a lifecycle owner.
And the lifecycle
of a lifecycle owner
can be observed by a
lifecycle observer.
And in that observer,
we can define
methods that will be called
whenever specific lifecycle
events will be triggered.
So all you need to do is
annotate those methods
with OnLifecycleEvent
plus the event name
that you're interested in.
Architecture
Components provides one
such lifecycle-aware
component, which is LiveData.
LiveData is a data holder,
so it can hold information
about the user, for example.
And other components can set
the value that is being called.
And activities and fragments,
so objects that have a lifecycle
can observe that LiveData.
And then they can react on
the changes of that LiveData,
and then they can update
the UI, for example.
But when the activity
is OnPause or OnDestroy,
the subscriber is
removed, so the LiveData
is considered inactive, and
the events are not propagated.
But once the activity's
recreated, we subscribe again,
and we can update the UI.
The class designed
to store and manage
the UI data that survives
configuration changes
is the ViewModel.
So let's see how the
lifecycle of the ViewModel
actually looks like, compared
to the activity lifecycle.
We can see that we can
create a ViewModel only
once the activity is created.
And then, the
ViewModel will only
be destroyed when the
activity is finished.
So more precisely, the ViewModel
survives configuration changes,
but it will not survive
pressing back on your phone,
killing the application
from recents,
or when the fragment
kills your application.
So this means that
the ViewModel is
perfect for handling
long-running operations,
because the ViewModel
will be updated
independent of whether the
data is observed or not.
So this means that you will no
longer get these null pointer
exceptions when trying to
update a nonexistent View.
So make sure you avoid
referencing Views
in ViewModels,
because they can lead
to memory leaks or crashes.
So the mindset here
changes a little bit,
because instead of pushing
the data to the UI,
you let the UI
observe the changes.
So just make sure you don't
hold any UI logic in the View,
but rather move this in the
ViewModel so it can be easily
unit tested.
So for example, it would be
the ViewModel's responsibility
to get the user, prepare
it to be displayed,
and hold it for the UI.
And then, the UI will
notify the ViewModel
of the user's actions.
The ViewModel would
work with a repository--
this will be a class
that you define--
to get and set the data.
So the repository
models or modules
are responsible for
handling data operations.
They provide clean API to
the rest of the application.
They know where to
get the data, what
APIs call to make when
the data is updated.
So you can see them as mediators
between different data sources.
So it's a good idea
to have a data layer
in your app completely unaware
of the presentation layer,
because algorithms that
synchronize or keep
caches or make database
synchronizations
are quite complicated.
So you want to make sure that
you add a single point of entry
to your data.
So this means that
the repository
will know which API to call to
get the user from the network.
And because we want to make
sure that we don't request
the data too many
times, we're also
going to save it
locally in the database.
But to save the
data in database,
Architecture Components comes
with a new library, Room.
Room is a wrapper over
a SQLite database.
It is an object mapping library
that provides data persistence
with minimal boilerplate code.
So for example, let's say that
this is our Users table, that
has a user ID, which is a
primary key, and the name,
and all sorts of other
information about the user.
And what we want is, actually,
every instance of the role
should be an instance
of a user object.
So to do this, we
define a user data model
that we just
annotate with Entity.
Here, we define the primary
key and the column infos.
To actually access the
data in the database,
we work with DAOs, so
Data Access Objects.
We're annotating
our class with DAO,
and then we define
access points.
So we can define queries,
inserts, update, and deletes.
Queries can also return
LiveData objects.
So they're making this
query an observable query.
So what's an observable query?
Let's say that we
have this Users table,
and we want to get
the users by ID.
So let's say that we're
interested in the user with ID
4.
So LiveData will
get the initial data
from the database, which is
the user with the name John.
But then when we obtain the data
in the table with, let's say,
Mark instead of
John, the LiveData
will automatically omit
that new information.
So using observable
queries means
that we have a UI that observes,
that reflects the latest
changes in the databases,
because the repository would
expose a LiveData
object, and then
the ViewModel will also expose
a LiveData object for the UI.
So what I've been mentioning
before, or until now
with the ViewModels and
with the repositories,
is what we call a guide
to App Architecture.
It provides testability and
separations of concerns.
But I saw that you've got
a few questions about this.
So for example, how
many LiveData objects
should I expose
from my ViewModel?
Should I have-- so let's say
that the LiveData contains
a model for the screen.
Should I expose only one?
Should I expose multiple?
So let's say that the top
part contains some user
information, and then the bottom
part, some general settings
data.
So you can see that,
actually, these are
two different logical units.
So what you could do is group
together these logical units.
So you could expose a
LiveData for the user info
and then another LiveData
for the settings.
What if I'm using MVP?
Should I switch to MVVM?
So should you replace the
presenter with a ViewModel?
Actually, it depends.
It depends on the amount
of logic that you have,
on how testable
your classes are.
The main idea is
that you should keep
the logic in the activities
and in fragments to a minimum.
So what you could do is put a
ViewModel between the presenter
and the repository and
still let the presenter work
with the View.
But what's extremely
important is not
to let ViewModels and presenters
know about the Android
framework classes.
And make sure you
distribute responsibilities.
Don't be afraid to
create new classes.
Should I use LiveData if
I'm already using RxJava?
If you're already
using RxJava, chances
are your project looks
something like this.
You're using it throughout
the entire application.
You're using it on the network
part, on the database part.
You have a repository
that exposes
flowables or observables, and
ViewModels that also do that.
So one way you can do this
is split the responsibility.
LiveData was made for the UI, so
you could leverage that and let
the LiveData handle
the connection
between the ViewModel and
your activities or fragments.
You could use
composite disposable,
where you keep your
subscriptions to the RxJava,
and then in the
ViewModel onClear,
you just clear
those disposables.
How about the data?
How do we save it?
Where do we save it?
Should you save it in the
database, in the ViewModel?
What should you save in
onSavedInstanceState?
So before going over
the few scenarios,
let's look at this again.
So only when the activity
is finished-- only then,
the ViewModel is clear.
So let's remember this.
So let's see what happens
when the application starts.
When the activity starts,
you call onCreate, and then
onCreate, we would
actually get that reference
to the ViewModel.
The ViewModel will talk to the
repository to get the user.
So the repository would do
a request to the network,
calling up getUser
from your back end.
The repository will save
that user information
in the database, and then it
will expose that information
to the ViewModel.
The ViewModel will create
the model for the UI,
because maybe you don't want to
expose all of that information
that you have about your user.
Maybe only the first
name and the last name.
And then, the activity
will use that information
and will display it.
Let's see what happens when
you do configuration changes.
So for example, when
you're rotating the device.
So on configuration change,
the onStop is being called.
But the ViewModel still
exists, still holds a reference
to the UI model.
When the application goes to
foreground, or is recreated,
the onStart is called.
And then in our
display method, we just
get a reference from that
UI model from the ViewModel.
That's it.
We don't need to work
with a repository at all.
Let's say that our application
goes to background,
and then the user
navigates back to the app.
So in this case, when the
activity goes to background,
onSave instance state is called.
And then, when the activity
comes to foreground,
we just display the
user information
based on whatever we have
in the ViewModel-- again,
without requesting
anything from the network.
Scenario three.
Application goes to background,
and then the process is killed.
So in this case, when the
activity goes to background,
we should, in
onSavedInstanceState,
we should save the user ID.
This is why.
Because when the
process is killed,
the ViewModel is also killed.
So when the activity
starts, in the onCreate,
we would have in the
bundle that user ID.
So what we can do is just inform
the ViewModel about the user ID
that we're interested in,
and then the ViewModel
will just talk to the
repository to get the user.
But the repository already
has that information
in the database, so we no longer
need to do another network
request just to get the user.
So the
onSavedInstanceState allows
us to have the minimum
amount of information
that we need to restore our UI
without doing any extra network
requests.
So what should you
put in each of them?
In the database, put the data
that survives process death.
In the ViewModel, put the
data that's needed by the UI.
And then, in
onSavedInstanceState,
put the minimal information that
is needed to restore the data.
Instead of just the
user, let's consider
that we have a list of users.
Many applications need to
load a lot of information
from the database.
Database queries
can take a long time
to run and use a lot of memory.
And we have a new paging library
that we will release soon
that can help you
with all of this.
So the main components
of the paging library
are a PagedList
Adapter, that actually
expands the RecyclerViewAdapter,
a PagedList, and a DataSource.
The DataSource is an
interface for page sources
to provide the data gradually.
But you'll need to implement
one of the two DataSources,
either a Keyed DataSource,
which will be used when you need
to load item N based
on the item N minus 1,
or a Tiled DataSource,
that allows
you to jump in any place
of your data set instantly.
And you also need to implement
another method, loadCount.
This one would be
the one that tells
whether you have an infinite
or a finite amount of items
that you need to
display in your list.
The PagedList is
a component that
loads the data automatically and
can provide update signals to--
for example, to the
RecyclerViewAdapter.
Of The data is
loaded automatically
from a DataSource on
the background thread.
But then, it's consumed
on the main thread.
And it supports both an
infinite scrolling list,
but also countable lists.
And you can configure
several things.
You can configure the initial
load size, the page size,
but also the prefetch distance.
So here's the data flow.
Let's say that we have some data
that we put on the DataSource
on the background thread.
The DataSource
invalidates the PagedList
and updates its value.
And then, on the main
thread, the PagedList
notifies its observers
of the new value.
So now the PagedListAdapter
knows about the new value.
So on the background
thread, the PagedListAdapter
needs to compute what's
changed, what's the difference.
And then, back on the
UI thread, the View
is updated in the
onBindViewHolder.
So all of this
happens automatically.
You just insert an
item in that database,
and then you see it animated
in, and no UI code is required.
OK.
Let's look at the code a bit.
So to tell the
PagedListAdapter how
to compute the differences
between the two elements,
you'll need to implement
a new class, DiffCallback.
Here, you will
define two things.
You will define how to compute
whether the contents are
the same, and how to define
whether the items are the same.
To simplify the connection
between the DataSource
and the RecyclerView, we can
use a LivePaged ListProvider.
So this will expose,
actually, a LiveData
of a PagedList of our user.
So all you will need to do
is provide a DataSource.
But if that DataSource
is true, then it
will be generated
for you in the DAO.
You don't need to write any
invalidating handling code.
You can simply bind the
LiveData of a PagedList
to a PagedListAdapter and
get updates, invalidates,
and lifecycle cleanup with a
single line of binding code.
So in our user DAO, we would
return a LivePaged ListProvider
of our users to get the
users by the last name.
And then, in the
ViewModel, we would
extend from the Architecture
Component ViewModel,
and then we would
keep a reference
to that LiveData
of our PagedList.
And we will get that
reference from the DAO
by calling getUsers
by the last name,
and then calling Create
using the configuration
that you want.
So for example, setting
the page size to 50,
setting the prefetch
distance to 50, and so on.
In our activity,
the activity needs
to be, of course,
a lifecycle owner.
And then, in the onCreate,
we get the reference
to our ViewModel.
We get the reference
to the RecyclerView,
and we create our adapter.
And then we use
another handy class
from the Architecture
Components-
LiveListAdapterUtil.
So this provides hooks for
LiveData and lifecycle,
so you can bind with
just one line of code
and not worry about the cleanup.
And then, we are just
setting the adapter
to the RecyclerView.
Let's look at the adapter.
So our adapter would
extend PagedListAdapter,
and then it will
connect the user, which
is the information
that's being displayed,
with the user ViewHolder.
And then we define the
callback, the DIFF_CALLBACK,
for our user objects.
And then in onBindViewHolder,
all we need to do
is bind the item
to the ViewHolder.
That's all.
We have a lot of new
concepts and components
with Architecture Components.
But the thing is, you
can use them separately.
So if you want, you'll
only be able to use
lifecycle, LiveData, and
PagedList or only ViewModel,
or only Room.
But you can also
use them together.
So start using the
Architecture Components
to create a more
testable architecture
for your application.
Thank you.
[APPLAUSE]
[MUSIC PLAYING]
