Android Room Persistence Example
Today we’re gonna look out a new Android Architecture Component library called Room. The room is basically a “Persistence library that provides an abstraction over SQLite“. The room is a new way to create the database in your Android apps. Although the Android framework provides built-in support for SQLite database, there are many drawbacks working with SQLite.
Drawbacks Of SQLite
Before the start, I want to clarify the drawbacks of SQLite database.
- You have to write a lot of boilerplate code.
- You have to implement object mapping for every query you write.
- Difficult to implement database migrations.
- Database operation on the main thread.
Components of Room
Now there are three main components for creating the database with the room.
- Entity: Entity is an annotated class. This annotation is used to create the database table. After adding Entity annotation room will take care of creating the database table for you.
- Dao: Dao is also an annotated class. To access the data from the database you used Dao (Data Access Object). In Dao interface, you declare all the methods needed to work with the database.
- Database: Database is an annotated class. In database class, we define all of our Entity class and tell the version of the database.
Android App Coding
So, enough of this theory let’s build a real-life scenario app. We’re going to create a Book Library app. In this app, the user adds a book name and select the who is the author then insert the book along with author name into the database.
Alright first create a new project in Android Studio with empty activity. Now add the Room dependency into a build.gradle file.
// Room database dependencies implementation 'android.arch.persistence.room:runtime:1.1.0' // Use current library version kapt 'android.arch.persistence.room:compiler:$versions.1.1.0' // If language is kotlin remain same else use annotationProcessor instead of kapt
Note: If you’re using Kotlin language please add kapt annotation plugin at the top of a build.gradle file.
Now let’s create our first component which is Entity. In our example, we need two Entity classes one for Author of a book and one for Book itself. It means we have two tables in our database.
The following shows how to create our first Entity of Author class.
AuthorModel
@Entity(tableName = "authors") data class AuthorModel(@PrimaryKey(autoGenerate = true) @ColumnInfo(name = "author_id") val authorId: Long = 0, @ColumnInfo(name = "author_name") var authorName: String)
For Entity, we add the annotation to our class and tell the name of a table. Now in every Entity class, one Primary Key is necessary. In order to tell which value is the primary key annotate with @PrimaryKey. The @ColumnName is for describing the name of table values. The @ColumnName is not necessary for every field if the not defined room will use the variable name when creating the table.
Below is another Entity for BookItem class.
BookItem
@Entity(tableName = "books" , indices = [(Index(value = ["book_id"], name = "idx_books_book_id"))], foreignKeys = [(ForeignKey( entity = AuthorModel::class , childColumns = ["author_id"] , onUpdate = ForeignKey.CASCADE , onDelete = ForeignKey.CASCADE))] ) data class BookItem(@PrimaryKey @ColumnInfo(name = "author_id") val authorId: Long, @ColumnInfo(name = "book_id") val bookId: Long, val name: String)
Now, this Entity class is complicated than the previous one. In BookItem we use a Foreign Key relation with the Author class table. You see every book has an author that’s why we need the authorId to specify who is the author of the book.
We create database tables right, how do we access the data. For accessing the data here comes the Dao’s annotation. For creating a Dao you need to create an interface and annotate with Dao and declares all the methods needed to work with the database. There are four annotations when declaring the methods in Dao’s.
- Insert: Insert for inserting the data.
- Update: For updating the data.
- Delete: For deleting the data.
- Query: This annotation is for SQL statement and this checked at compile time. If the query has an error it will tell you at compile time instead of when executing the query.
Note: All of these queries is Synchronous meaning that they will be run on the same thread you’re triggering from. If that’s Main Thread your app will crash IllegalStateException. So, the best approach is that you’ve to run these queries from Background Thread and get the result in Main Thread. For this, you can use RxJava and Kotlin Co-routines.
Now let’s see how we can create a basic Dao interface.
@Dao interface BaseDao<in T> { @Insert(onConflict = OnConflictStrategy.REPLACE) fun insert(t: T): Long @Delete fun delete(type : T) @Update fun update(type : T) }
See every database table has some base methods like insert, delete, update etc. So, I create a generic Dao interface and we just need to extend this BaseDao interface.
Below is the AuthorDao interface.
AuthorDao
@Dao interface AuthorDao : BaseDao<AuthorModel> { @Query(value = "SELECT * FROM authors") fun getAllAuthors(): List<AuthorModel> @Query(value = "SELECT * FROM authors WHERE author_id = :authorId") fun getAuthorWithId(authorId: Long): AuthorModel? @Query(value = "SELECT author_id FROM authors") fun getAllIds(): List<Long> }
You see with Query annotation we’re providing a SQL query and Room will be able to provide the data according to SQL query.
Below is the BookDao interface.
BookDao
@Dao interface BookDao : BaseDao<BookItem> { @Query(value = "SELECT * FROM books") fun getAllBooks(): List<BookItem> @Query(value = "SELECT * FROM books WHERE author_id = :authorId") fun getBooksWithAuthorId(authorId: Long): List<BookItem> }
The class, that’s put the Entities and Dao’s together is the RoomDatabase. In the database class, we define all the Entities and the Version of the database.
Below show’s how to create RoomDatabase class.
BookLibraryDatabase
@Database(entities = [AuthorModel::class, BookItem::class], version = 1) abstract class BookLibraryDatabase : RoomDatabase() { abstract fun authorDao(): AuthorDao abstract fun bookDao(): BookDao }
Everything is done for creating a RoomDatabase. It’s time to see how can we create RoomLibraryDatabase instance and get the Dao’s object.
Below shows how to create RoomLibraryDatabase instance.
val database = Room.databaseBuilder(context, BookLibraryDatabase::class.java, DATABASE_NAME).build()
So, we have a database object. Now It’s time to use all of the database utility methods that we write in Dao class.
// For inserting the author Observable.just(database.authorDao().insert(AuthorModel(authorName = "Ahsen Saeed"))) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe({// Author inserted},{it.printStacktrace()}) // Author with Id Observable.just(database.authorDao().getAuthorWithId(id)) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe({System.out.println(it)},{it.printStacktrace()}) // Get all authors Observable.just(database.authorDao().getAllAuthors()) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe({ // Here you all authors},{it.printStacktrace()}) // Get books of authors with id Observable.just(database.bookDao().getBooksWithAuthorId(id)) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe({ // Here you have all books with author id},{it.printStacktrace()}) // Delete the book Observable.just(database.bookDao().delete(book)) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe({ // Book deleted successfully},{it.printStacktrace()}) // Similarly you can perform all the utility the functions
You guys must be remembered that we need to call these methods from Background Thread and get the result on the Main Thread.
So that’s it, I’m going to end this blog here. I hope you guys, have learned something from this post. If you’ve any queries regarding Room persistence, please do comment below.
I have also written some articles on Room Persistence with RxJava2 and Room Persistence with LiveData.
Thank you for being here and keep reading…
I think the code: childColumns = [“book_id”]
should be childColumns = [“author_id”]
To insert an author and book into database, it only works if I make the change above.
Hey Rowan,
Thanks for suggestion, I update my code.