To access app data using Room, use DAOs(Data Access Objects). These collections of Dao objects make up the main components of Room, and each Dao contains abstract methods to access the database. Using DAOs to access the database instead of direct queries enables separation of the components of the database architecture. In addition, DAOs makes it easier to mock data when you test your app.

Note: Configure dependencies

Daos can be either interfaces or abstract classes. If it is an abstract class, you can optionally provide a constructor that takes RommDatabase as its only argument. Room creates an implementation for each DAO at compile time.

Note: If you do not call allowMainThreadQueries() in the Builder, then you cannot use Room to access the database in the main thread, as this may cause the main thread to block for a long time. Asynchronous queries — queries that return LiveData or Flowable — are not subject to this rule. Because they can be queried asynchronously in background threads.

Some simple and useful methods

There are several simple and useful queries that can be represented using DAO classes, and this document includes several common examples.

Insert

If you create DAO class methods that use the @INSERT annotation,Room will generate an implementation for inserting all parameters into the database in a single transaction.

@Dao
interface MyDao {
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insertUsers(vararg users: User)

    @Insert
    fun insertBothUsers(user1: User, user2: User)

    @Insert
    fun insertUsersAndFriends(user: User, friends: List<User>)
}
Copy the code

If the @INSERT annotated method accepts only one argument, it will return a long value representing the rowId of the newly inserted item. If you receive an array or collection, the return is either long[] or List.

For more details, see the reference documentation for the @INSERT annotation. And SQLite documentation for Rowid Tables.

Update

The @update annotation makes it easy to modify a set of entities given as parameters in the database. It uses a query that matches the primary key of each entity.

@Dao
interface MyDao {
    @Update
    fun updateUsers(vararg users: User)
}
Copy the code

Although it is usually not necessary, you can have this method return an int indicating how many records in the database have been updated.

Delete

The @delete annotation removes a set of entities given as parameters from the database. It uses the primary key to query for the entity to be deleted.

@Dao
interface MyDao {
    @Delete
    fun deleteUsers(vararg users: User)
}
Copy the code

Query information

@Query is the main annotation used in the DAO class. It allows you to perform read and write operations in the database. Every method annotated by @Query is checked at compile time, so if there is a problem with the Query, there is a compile error, not a run-time failure.

Room also validates the return value of the query, so if the field name in the returned object does not match the column name in the query response, it will be prompted in one of two ways.

  • Issue a warning if only part of the field names match
  • If no field names match, an error is given

A simple query

@Dao
interface MyDao {
    @Query("SELECT * FROM user")
    fun loadAllUsers(): Array<User>
}
Copy the code

This is a simple query that loads all users. At compile time, Room knows that it is querying all columns of the User table. If the query contains a syntax error, or if the user table does not exist in the database, Room will give you a visual error during your app compilation.

Pass the parameters to the query

Most of the time, you need to pass parameters into the query to perform filtering operations, such as showing only users over a certain age. To do this, use the method parameter in the Room annotation.

@Dao
interface MyDao {
    @Query("SELECT * FROM user WHERE age > :minAge")
    fun loadAllUsersOlderThan(minAge: Int): Array<User>
}
Copy the code

When processing this query at compile time, Room binds :minAge to the minAge in the method argument. Room uses parameter names to perform a match, and if it does not, an error is thrown at compile time.

You can use passing multiple parameters or referencing them multiple times in a query.

@Dao
interface MyDao {
    @Query("SELECT * FROM user WHERE age BETWEEN :minAge AND :maxAge")
    fun loadAllUsersBetweenAges(minAge: Int, maxAge: Int): Array<User>

    @Query("SELECT * FROM user WHERE first_name LIKE :search " +
           "OR last_name LIKE :search")
    fun findUserWithName(search: String): List<User>
}
Copy the code

Returns a subset of the columns

Most of the time, you only need to get a few fields in the entity. For example, your UI might display only the first and last name, rather than the user’s full information. Get only the few columns you need, save valuable resources, and the query completes faster.

Room allows you to return any Java-based object from a query, as long as the result column set can be mapped to that object. For example, you could create the following object to query a user’s first and last name

data class NameTuple(
    @ColumnInfo(name = "first_name") val firstName: String? , @ColumnInfo(name ="last_name") val lastName: String?
)
Copy the code

Now you can use:

@Dao
interface MyDao {
    @Query("SELECT first_name, last_name FROM user")
    fun loadFullName(): List<NameTuple>
}
Copy the code

Room understands that the query returns the values of the first_name and last_name columns and that these values are mapped to the NameTuple member variable. Because Room generates the correct code. Room will warn if the query returns too many columns, or if NameTuple does not exist.

Note: These POJOs can also be annotated with @embedded

Pass parameter set

Some queries may need to insert a variable number of parameters, the exact number of which is known only at run time. For example, you might want to retrieve information about all users at a location from a subset of regions. When an argument is a collection, Room knows to expand it automatically at run time based on the number of arguments

@Dao
interface MyDao {
    @Query("SELECT first_name, last_name FROM user WHERE region IN (:regions)")
    fun loadUsersFromRegions(regions: List<String>): List<NameTuple>
}
Copy the code

Observable queries

When performing queries, you always want your app’s UI to update automatically as the data changes. To do this, use LiveData as the return value of the query method. Room generates all the code necessary to update LiveData when data changes in the database

@Dao
interface MyDao {
    @Query("SELECT first_name, last_name FROM user WHERE region IN (:regions)")
    fun loadUsersFromRegionsSync(regions: List<String>): LiveData<List<User>>
}
Copy the code

Note: Since version 1.0, Room uses the list of tables accessed in queries to decide whether to update LiveData instances.

Use RxJava for reactive queries

Room provides the following support for RxJava2 return values:

  • @ Query method: Publisher, Flowable, observables
  • @insert, @update, @delete methods :Complete,Single and Maybe

To use this feature, add a dependency in your app’s build.gradle

dependencies {
    def room_version = "2.1.0."
    implementation 'androidx.room:room-rxjava2:$room_version'
}
Copy the code

View the current version of the library, Versions

@Dao
interface MyDao {
    @Query("SELECT * from user where id = :id LIMIT 1")
    fun loadUserById(id: Int): Flowable<User>

    // Emits the number of users added to the database.
    @Insert
    fun insertLargeNumberOfUsers(users: List<User>): Maybe<Int>

    // Makes sure that the operation finishes successfully.
    @Insert
    fun insertLargeNumberOfUsers(varargs users: User): Completable

    /* Emits the number of users removed from the database. Always emits at
       least one user. */
    @Delete
    fun deleteAllUsers(users: List<User>): Single<Int>
}
Copy the code

Direct access to the cursor

If your app logic requires you to return rows directly, you can return Cursor objects in the query

@Dao
interface MyDao {
    @Query("SELECT * FROM user WHERE age > :minAge LIMIT 5")
    fun loadRawUsersOlderThan(minAge: Int): Cursor
}
Copy the code

Warning: It is strongly recommended not to use the Cursor API because it does not guarantee which rows are present or which values are not contained within the row. Use this feature only if you really need to use CURSOR and cannot easily refactor your code

Query table more

Some queries may require multiple tables to compute the results. Room allows you to write arbitrary queries, so you can correlate table queries. In addition, if the data type is observable in the response, such as Flowable or LiveData,Room checks whether the table reference in the query is valid. The following code snippet shows how to perform a table join to merge information between a table containing the user who is borrowing a book and a table containing data about the current borrowing book:

@Dao
interface MyDao {
    @Query(
        "SELECT * FROM book " +
        "INNER JOIN loan ON loan.book_id = book.id " +
        "INNER JOIN user ON user.id = loan.user_id " +
        "WHERE user.name LIKE :userName"
    )
    fun findBooksBorrowedByNameSync(userName: String): List<Book>
}
Copy the code

You can also return POJOs in a query, for example, you can write a query to load users and their pet names

@Dao
interface MyDao {
    @Query(
        "SELECT user.name AS userName, pet.name AS petName " +
        "FROM user, pet " +
        "WHERE user.id = pet.user_id"
    )
    fun loadUserAndPetNames(): LiveData<List<UserPet>>

    // You can also define this class ina separate file. data class UserPet(val userName: String? , val petName: String?) }Copy the code

Write asynchronous methods using Kotlin coroutines

You can use Kotlin’s coroutine functionality for asynchronous execution by adding the suspend keyword to the DAO method. It guarantees that it cannot be executed on the main thread.

@Dao
interface MyDao {
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insertUsers(vararg users: User)

    @Update
    suspend fun updateUsers(vararg users: User)

    @Delete
    suspend fun deleteUsers(vararg users: User)

    @Query("SELECT * FROM user")
    suspend fun loadAllUsers(): Array<User>
}
Copy the code

Note: Using Kotlin coroutines in Room requires Room 2.1.0,Kotlin 1.3.0,Coroutlines 1.0.0 and above.

This guide also applies to methods that use the @Transaction annotation in daOs. You can use this feature to build suspended database methods from other DAO methods. These methods are then run in a single database transaction.

@Dao
abstract class UsersDao {
    @Transaction
    open suspend fun setLoggedInUser(loggedInUser: User) {
        deleteUser(loggedInUser)
        insertUser(loggedInUser)
    }

    @Query("DELETE FROM users")
    abstract fun deleteUser(user: User)

    @Insert
    abstract suspend fun insertUser(user: User)
}
Copy the code

Note: Avoid performing additional application-side work in a single database transaction, because Room treats transactions as exclusive transactions and can only execute one at a time. This means that transactions that contain more operations than necessary can easily lock up the database and affect performance.


0. Overview

1. Use Room entities to define data

2. Define relationships between objects

3. Create a view in the database

4. Use Room DAOs to access data

5. Migrate the database

6. Test the database

7. Reference complex data using Room