7 Pro-tips for Room

HoxL...oz8a
31 Mar 2023
31

Room is an abstraction layer on top of SQLite that makes it easier and nicer to persist data. If you’re new to Room then check out this primer:
In this article, I’d like to share some pro-tips on getting the most out of Room:
Pre-populate your database via RoomDatabase#Callback
Use DAO’s inheritance capability
Execute queries in transactions with minimal boilerplate code via @Transaction
Read only what you need
Enforce constraints between entities with foreign keys
Avoid false positive notifications for observable queries
1. Pre-populate the database
Do you need to add default data to your database, right after it was created or when the database is opened? Use RoomDatabase#Callback! Call the addCallback method when building your RoomDatabase and override either onCreate or onOpen.
onCreate will be called when the database is created for the first time, after the tables have been created. onOpen is called when the database was opened. Since the DAOs can only be accessed once these methods return, we‘re creating a new thread where we’re getting a reference to the database, get the DAO, and then insert the data.
See a full example here.
Note: When using the ioThread approach, if your app crashes at the first launch, in between database creation and insert, the data will never be inserted.
2. Use DAO’s inheritance capability
Do you have multiple tables in your database and find yourself copying the same Insert, Update and Delete methods? DAOs support inheritance, so create a BaseDao<T> class, and define your generic @Insert, @Update and @Delete methods there. Have each DAO extend the BaseDao and add methods specific to each of them.
See more details here.
The DAOs have to be interfaces or abstract classes because Room generates their implementation at compile time, including the methods from BaseDao.
3. Execute queries in transactions with minimal boilerplate code
Annotating a method with @Transaction makes sure that all database operations you’re executing in that method will be run inside one transaction. The transaction will fail when an exception is thrown in the method body.
You might want to use the @Transaction annotation for @Query methods that have a select statement, in the following cases:
When the result of the query is fairly big. By querying the database in one transaction, you ensure that if the query result doesn’t fit in a single cursor window, it doesn’t get corrupted due to changes in the database between cursor window swaps
When the result of the query is a POJO with @Relation fields. The fields are queries separately so running them in a single transaction will guarantee consistent results between queries.
Delete, @Update and @Insert methods that have multiple parameters are automatically run inside a transaction.
4. Read only what you need
When you’re querying the database, do you use all the fields you return in your query? Take care of the amount of memory used by your app and load only the subset of fields you will end up using. This will also improve the speed of your queries by reducing the IO cost. Room will do the mapping between the columns and the object for you.
Consider this complex User object:
On some screens we don’t need to display all of this information. So instead, we can create a UserMinimal object that holds only the data needed.
In the DAO class, we define the query and select the right columns from the users table.
5. Enforce constraints between entities with foreign keys
Even though Room doesn’t directly support relations, it allows you to define Foreign Key constraints between entities.
Room has the @ForeignKey annotation, part of the @Entity annotation, to allow using the SQLite foreign key features. It enforces constraints across tables that ensure the relationship is valid when you modify the database. On an entity, define the parent entity to reference, the columns in it and the columns in the current entity.
Consider a User and a Pet class. The Pet has an owner, which is a user id referenced as foreign key.
Optionally, you can define what action to be taken when the parent entity is deleted or updated in the database. You can choose one of the following: NO_ACTION, RESTRICT, SET_NULL, SET_DEFAULT or CASCADE, that have same behaviors as in SQLite.
Note: In Room, SET_DEFAULT works as SET_NULL, as Room does not yet allow setting default values for columns.
6. Simplify one-to-many queries via @Relation
In the previousUser-Pet example, we can say that we have a one-to-many relation: a user can have multiple pets. Let’s say that we want to get a list of users with their pets: List<UserAndAllPets>.
To do this manually, we would need to implement 2 queries: one to get the list of all users and another one to get the list of pets based on a user id.To do this manually, we would need to implement 2 queries: one to get the list of all users and another one to get the list of pets based on a user id.
We would then iterate through the list of users and query the Pets table.
To make this simpler, Room’s @Relation annotation automatically fetches related entities. @Relation can only be applied to a List or Set of objects. The UserAndAllPets class has to be updated:
In the DAO, we define a single query and Room will query both the Users and the Pets tables and handle the object mapping.
7. Avoid false positive notifications for observable queries
Let’s say that you want to get a user based on the user id in an observable query:
You’ll get a new emission of the User object whenever that user updates. But you will also get the same object when other changes (deletes, updates or inserts) occur on the Users table that have nothing to do with the User you’re interested in, resulting in false positive notifications. Even more, if your query involves multiple tables, you’ll get a new emission whenever something changed in any of them.
Room only knows that the table has been modified but doesn’t know why and what has changed. Therefore, after the re-query, the result of the query is emitted by the LiveData or Flowable. Since Room doesn’t hold any data in memory and can’t assume that objects have equals(), it can’t tell whether this is the same data or not.Note: if you’re returning a list to be displayed, consider using the Paging Library and returning a LivePagedListBuilder since the library will help with automatically computing the diff between list items and updating your UI

Write & Read to Earn with BULB

Learn More

Enjoy this blog? Subscribe to RCB Lovers😍

0 Comments

B
No comments yet.
Most relevant comments are displayed, so some may have been filtered out.