[MUSIC PLAYING]
FLORINA MUNTENESCU: Hello, everyone.
I'm Florina Muntenescu, and I'm a developer advocate.
DANIEL SANTIAGO RIVERA: I'm Daniel Santiago Rivera,
and I work on the Android Toolkit.
FLORINA MUNTENESCU: Room was created
to simplify working with databases in Android
by adding compile-time checked queries and an easier way
to implement migrations and an easier way to test.
But since 1.0, Room added more and more features.
In Room 1.1, we extended the RxJava support
and we've added the raw queries.
Room 2.0 just meant a migration to AndroidX.
But then with Room 2.1, we've added several advanced
features, like full-text search and views.
But then you told us that there is room for improvement.
So then, in Room 2.2, we've been listening
to some of the features that you've asked
and we've implemented that.
So today, we're going to talk about what
we've done in Room 2.0.
DANIEL SANTIAGO RIVERA: Thanks.
So since the beginning, Room has always
given you the capabilities to define a dao and a query
and then get some data back.
And you've been able to do this synchronously
in a blocking way.
But Room also shaped with like data,
and there was a way to get observable data set results.
So if something changed underneath it,
you would get a new set of results.
But it didn't have anything for, like, one-shot operations.
That was mostly up to you.
As development progressed, we added support for Rx,
and this was a complete story.
You have types for both observable queries
and one-shot operations.
Room also supports Guava and this was a little backwards
from Lifecycle.
No observability but there was one-shot operations.
In Room 2.1, we started going the Coroutines route
and we added a suspend function.
And these fall into that category
of one-shot operations.
But in Room 2.2, we added observable flow,
and this completes that Coroutine story
so you can have a full Coroutine application.
But what is Flow?
Let's look at a rundown on what Flow is.
Flow is basically like a relatively new Kotlin Coroutine
primitive.
The cool thing about it is that it's [INAUDIBLE]..
So you emit some data and there has
to be someone on the receiving side
for more data to be admitted.
The API itself is split into two kinds of operations--
there's those that form the data from varying
intermediate operators, like map and filter,
and there's those that consume it.
But let's see some code and some examples.
So imagine we're making the best puppy-finding application
and we need to provide an API for finding some dogs
and pet them.
This has to be asynchronous because sometimes
finding dogs is tricky and might take some time.
We also have a parameter here that limits
the amount of dogs we get.
If we were to build this, we would
start with a Flow Builder.
And the Flow Builder takes a lambda.
In the body of this lambda, you write the logic
to be able to provide that.
In this case, to get our dogs and emit them.
The receiver type is a Flow collector.
It only has one function, emit.
So the interesting part here is that emit
is a suspend function.
And it is because that means somebody on the receiving end
must be collecting the data before this function completes
and your body continues.
If we were to call the function, we
use one of those terminal operators like collect.
If we execute this, we'll be getting a bunch of puppies
and petting them, which is amazing.
On the other end, if we use a limit of three,
we would only get three dogs.
And three dogs is still pretty cool, in my opinion.
In terms of room, in your dao, you can now basically
have a query function that returns a Flow,
and it'll behave like expected in the sense
that you will get a list of your result.
But then it's observable.
And if we initialize it, imagine we have a Room database.
On the other end, we're trying to display our puppies.
When we get it, we get a list of puppies.
But then if something changes on the database,
like we download dogs from the cloud,
you're going to need emission.
And this emission contains both the old result set
and the new one.
The cool thing about Flow--
and supporting Flow in Coroutines--
is that you can start combining it
with the rest of the effort we've been doing
with regards to Coroutine.
Especially, you can combine it with the Lifecycle extensions
that will basically allow you to launch a Coroutine.
When the fragment is started, you
can start collecting those dogs and start showing
them, which is pretty neat.
FLORINA MUNTENESCU: So you've told us
that you want to be able to repopulate your application.
So whenever your users install your app,
you want to be able to already display the best dogs.
So in order to do that, you would
use a prepackaged database.
This database would either be part of your APK
or would come from a backend.
So if you would have to implement this by yourself,
you would have to do a lot of things,
from opening the database, validating the schema,
locking the database file, and a lot of things,
taking away from what you actually
need to do-- the features that you need to build.
So in Room 2.2, we've added two new methods
on the database filter, createFromFile
and createFromAsset.
So createFromFile, we will get as a parameter a file.
This will be the downloaded file that you have.
So one important thing here, you should make sure
that you set View Permissions on your file
so Room is able to read that file and copy that data.
And then, if you are pre-packaging your database
in your APK, make sure you put it in the Assets folder.
In terms of working with migrations,
things are not very different.
So in general, Room looks at two things.
It looks at the database version installed on your device
and at the version that you declare in code.
So in your @Database annotation.
But now, with pre-packaged databases,
you have one more player.
You have the pre-packaged database.
So when you're talking about migrations,
for example, if the pre-packaged database is
version 1 and the one that you put in code is version 3,
Room will also have to handle the migration from version 1
to version 3.
So just keep this in mind whenever
you're working with pre-packaged databases.
Next feature is relations.
So this is not something new for Room, but what we did in 2.2
is to increase the support for relations.
So today, let's do a crash course on what this means.
So let's say that an owner can only have one dog.
This is a sad world, but let's say that this
is where we're living.
So this means that we have a 1 to 1
relation between the owner and the pet.
And this can be translated into database like this.
We would have two tables, a Pet table and an Owner table.
And the Pet table would also have
a reference to the ownerId.
For Room, this means that you would
need to create two entities, one for pets and one for owner.
And then, if you actually really want
to enforce this constraint between the petOwnerId
and the ownerId, you could use a foreign key.
OK.
But how do we get a list of all the owners with their pets?
Well, if you want to do this just using SQLite,
you would need to select first from Owner,
and then you would need to select
from Pet based on the ownerId.
So this would be put inside a data class--
a PetAndOwner class that has the owner and the pet.
With Room, you would annotate the owner with embedded
and you would add the @Relation annotation for pet.
So while this @Relation annotation isn't new,
what is new in Room 2.2 is the fact
that you are able to add this annotation to an object that
is not a collection.
So for example, here, we're just adding it to the Pet
and we're telling Room that there
is a connection between the petOwnerId and the ownerId.
If you want to, when writing your query in your dao,
you would just select from Owner and you would
return the list of PetAndOwner.
OK.
So we said that an owner has a dog.
What if an owner can have multiple dogs?
So this will be a 1 to many relation,
and this is mapped in the same way in your database.
So you still have a pet and an owner.
And then, when we want to get the list of owners
with all of their pets, the queries that you need to write
are actually the same.
You still need to select from Owner
and then you would need to select from Pet.
But we don't want to do this by hand.
We want to use Room.
So we will write our data class, OwnerWithPets,
where we would still have the owner embedded
and the @Relation annotation on the list of Pet.
So here, in the @Relation annotation,
we're saying that there is a connection
between the pet owner of the Pet table
and the ownerId column of the Owner table.
Our query is simple.
It's just SELECT FROM Owner, returning
a list of OwnerWithPets.
OK.
So we have an owner having multiple pets.
But what if a pet can have multiple owners?
Well, in this case, we have a many to many relation.
And unfortunately, this can't be expressed simply
with two tables.
But rather, what we need to do is
to create a junction-- a link table.
A PetOwner table that contains the IDs of the pet
and of its owner.
So the pet owner would be an entity
with a composite primary key, the pet and the owner.
But now, if we want to create a list of all the owners
with all of their pets, things look like this.
So first, we would need to select from the owner,
and then we would need to write the query that
creates an inner join between the Pet and the Junction table.
So this is quite a lot of queries to write
for this amount of data.
So now, with Room, we can write things differently.
So we would still use the same OwnerWithPets data
that we've defined before with our owner and our list of pets.
Also annotate it with @Embedded and @Relation.
But what is different is this associateBy tag in @Relation.
So this is the one that tells Room
that, OK, you should connect the pets and the owner
based on this junction table, PetsAndOwner.
So now, in the dao, we need to just write
our query, SELECT FROM Owner, and return
the list of OwnerWithPets.
So this means that, now, in Room 2.2,
with one annotation, @Relation, we can support 1 to 1,
1 to many, and many to many relations.
OK, Danny, is there room for more features?
DANIEL SANTIAGO RIVERA: Yes.
Yes, there is.
There's more features.
Another thing we added in Room 2.2 was default values.
Not coupling default values, schema default values.
And you can find this in the column in Flow annotation.
There's a new property called defaultValue.
And this actually takes an SQLite expression.
So you can use things like [INAUDIBLE]
which is a keyword on SQLite.
Another thing we added in Room 2.2, or we made in Room 2.2,
was a gradle incremental annotation processor.
And this is basically a change we
did so that you can get some build benefits
when you're continuously building your application.
You have to opt in into this feature.
It's Annotation Processor flag.
We want to enable it by default in the future,
but we're in this phase where we're trying it out,
hashing out any issues.
So please try it out.
Let us know if it works or not.
The last feature we had room for was kind of like a long-lasting
bug--
or not a bug, but like a long issue
of dealing with a sublist of columns.
So imagine we had this entity, Dog.
It has a bunch of properties and a smaller [INAUDIBLE]
for it which has a subset for it.
If you try to query the Dogs table
but return a puppy, you will get a warning from Room, telling
you, hey, your query has too many columns.
So it's returning too many columns that you don't need.
And the reason this is happening is
because we were using that star projection.
With Room 2.2, we added a feature
that will actually take star projection
and rewrite it just to the columns that you need.
And you get that build benefit, that performance
benefit kind of for free.
We call this expanding projections.
It's an experimental feature.
So try it out and let us know how it goes.
To enable it, you have to go to your Annotation Processor
options and also up in via a flag.
Expanding projections, although, also
tackles the long-lasting issue of conflicting column names.
And you could see this a lot if you
tried to do a join of two tables that had
two columns with the same name.
For example, here, we have Dogs and Owners
and they both have a column name ID.
Expanding projections solve this because you
can define a [INAUDIBLE] and you can use embedded with prefixes.
And if you combine the prefix along with the SQLite operation
to rename the table, then Room will figure out
which column goes to which entity
and he will be able to rewrite your projections so
that there's no conflicting columns.
So this makes it easier for those
queries with big joins that have conflicting names.
As you keep adding more things, you
can keep your star projection, and Room will
be able to figure out the rest.
So to recap, we added Flow support for Room.
That completes the Coroutine story.
You can go ahead and use coupling and Coroutines
everywhere.
You can actually also replace Lite data with Flow.
We feel it's a nicer API, especially because it
has better error handling.
Pre-packaged database is a feature
that's been asked for a while.
You can have that, too.
We expanded relations, the annotation,
to have 1 to 1 and many to many.
If you still have to do some pretty complicated relations,
there's always the fallback of writing your own queries.
You get some sweet build speeds with that incremental
annotation processor.
Schema default bodies and expanded projections
are also very useful.
So try it out.
Let us know if you have any issues or problems.
Come talk to us.
We'll be in the sandbox.
Thank you.
FLORINA MUNTENESCU: Thank you.
[APPLAUSE]
[MUSIC PLAYING]