[MUSIC PLAYING]
ELIF BILGIN: Hello, everyone.
My name is Elif Bilgin.
I'm one of the engineers on the Android Toolkit and Jetpack
team.
And today we're going to be talking
about what's new in Room.
The last time we talked about Room was at 2.2, at which point
we were already supporting so many features.
But then all of those features weren't enough,
and you still thought that there was still
room for improvements.
So here's what we've been up to since.
First up, we have auto migrations,
a brand new feature that's bound to make migrations easy.
So let's do a quick recap before we barge into the Room.
What is a migration?
A database migration allows you to update your database schema
with any changes between versions
while avoiding data loss.
During a migration, Room will check and validate
the updated schema.
The schema can also be exported if the user sets a flag
in the database definition.
Before 2.4, you had to implement a manual migration
class in which you would write and execute all the SQL
statements needed to migrate between versions
of your database.
This meant that you would risk ending up
implementing a migrate function involving
a lot of long and complex SQL queries,
increasing the risk for any errors.
Let's walk through an example of the scenario.
Say you have a database with two tables, Artist and Track.
Let's say you would like to rename the table Track to Song.
If you were to make this change using a manual migration,
this is how that would work.
You can see here that you will have
to manually write and execute the statement to perform
this change.
However, with auto migrations, you
can handle this change by simply defining
an auto migration in your database definition
and providing the exported schemas of the two versions.
The Auto Migration API will handle the rest
by generating and implementing the migrate function for you,
complete with writing and executing all the necessary SQL
statements.
Now, let's go one step further and say
we want to change one of the fields in the Artist table,
renaming singerName to artistName.
While this may seem like something trivial,
SQLite doesn't have an API for this,
so we will need to go through the 12-step generalized ALTER
TABLE procedure.
Using manual migrations, this would
mean that you would have to manually write and execute
all the SQL statements associated with the 12 steps.
So here's how that would go.
You would take the table we want to perform the change on.
You would create a table that has the structure you want
after the change has been made.
As you can see here, the singerName column
has been renamed to artistName.
You would then insert the data from the old table
to no one to prevent any data loss.
You would drop the old table.
And you would lastly rename the new table
to the original name of the table, which
was Artist followed by a foreign key check.
Looking at this in terms of code,
using a manual migration for straightforward cases
like this, where there's literally just one change
done between versions, can be very verbose and error-prone.
We definitely don't want to write all that,
so let's use auto migrations.
In the same example of a column rename,
Room can't handle this out-of-box.
The reason for this is because of how my auto migrations
are generated under the hood.
Room goes through the schemas of the two versions
of the database, making comparisons
to detect changes between them.
In the case where a column or a table has been renamed,
Room cannot clarify what change has occurred.
Has it been deleted and a new one added?
Or has it been renamed?
And the same problem applies for a deletion
of a column or a table.
In these ambiguous cases, Room needs a bit of help from you.
More precisely, you need to define an auto migration spec.
The auto migration spec is an interface
for defining auto migration specifications, clarifying
these ambiguous scenarios.
This is done by annotating the spec with the relevant change
annotations.
So here in this case, we have a renamed column annotation
where we provide the tableName, the original name
of the column, and the updated name.
Like this, the code is cleaner and easier
to read and understand.
As a bonus, if you have any additional tasks
to execute after the migration has been completed,
you can use the onPostMigrate function of the auto migration
spec to run them.
When you've finished implementing your auto
migration spec, you will need to provide it
to the database definition and define an auto migration.
Now, once again, with the two schemas
of the versions you want to migrate between provided,
the Auto Migration API will handle the rest
by generating and implementing the migrate function for you.
Going back to the ambiguous change scenarios
that require user input, these are
all the available annotations.
Even more, if you have multiple changes that
require user input within the same migration,
you can use repeatable annotations
to provide the needed clarifications.
All other changes will be handled out of the box
without any input needed.
Let's say you've started using auto migrations
in your implementations, and you want
to test that they're working properly.
Well, good news-- to test auto migrations,
you can actually use the existing MigrationTestHelper
API.
No change needed.
MigrationTestHelper will automatically run and validate
all auto migrations, and no input will be needed from you.
Under the hood, if auto migrations are present,
they will be automatically added to the list of migrations
to be run and validated.
It is important to note that user-provided migrations are
of higher priority, meaning that if you were to define an auto
migration going between two versions that already had
a custom migration defined, then the custom migration will
be preferred over the auto migration.
Next up we have another big feature we have made room for,
relational query methods.
Let's say we have the same database from before,
with the same tables, which are now Artist and Song,
and we want to get a collection that is
a mapping of artists to songs.
Looking at this with some example data in our tables,
this would mean that Purple Lloyd would get matched
with their hit songs such as "Another Tile in the Ceiling"
or "The Great Pig in the Sky," and AB/CD
would get matched with their songs,
"Back in White" and "Highway to Heaven."
If we're using relation and embedded,
this is how the mapping would work.
As you can see here, we create a brand new data class, artist
and songs, where an artist is related to a list of songs.
In fact, if we were to represent other relationships,
we would need to create additional data classes
to represent them, which can easily
raise verbosity problems.
Relation is meant to be used for simple cases
where you would only have a few straightforward
relationships in your database.
Since, in relation, there's no filtering, sorting, grouping,
or compose keys, you're limited in the relationship result.
But it is a quick and easy way to get
the job done for simpler tasks.
In order to support these missing features,
we didn't expand on relation because it
would involve complicating the annotation and move users away
from SQL.
Room wants you to embrace SQL to its full potential,
as it is very powerful.
So let's look at how Room solves this problem with a brand
new feature.
To represent the same relationship shown previously
between artists and their songs, we
can now write a simple down method
with a return type of a map.
You only need to provide the query and the return tag.
Room will take care of the rest.
Under the hood, what Room will do
is find artist and song and a cursor,
and put them in the key and value.
In this case, we have a one-to-many mapping
where an individual artist is mapped
to a collection of their songs.
You can also have a one-to-one mapping, like so.
You can actually have a lot of flexibility in what
you can use within the mapping.
This is possible with the magic of the MapInfo annotation.
MapInfo is a helper API for user input
in terms of clarifications, similar to the auto migration
change annotations we talked about.
It allows you to clarify exactly what you
would like to do with the information returned
in the cursor from a query.
With the MapInfo annotation, you can
specify which column you want to use for the key and value
mappings of your queries in the output data structure.
It is important to note that the type being used for the key
must implement equals and hashCode functions,
as this is crucial for the mapping process.
Here's an example in which the artist
name has been used as the key.
Here's another example in which an artist is mapped
to a list of their song names.
And lastly, here's an example where an artist name is mapped
to a list of their song names.
The MapInfo annotation gives you the flexibility
to use specific columns instead of the entire data class
for more customized mapping.
Another added benefit of relational query methods
is that now we can support more data operations.
Here's an example of grouping that is now seamlessly
possible with this new feature.
And here's an example of filtering that is now possible.
Lastly, it's important to note that multimap is a core return
type, and can be wrapped with the various observable types
that are already supported by Room.
So relational query methods allow
you to easily define as many relations
as you want in your database.
Wow, that was a lot of big news.
But we have room for more features.
Here's a lightning round of some of the smaller cool features
that we've added to Room.
First up, enum type converters.
Room will now default to using an enum to string-- and vice
versa-- type converter if none is provided.
If a type converter for an enum already exists,
Room will prioritize using it over the default one.
Query callbacks-- sometimes you want
to log queries to see what's going on in your database.
We added callbacks to the Room database's builder method.
So when the database is running queries, it'll get logged.
We have also extended some of our API support.
Room now supports native paging 3.0 APIs
and removes the previous workaround.
Important to note that you will now
need to depend on a new artifact called room-paging.
We've also added support for RxJava3.
And note that you will now need to depend on a new artifact
room-rxjava3.
Last up, Kotlin Symbol Processing, for short, KSP.
Room 2.4.0 now includes support for KSP,
a native replacement for KAPT that
runs annotation processors as a part of Kotlin compilation.
Moving to KSP is very simple.
Just remove the Kotlin KAPT plugin,
unless you have other annotation processors using KAPT,
and replace it with the KSP plugin.
Similarly, in your dependencies, add Room
to the KSP configuration instead of KAPT, and you're done.
So why switch the KSP?
Well, KSP provides up to two times faster compilation
for Room, speeding up your builds
and saving valuable development time.
As KSP directly reads Kotlin code,
Room has a much better understanding
about the Kotlin code like nullability,
and generates code accordingly, preventing null pointer
exceptions at runtime and much more.
As KSP becomes stable, we will expand room's Kotlin support
for long-awaited features like value classes,
support, and generating Kotlin code.
Auto-migrations, relational query methods, KSP, this
was a lot.
We hope you're just as excited as we are about all
of these Room renovations.
Do check them out and start using them in your apps.
Thanks for watching.
[MUSIC PLAYING]