Getting Started With Room Persistence Library
This year at Google I/O, the Android team announced Android Architecture Components a combination of new, helpful libraries for Android development. One that particularly interested me was the Room Persistence Library, which is an abstraction layer of SQLite designed to make database access and creation a lot easier. Right off the bat it reminded me of Realm, which I learned about at Droidcon NYC and really admired, so I decided to dive in and build a todo list using Room & RxJava.
Update
I am leaving this information for legacy sake, but about a year after this was published I wrote again on the topic, including some new info and a little nice Kotlin syntax.
Project Setup
First, add the following dependencies to your app’s build.gradle
file. The Kotlin dependency is optional, but it’s the language I’ll be using for this tutorial.
compile "android.arch.persistence.room:runtime:$roomLibraryVersion"
compile "android.arch.persistence.room:rxjava2:$roomLibraryVersion"
compile "io.reactivex.rxjava2:rxjava:$rxJavaVersion"
compile "io.reactivex.rxjava2:rxandroid:$rxAndroidVersion"
annotationProcessor "android.arch.persistence.room:compiler:$roomLibraryVersion"
kapt "android.arch.persistence.room:compiler:$roomLibraryVersion"
If you’re not using Kotlin, you also don’t need the kapt
line at the end. That’s for Kotlin annotation processing. Here are all of the version numbers used in this tutorial:
roomLibraryVersion = "1.0.0-alpha1"
rxJavaVersion = "2.0.6"
rxAndroidVersion = "2.0.1"
You can get more information or the latest versions here.
Task Entity
An Entity is a class that represents a database row. In this application, we’ll have a table of Task
objects that the user has to complete, so we can annotate our class with the @Entity
annotation. We can also use annotations like @PrimaryKey
to define which property should be the primary key, and even autogenerate one if necessary:
@Entity
class Task() {
@PrimaryKey(autoGenerate = true) var id: Int = 0
var description: String = ""
var completed: Boolean = false
constructor(description: String, completed: Boolean = false): this() {
this.description = description
this.completed = completed
}
}
Task DAO
A DAO, or Database Access Object is an interface used to abstract access to the database. This is where you put all of your CRUD (Create, Read, Update, Delete) methods. Below is the code for our DAO, but please check the documentation for additional information:
@Dao
interface TaskDAO {
@Query("SELECT * FROM task")
fun getAll(): Flowable<List<Task>>
@Query("SELECT * FROM task WHERE completed = :arg0")
fun getTasksByCompletion(complete: Boolean): Flowable<List<Task>>
@Insert
fun insertAll(vararg tasks: Task)
@Update
fun update(task: Task)
@Delete
fun delete(task: Task)
}
Note: The getTasksByCompletion()
and delete()
methods aren’t actually used in this sample, but were added just for educational purposes.
From the above methods, the Query methods can use the Room RxJava2 integration to return Flowable
objects. A Flowable
is an RxJava component you can read about here.
AppDatabase
The last thing we need to create is our Database class. This is an abstract class extending from RoomDatabase which defines the entities used in this database, it’s version, and is the primary access point for the database.
In this example, I’ve also decided to use the class with the Singleton Pattern to get a database instance for the app to use:
@Database(entities = arrayOf(Task::class), version = 2)
abstract class AppDatabase : RoomDatabase() {
abstract fun taskDao(): TaskDAO
companion object {
private var INSTANCE: AppDatabase? = null
private set
fun getInMemoryDatabase(context: Context): AppDatabase {
if (INSTANCE == null) {
INSTANCE = Room.databaseBuilder(context,
AppDatabase::class.java, "todo-list")
.build()
}
return INSTANCE!!
}
}
}
For every DAO you create, you’ll need a corresponding abstract method for that DAO inside of this class. If you want to learn more about database migrations, which aren’t covered in this post, you can read about them here.
Accessing The Database - Notes
Now that we’ve created our AppDatabase, we can call AppDatabase.getInMemoryDatabase(context).taskDao()...
to perform database operations. However, Room does not allow you to access the database on the main thread as it could produce Application Not Responding (ANR) errors. Your choices are to move the code to a separate thread yourself, or if you insist you can use the allowOnMainThread() method in your builder.
I’ve also decided to make use of Kotlin’s extension methods, to create an extension method on a Context to easily access our database from an activity or view context seen later in the post:
fun Context.taskDao(): TaskDAO {
return AppDatabase.getInMemoryDatabase(this).taskDao()
}
Query
Now that we have our database setup, let’s first go over our implementation of the query calls. Since our queries return RxJava Flowables, we’ll use that object to subscribe on a new thread, observe on the main thread, and update the tasks in our RecyclerView.Adapter
when it’s done, like this:
taskDao().getAll()
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ adapter.tasks = it })
For the full Activity and Adapter code, please see GitHub.
Insert
To insert into the database, I’ve used an RxJava Single to run this action asynchronously:
Single.fromCallable { taskDao().insertAll(task) }
.subscribeOn(Schedulers.newThread())
.subscribe()
Update
This was a little tricky. I tried using a Single just like the last example, but I couldn’t quite get it to work. You can read more about the question and solution on StackOverflow, but here is the code inside of the ViewHolder:
class TaskViewHolder(view: View?, taskAdapter: TaskAdapter) : RecyclerView.ViewHolder(view) {
val adapter: WeakReference<TaskAdapter> = WeakReference(taskAdapter)
val descriptionTextView = view?.findViewById(R.id.task_description) as? TextView
val completedCheckBox = view?.findViewById(R.id.task_completed) as? CheckBox
private lateinit var emitter: ObservableEmitter<Task>
private val disposable: Disposable = Observable.create(ObservableOnSubscribe<Task> { e -> emitter = e })
.subscribeOn(Schedulers.newThread())
.observeOn(Schedulers.newThread())
.subscribe({ itemView.context.taskDao().update(it) })
fun bindTask(task: Task) {
descriptionTextView?.text = task.description
completedCheckBox?.isChecked = task.completed
completedCheckBox?.setOnCheckedChangeListener { _, isChecked ->
adapter.get()?.tasks?.get(adapterPosition)?.completed = isChecked
emitter.onNext(adapter.get()?.tasks?.get(adapterPosition))
}
}
}
That is all of the Room related code for this tutorial! You can find all of this on GitHub, but those of you familiar with SQLite on Android can already see how much simpler this was. We didn’t have to write any table contracts, define individual columns, write a pesky SQLiteOpenHelper, or any of that boilerplate work.