[MUSIC PLAYING]
YIGIT BOYAR: So today, we are going to talk about Room.
So we asked people to write offline-ready applications.
We want your application to work without the network connection.
But if you don't have a proper model inside your application,
it's pretty much impossible to write a good offline
experience.
So for that reason, you do need a database.
Luckily, on Android, we have SQLite.
And SQLite is a really, really awesome technology.
It's very fast, and when you need
to optimize it for your use case, it's very easy to do so.
It's a very powerful query language.
You can express many different things, and make it concise,
and easily grab the data.
It's the biggest advantage compared to other object
stores are key-value stores.
And SQLite also scales very well.
I mean, for an application, you probably won't have much data.
Well, you can have multiple gigabytes of data,
and SQLite will be just fine.
And there are companies using it on the back end.
So for your scalable to data size needs,
SQLite will be perfect.
Now, SQLite on Android was not that cool.
You need to write out a boilerplate code
to convert between your Java or Kotlin objects and your SQLite.
There is no compile time safety.
So if you're building an SQL query,
and you forget, like, a one comma if case,
you're going to get a runtime crash,
and it's very hard to test all those cases.
You also cannot observe what has changed.
Now, we want people to write reactive applications
or reactive UIs, and if you cannot observe your data model,
it's kind of hard.
You have to build it yourself.
So we built it for you.
So around two years ago, we shipped Room.
We introduced a compile time safety,
we introduced observability, and we introduced a strong IDE
integration.
As you can notice with Room, with Navigation,
this is a big theme for us.
We want to develop libraries together with Android Studio
to provide a nice user experience.
Then this year's I/O, we introduced write-ahead logging,
which speeds up SQLite a lot.
And we also introduced support for big paging
so that you can have very large datasets and queries,
and you can easily load them into a recycler view.
2.0 release is just our conversion from Android Support
to AndroidX.
And we kept it the same as 1.1 point
so that you can have an easy migration.
And 2.1 is what we're going to talk about today.
And this is actually kind of the X of 2.0.
It's a really large release.
We have full-text search, VIEWs, multi-instance invalidation,
AutoValue, and more RX stuff.
So let's talk about them.
DANIEL SANTIAGO: Sweet.
One of the pretty cool new features we added in 2.1--
it's Full-Text Search, or FTS.
And FTS is basically a way to index text documents
and make them searchable.
Let's take a look at an example.
Imagine we have a music app, and we want a search functionality
to it.
You have a search box.
You want to type something, and you
want to be able to find songs with that music.
If we have Room, we express.
We store the song data in a table.
That's an entity.
Conveniently, we have our label as an embedded object.
And our song labels--
what's the song name, the album name, and artist name--
this is kind of like what we want
to search and make the index.
If we were to do this without FTS right now,
we need to write a query.
And basically, you would have to use the LIKE operator.
This is not very good.
It's very limited.
That percentage sign is kind of like a wild card,
and this basically causes a full-table scan.
YIGIT BOYAR: Yeah, even if you index that
query column in the database, SQLite
will not be able to use that index,
because the index only works if you're
adding a prefix search, which is not what you want to do here.
DANIEL SANTIAGO: Yeah, so don't do this.
Moreover, if you try to actually search across album or artist,
you have to expand this query.
And this, as you can see, doesn't easily scale.
FTS helps us with this situation,
because it now creates a virtual table and all the columns
are indexed.
And to use FTS, you just now annotate your entity with @FTS.
Now, in your query, instead of using LIKE,
you would use MATCH, which is a different operator.
And as you can see here, we use WHERE Song.
So we're using the same column network statement,
and that basically tells the MATCH operator
that you want to search across all those labels.
So this helps us with searching across artists and albums
if you have an omni box.
You might say, oh, then I can use FTS on all of my tables
if I have an [INAUDIBLE],, but not quite.
So using FTS consumes more disk space.
And the reason is because when you create an FTS table,
you actually create a virtual table.
And that's backed by a few tables where your content is.
And a lot of that's indexing information.
This is known as shadow tables.
When you actually query from your virtual table,
the information actually comes from these tables.
There's also a few drawbacks to FTS.
You cannot have foreign keys on your entity or compose primary
keys.
But there's one pretty new feature,
which is external content.
Going back to our song entity, if we
wanted to instead use our real table
and create a second table for only our labels,
we just basically use that FTS annotation,
but we tell it, hey, my data is actually
going to be stored in this other real table that I already have.
Conveniently, this new data class and new virtual table
only has the labels.
So in our previous example, everything
was indexed, even the URL, which is not quite what
we want to index.
In this case, we only have the labels.
What happens now is we have a virtual table in front of it.
It's on FTS, and behind it, you have the same shadow
tables for an indexing.
But the actual content we store in the original Song table
that we have.
This is way better in saving disk space,
and it's a little bit more flexible.
To query this FTS table with extended content,
you do have to query from the virtual table,
and then you would do a join, because we
want to get the songs.
And then similarly, you will still use MATCH.
One thing, though, is that, because these
are two different tables, when you insert into the Song table,
things are not actually inserted into the virtual table, FTS
table, which means your index doesn't get updated,
so you have to do that yourself.
But we don't want you doing this.
We want to make it easy.
So when you use Room, Room will actually
create triggers for you to keep these two things in sync.
That's pretty cool.
YIGIT BOYAR: Another important feature we have added
is support for database views.
So let's go back our song and album example.
We have songs and albums, and a song might be multiple albums.
So we have a junction table that associates the songs
with albums.
Now, this is all cool.
So let's say you want to fetch a listing.
You want to have the album name and all the songs in it
as a list.
OK, cool.
We just write it.
We have the listing [INAUDIBLE],, and we write a query,
and fetch from that junction table.
Unfortunately, you cannot do this,
because that table doesn't have the song's name
or the album's title.
You kind of need to write a query like this, where
you fetch from that table and join it with the Song and Album
table, and then you can return your listing data.
Now, this is actually cool.
SQLite is powerful that you can express this.
But if you find yourself keep writing these things,
it's kind of like lots of boilerplate, things
you need to keep in your mind.
Wouldn't it be cool if you could just
have a table that has the song and the album
together without you duplicating the data
into that table and the Songs and the Album tables?
And this is where database views come into place.
So you basically write the query that defines Album and Song
together as a query.
You annotate an entity with the database view.
So you're saying that it's like view to this database.
And in that [INAUDIBLE],, this is the same Room rule,
so you can have any [INAUDIBLE] with the embedded fields
or what not.
Once you declare it and add it to your Room database--
so we have that declaration.
If you tried to rewrite the previous query,
then you just get rid of all the joins and instead
select from that table.
Well, we are selecting from a view.
That table doesn't exist.
But for all intents and purposes of querying, that's a table.
Now, it's much more simpler.
Because I said it's like a table,
you can also return that [INAUDIBLE]..
Or you can even return the live data.
Because we know how that view was constructed,
we know when the value might change,
so you could get the live data of it.
You can run queries.
You can pretty much do everything you can do with
a table, except you cannot do inserts and updates,
because there is no baking data.
But you can have views inside other views.
All that stuff works.
So this makes it much nicer to write queries,
and it also allows you to logically address your data.
Another important feature we have added
is support for multiple instances.
So let's say we are writing the application.
So we have a playlist, all the songs,
and we have a sync service that goes to our back end,
pulls the name, updates from my playlist,
and writes them into the database.
When you are using Room, if the sync service
updates the database, it automatically updates the UI.
And this is a super cool feature,
because you write these two components absolutely
independent of each other.
They don't know about each other.
They have the database as a sync point.
This works perfectly, but then your startup is successful,
you grow your team, your application is bloated,
so you decide to model that synchronization
into a background process.
Now, when it's running in a different process,
it pulls the song, writes it into the database,
and the UI has no idea.
It doesn't know the database has changed, because it only
knows that database is changed if the same Room instance is
the one making the updates.
So we can fake it, because we don't get that information
from SQLite.
There's something [INAUDIBLE].
Now, with Room 2.1, you can enable
multi-instance invalidation when you build the Room instance,
which will look for other instances of Room that are
accessing the same database.
Once you do that, now, your background process service
can update the database, and all instances of Room
will update automatically.
Now, we don't do this.
This is off by default, because we need to create the service.
There's some IPC involved.
It's not a big cost, but it is a cost
that most people don't need.
So you need to enable this flag to take
advantage of this feature.
DANIEL SANTIAGO: Another feature we added,
which was actually requested by the community,
was automatic support.
If you're using Kotlin, you don't
have to worry about this, because you have data classes.
But if you're still in Java world,
then you might be using auto value, because you
want Java immutable objects.
Well, Room, now, can understand these auto value
annotated objects.
If you know a little bit about auto value,
you basically have an abstract class,
and you annotate it with AutoValue.
But now, you can also annotate that same abstract class
with @Entity, and Room will be able to discover
that you want to make a backing SQLite table for it.
In auto value, instead of having fields,
you have abstract getters.
These can now be annotated with Room annotations,
so their primary key, column information, and things
like that.
The only caveat, though, is that you
do have to also add auto values CopyAnnotation,
and this is the annotation that basically makes
these two tools work together.
YIGIT BOYAR: By the way, to support this, so normally
these annotations were only limited to fields,
and we needed to extend it to let you put them
on those abstract methods.
But it only works if you're using auto value.
If you're not using auto value, we are not
going to let it compile.
DANIEL SANTIAGO: Similarly, if you were writing a normal data
class, you would have a constructor with the fields.
In auto value world, you still need that factory method.
And Room will be able to discover this to create
your auto value class.
And then using it, you would use it at any other data class.
You would use the abstract class that you declare.
Another highly requested feature that
has been requested for a while-- and we finally
got it-- is more Rx.
So now, you can actually have asynch return
times Completable, Single, and Maybe,
in methods annotated with Insert, Update, and Delete.
So you know we listen.
And when you request stuff, we'll listen.
[LAUGHS]
YIGIT BOYAR: Yeah, this is actually only available in Rx.
That's interesting.
DANIEL SANTIAGO: It might be available in another type
of asynch.
YIGIT BOYAR: Like [INAUDIBLE],, Kotlins?
DANIEL SANTIAGO: Maybe.
YIGIT BOYAR: Yeah, please.
So Room 2.1 is a really big release.
The full-text search, the database views--
and when we decided which features to work on,
we are basically relying on your feedback.
I personally objected that allowing complete
of less stuff in those Insert queries for a long time,
and I gave up, because people really wanted it.
And this is our development philosophy.
We basically look at what the community is doing,
how are they using it, what do they want, and implement them.
So please, try to use to 2.1.
It's a very big release.
And we want to just ship it as stable as soon as possible,
and we need your feedback.
We basically look at the number of apps shipping with Room
and see how they are using it.
We'll look for the incoming bugs.
I mean, we don't really have bugs, but sometimes--
DANIEL SANTIAGO: No bugs, no bugs.
YIGIT BOYAR: --look for incoming user errors, try fix them.
And [INAUDIBLE] to ship.
So please work with us, and we'll
try to wrap it up and ship it.
And also please let us know what other
features you want in Room so that we can implement them.
All right, thanks a lot for coming to this talk.
I hope it was useful.
[APPLAUSE]
DANIEL SANTIAGO: Thank you.
[MUSIC PLAYING]