Getting Started With Room for Android
September 14, 2017One of the most exciting announcements for Android developers to come from Google I/O 2017 was the introduction of Android Architecture Components. Architecture Components are a collection of Android libraries from Google intended to help developers better handle Android lifecycle events and to persist data across those events. In the case of data persistence, part of this new framework is Room, a SQLite database helper library.
The Android framework already has components for working with SQLite database files created by your app, but they are very tedious to implement and modify due to how little abstraction there is from working with raw SQLite. Room is the newest player in the ORM field, and its simplicity and ease-of-use make it an attractive option. Many libraries have come forth in the past few years to make working with Android SQLite databases much cleaner and friendlier. Object-relational Mapping (ORM) database libraries, such as OrmLite, GreenDAO, SugarORM, DBFlow, and ActiveAndroid all attempt to lessen the tedium of SQLite database table creation and creating SQL statements to query and manipulate the database. Realm and ObjectBox are ORMs that use their own database systems under the hood instead of SQLite.
We’ll be using Kotlin and RxJava in the following examples.
Gradle
Add the following dependencies to your build.gradle
to get the Room, RxJava 2,
and the RxAndroid and RxKotlin bindings libraries. These are the latest versions
of these libraries as of this writing, so check to make sure that you have the
latest if you decide to give Room a try.
android {
...
}
dependencies {
...
// Database
def roomVersion = "1.0.0-alpha5"
compile "android.arch.persistence.room:runtime:$roomVersion"
kapt "android.arch.persistence.room:compiler:$roomVersion"
compile "android.arch.persistence.room:rxjava2:$roomVersion"
// RxJava
compile 'io.reactivex.rxjava2:rxjava:2.1.0'
compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
compile 'io.reactivex.rxjava2:rxkotlin:2.0.3'
}
Entity classes
@Entity(tableName = "users")
data class User(
@PrimaryKey(autoGenerate = true)
var id: Int = -1,
var username: String? = null,
var email: String? = null,
var isAdmin: Boolean = false
)
Specify your database entity with a class annotated with @Entity
, give it an
optional table name, annotate one of the fields as a @PrimaryKey
, and you’re
all set.
Data Access Object (DAO)
Now we need a DAO interface to specify how we will query/create/update/delete
our User
objects from the "users"
database table.
@Dao
interface UserDao {
// Retrieve a single User by its id
@Query("SELECT * FROM user WHERE id = :id")
fun getById(id: Int): User
// Retrieve all User objects
@Query("SELECT * FROM user")
fun getAllUsers(): List<User>
// Retrieve all User objects via an Rx Flowable object
// with the Users returned to the subscriber after the query completes,
// and the subscriber will be be called again each time the data changes.
@Query("SELECT * FROM user")
fun queryAllUsers(): Flowable<List<User>>
// Retrieve a single User by its id, returned via an Rx Single.
// If a user is found, onSuccess() is called.
// Otherwise, it will call onError() with
// android.arch.persistence.room.EmptyResultSetException
@Query("SELECT * FROM user WHERE id = :id")
fun getByIdSingle(id: Int): Single<User>
// Retrieve a single User by its id, returned via an Rx Maybe.
// If a User is found, onSuccess() is called.
// Otherwise, onComplete() is called.
@Query("SELECT * FROM user WHERE id = :id")
fun getByIdMaybe(id: Int): Maybe<User>
// insert a User
@Insert
fun insert(user: User)
// delete a User
@Delete
fun delete(user: User)
}
With this DAO interface, we’ve specified a few different operations that we can
use on the User table. Room allows us to use raw SQL statements for querying our
objects and we can provide different arguments to the SQL (:id
) with matching
method arguments (id: Int
). At compile time, Room will verify that each SQL
argument has a matching method argument.
Setting up the Database
@Database(
version = 1,
entities = arrayOf(
User::class
)
)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
}
Google recommends using the Singleton pattern when instantiating a
RoomDatabase
. We can either do this by putting an instance of it in our custom
Application
object:
class App : Application() {
companion object {
lateinit var database: AppDatabase
}
override fun onCreate() {
super.onCreate()
App.database = Room.databaseBuilder(this, AppDatabase::class.java, "app.db").build()
}
}
or if you are using Dagger dependency injection:
@Provides
@Singleton
fun provideAppDatabase(applicationContext: Context): AppDatabase {
return Room.databaseBuilder(applicationContext, AppDatabase::class.java, "app.db").build()
}
Type Converters
In the definition of the AppDatabase
class, you may have noticed the
@TypeConverters(Converters::class)
. We can define custom conversion methods to
convert an object into a type recognized and able to be persisted by
SQLite. For example, if you wanted to persist a
java.util.Date
as a Long
in the database,
class Converters {
@TypeConverter
fun fromTimestamp(value: Long?): Date? {
return if (value == null) null else Date(value)
}
@TypeConverter
fun dateToTimestamp(date: Date?): Long? {
return date?.time
}
}
Where the @TypeConverters
annotation is placed also affects its
scope, from applying to all fields down to individual fields.
Writing and reading to the database
Now that our database code is all set up, persisting data is as simple as
val user = User(
username = "johndoe",
email = "john@doe.com",
isAdmin = true
)
App.database.userDao().insert(user)
Well, almost that simple. If you run this, you’ll get this exception:
java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long period of time.
Room enforces database access to be off the main thread, since database operations can be potentially long-running. Using Rx and RxKotlin, we can easily move the database call to a background I/O thread.
Single.fromCallable {
val user = User(
username = "johndoe",
email = "john@doe.com",
isAdmin = true
)
App.database.userDao().insert(user)
}
.subscribeOn(Schedulers.io())
.subscribeBy(
onError = { error ->
Log.e(TAG, "Couldn't write User to database", error)
}
)
Using the same Rx approach, we can also read from the database.
Single.fromCallable {
// need to return a non-null object, since Rx 2 does not allow nulls
App.database.userDao().getById(123) ?: User()
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeBy(
onSuccess = { user ->
// do something with 'user'
},
onError = { error ->
Log.e(TAG, "Couldn't read User from database", error)
}
)
Using the dao method that returns a Flowable
, we can also set up a subscriber
to get updates to the database as they come in.
App.database.userDao().queryAllUsers()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe { users ->
// do something with 'users'
}
Conclusion
There are many solutions for persisting mass amounts of data for an Android app, with most making use of Android’s built-in SQLite framework. My favorite features of Room were being able to create methods annotated with SQLite statements for querying the database and its integration with Rx. I found Room to be relatively quick and easy to set up and using it with Rx to be just as quick and easy.
If you’d like to learn more about Room, check out the following resources:
Stay in the loop with our latest content!
Select the topics you’re interested to receive our new relevant content in your inbox. Don’t worry, we won’t spam you.

Product Strategy: Turning your App Ideas Into Reality
April 16, 2020Anyone who has been there knows, it can be incredibly frustrating having an app idea and not being able to bring it to life. And with organizational, technical, or financial roadblocks in the way, it can be equally frustrating to simply get a project off the ground.
Read more
The Future Of The Healthcare App: New Ways To Engage Patients And Doctors
April 1, 2020It wasn't until the recent events surrounding COVID-19 that I began to consider how apps can help improve the healthcare process. As someone who is purely an app user (not a developer), I decided to use some of my social distancing time to research healthcare apps and better understand where the opportunities lie.
Read more
Finding Connectedness During COVID
February 17, 2021When the world was turned upside down last March, our team was well set up for the technical side of what we do, but figuring out how to make us feel connected over a long stretch of remote work presented a new obstacle.
Read more