What’s New in Room?
Room keeps changing, adding in new features with every major and minor release. This chapter highlights some of those changes.
Version 2.3.x
April 2021 saw the release of 2.3.0
. As of late October 2021, no new patch releases have been required.
2.3.0
gave us a bunch of improvements, particularly around type and column management.
Enum Support
Historically, if your @Entity
had a property that was some enum class
, you needed to write a type converter to convert that enum to and from something else, such as an Int
or String
. That is still an option.
However, Room 2.3.0
added automatic conversion of an enum class
value to and from its String
representation. So, now you can use an enum without a @TypeConverter
pair:
package com.commonsware.room.misc
import androidx.room.*
enum class Silly { First, Second, Third }
@Entity(tableName = "autoEnum")
data class AutoEnumEntity(
@PrimaryKey(autoGenerate = true)
var id: Long,
var silly: Silly
) {
@Dao
abstract class Store {
@Query("SELECT * FROM autoEnum")
abstract fun loadAll(): List<AutoEnumEntity>
@Query("SELECT * FROM autoEnum WHERE id = :id")
abstract fun findById(id: Int): AutoEnumEntity
fun insert(entity: AutoEnumEntity): AutoEnumEntity {
entity.id = _insert(entity)
return entity
}
@Insert
abstract fun _insert(entity: AutoEnumEntity): Long
}
}
In this case, our silly
column is declared as TEXT
in the generated SQL:
CREATE TABLE IF NOT EXISTS `autoEnum` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `silly` TEXT NOT NULL)
Part of what Room code-generates for us are functions to convert the enum class
values to String
representations and back:
private String __Silly_enumToString(final Silly _value) {
if (_value == null) {
return null;
} switch (_value) {
case First: return "First";
case Second: return "Second";
case Third: return "Third";
default: throw new IllegalArgumentException("Can't convert enum to string, unknown enum value: " + _value);
}
}
private Silly __Silly_stringToEnum(final String _value) {
if (_value == null) {
return null;
} switch (_value) {
case "First": return Silly.First;
case "Second": return Silly.Second;
case "Third": return Silly.Third;
default: throw new IllegalArgumentException("Can't convert value to enum, unknown value: " + _value);
}
}
Room then uses those functions as if they were a custom @TypeConverter
, to convert our enum class
values to and from a String
representation to put in the TEXT
column.
Type Converter Improvements
The @TypeConverter
annotation is a useful way to get Room to recognize types that it otherwise could not handle. However, @TypeConverter
had been fairly inflexible: you had to implement the functions on a class, where Room would instantiate that class as needed.
Room 2.3.0 improves the flexibility in this area.
The simple improvement is that now you can have @TypeConverter
annotations on an object
, saving Room the need to instantiate the class.
The more complex improvement is in a new @ProvidedTypeConverter
annotation. This is for cases where you are happy to have the @TypeConverter
functions be on a class, but you want to be in charge of creating the object for that class. One popular case is where you want to use a dependency inversion framework (Dagger/Hilt, Koin, etc.) — for example, perhaps you are providing a JSON converter via DI and need to inject the converter into an object that will use that converter for @TypeConverter
functions.
To make this work, you write a class
to house your @TypeConverter
functions as normal. However, now you can add a constructor on that class or otherwise configure it as you see fit. You do, however, also have to add @ProvidedTypeConverter
as a class-level annotation:
@ProvidedTypeConverter
class SomeValueTypeConverter(private val adapter: JsonAdapter<SomeValueType>) {
@TypeConverter
fun someValueFromJson(json: String) = adapter.fromJson(json)
@TypeConverter
fun someValueToJson(value: SomeValueType) = adapter.toJson(value)
}
Here, we have a SomeValue
type (not shown here) and an injected Moshi adapter for that type. The @TypeConverter
-annotated functions simply delegate to Moshi.
@ProvidedTypeConverter
tells Room to allow classes without a public zero-argument constructor. However, the cost is in the second step: you need to supply the instance of your @ProvidedTypeConverter
class to the RoomDatabase.Builder
, via an addTypeConverter()
call:
class YourRepository(private val someValueTypeAdapter: JsonAdapter<SomeValueType>) {
val db = Room.databaseBuilder(context, YourDatabase::class.java, DB_NAME)
.addTypeConverter(SomeValueTypeConverter(someValueTypeAdapter))
.build()
}
The name of the annotation explains the rule: by using @ProvidedTypeConverter
, you have more flexibility, but you have the corresponding responsibility to provide the instance of the type converter to the RoomDatabase
.
Packaged Databases Improvements
As we have seen, you can package a database with your app, so you have starter data immediately when the app is first opened. Room 2.3.0 makes a couple of improvements in this area.
In addition to using databases packaged as assets — as the chapter on packaged databases focuses on — you can use the same technique to populate your database from a file, using createFromFile()
instead of createFromAsset()
. This, however, requires a File
object, and those are more difficult to come by given the “scoped storage” added to Android 10. Room 2.3.0 now gives us createFromInputStream()
, so we can get our starter database from a InputStream
, such as one that we might get from openInputStream()
on a ContentResolver
using a Uri
.
Also, these create...()
functions now have a variant that takes an additional RoomDatabase.PrepackagedDatabaseCallback
object (whose name makes the author very grateful for auto-completion in modern IDEs…). This will be called with onOpenPrepackagedDatabase()
after the data source has been copied and the Room database is set up. That way, if you need to do some cleanup (e.g., delete a downloaded file from getCacheDir()
), you know it is safe to do so.
@RewriteQueriesToDropUnusedColumns
Using *
in a @Query
SQL SELECT
statement is easy. However, by default, Room is not very sophisticated about using it. Room will happily retrieve all the columns available to it from your table, even if your entity or other output object only uses a few of those columns. Plus, you will get a compile-time warning from Room about the inefficiency. It is more efficient to have your SELECT
list the exact columns that you need, but this gets tedious to keep in sync with your output object structures.
Adding @RewriteQueriesToDropUnusedColumns
to your @Query
-annotated function tells Room to try to handle this automatically. The Room compiler will replace your *
with the column names that are needed in the SQL that it actually executes. That way, you get the convenience of *
and the efficiency of only querying for the needed columns. You can also add @RewriteQueriesToDropUnusedColumns
to the entire @Dao
class or interface, to have it apply to all @Query
functions.
Paging 3 Support
If you are using Paging 3 — the now-latest generation of the Jetpack Paging framework — Room will now let you set up DAO functions that support it. The chapter on paging has been updated to show Paging 3.
RoomDatabase.QueryCallback
For logging purposes, it might be useful to learn when Room executes some SQL. For that, you can call setQueryCallback()
on your RoomDatabase.Builder
when you are setting up Room. This function takes a RoomDatabase.QueryCallback
object, which has a single onQuery()
function — this makes it easy to be replaced by a lambda expression in Java or Kotlin. There, you get a String
of the SQL to be executed and a List
of the objects to be bound as replacements for ?
-style placeholders in that SQL.
Do be careful, though, about your logging, as the bind objects may contain data that should be considered private. Consider only logging in debug
builds.
RxJava 3 Support
Not everybody realizes this, but RxJava 2.x is end-of-life: other than some bug fixes, no new updates are planned for that. The RxJava team has moved on to RxJava 3, which has a similar API to RxJava 2.x but not quite identical.
Previously, Room supported other reactive options — LiveData
, RxJava 2.x, coroutines — but not RxJava 3. Now, using androidx.room:room-rxjava3
, you can use RxJava 3 types in your Room DAO functions, if you so choose.
Prev Table of Contents
This book is licensed under the Creative Commons Attribution-ShareAlike 4.0 International license.