Android Room using Kotlin

Android Room using Kotlin

Room is a part of the Android Architecture components which provides an abstraction layer over SQlite which allows for a more robust database acces while still providing the full power of SQlite.

Today we are going to discuss the basics of using the Room library and how it can enhance your workflow as a developer.

Room Basics

The Room library consists of 3 major components:

Entity:

The Entity represents a table within the database and has to be annotated with @Enity. Each Entity consist of a minimum of one field has to define a primary key.

DAO (Database Access Object):

In Room you use data access objects to access and manage your data. The DAO is the main component of Room and includes methodes that offer access to your apps database it has to be annotated with @Dao. DAOs are used instead of query builders and let you seperate differend components of your database e.g. current data and statistics, which allows you to easily test your database.

Database:

Serves as the database holder an is the main accespoint to your relational data. It has to be annotated with @Database and extents the RoomDatabase. It also containes and returns the Dao (Database Access Object).

Adding the needed dependencies

First, we need to add the needed dependencies that will enable us to use the Room library. We can do so with some simple lines in the build.gradle (Module:app) file

apply plugin: "kotlin-kapt"

dependencies {
    implementation "androidx.room:room-runtime:$room_version"
    implementation "androidx.legacy:legacy-support-v4:1.0.0"
    implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'
    implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.0.0'
    kapt "androidx.room:room-compiler:$room_version"
}

We also need to create a variable for the room version in our build.gradle (Project) file

buildscript {
  ext.room_version = '2.1.0-alpha01'
}

Entity

After importing the dependency we can start defining the Entity which represents a table in the database. In this example we just have a simple todo list item with a title and a content field.

@Entity
data class TodoEntity(
        @PrimaryKey var title: String,
        @ColumnInfo(name = "content") var content: String
)

Now you should start to relise that Room is based on annotations. Now let’s look on how you can autogenerate a Primarykey and change the Tablename.

@Entity(tableName = "todo_items")
data class TodoEntity(
        @PrimaryKey(autoGenerate = true)
        var id: Int,

        @ColumnInfo(name = "title") var title: String,
        @ColumnInfo(name = "content") var content: String
)

DAO

Next, we can start building our DAO which will contain our data queries.

@Dao
interface TodoDao {
    @Query("SELECT * FROM todoentity")
    fun getAll(): List<TodoEntity>

    @Query("SELECT * FROM todoentity WHERE title LIKE :title")
    fun findByTitle(title: String): TodoEntity

    @Insert
    fun insertAll(vararg todo: TodoEntity)

    @Delete
    fun delete(todo: TodoEntity)
    
    @Update
    fun updateTodo(vararg todos: TodoEntity)
}

Here we just define basic SQL database functionality like inserting and deleting entries. You can see that the @Query annotation is used to annotate functions which are using queries. You can also use parameters in your queries using :parametername as you can see in the findByTitle function.

You can also make your queries observable using LiveData as you can see in this example.

@Dao
interface TodoDao {
   @Query("SELECT * FROM todoentity WHERE title LIKE :title")
    fun findByTitle(title: String): LiveData<List<TodoEntity>>
}

Here, you are returning a LiveData Object holding a list of TodoEntries which you can observer in your activity.

Database

After that we can start writing the database which contains all your DAOs as abstract methods.

@Database(entities = arrayOf(TodoEntity::class), version = 1)
abstract class AppDatabase : RoomDatabase() {
    abstract fun todoDao(): TodoDao
}

Here we define the version of the database and the entity and DAO that we are using.

You can also use more than one entity and define a custome build and invoke methode as you can see in this example.

@Database(
    entities = [TodoEntity::class, TaskEntry::class],
    version = 1
)
abstract class AppDatabase : RoomDatabase(){
    abstract fun TodoDao(): TodoDao
    abstract fun TaskDao(): TaskDao

    companion object {
        @Volatile private var instance: AppDatabase? = null
        private val LOCK = Any()

        operator fun invoke(context: Context)= instance ?: synchronized(LOCK){
            instance ?: buildDatabase(context).also { instance = it}
        }

        private fun buildDatabase(context: Context) = Room.databaseBuilder(context,
                AppDatabase::class.java, "todo-list.db")
                .build()
    }
}

Accessing the Database

After defining the database we can get an instance in our activity using the Room.databaseBuilder() method.

val db = Room.databaseBuilder(
            applicationContext,
            AppDatabase::class.java, "todo-list.db"
).build()

We don’t need the Room.databaseBuilder() to get an instance of the second database example defined above. We just need to call the invoke() methode and pass the activity context.

val db = AppDatabase(this)

Now we can start using the database instance to access our DAO object.

GlobalScope.launch {
            db.todoDao().insertAll(TodoEntry("Title", "Content"))
            data = db.todoDao().getAll()

            data?.forEach {
                println(it)
            }
}

Database operations cannot run on the UI thread so we need to create another one. In this example we are using Kotlin coroutines to launch a thread for the database operations.

Testing your database

Now we can start testing our database to make sure that the read and write functionality is functioning correctly.

@RunWith(AndroidJUnit4::class)
class EnityReadWriteTest {
    private lateinit var todoDao: TodoDao
    private lateinit var db: AppDatabase

    @Before
    fun createDb() {
        val context = InstrumentationRegistry.getContext()
        db = Room.inMemoryDatabaseBuilder(
                context, AppDatabase::class.java).build()
        todoDao = db.todoDao()
    }

    @After
    @Throws(IOException::class)
    fun closeDb() {
        db.close()
    }

    @Test
    @Throws(Exception::class)
    fun writeUserAndReadInList() {
        val todo: TodoEntry = TodoEntry("title", "detail")
        todoDao.insertAll(todo)
        val todoItem = todoDao.findByTitle(todo.title)
        assertThat(todoItem, equalTo(todo))
    }
}

Here we insert an object into the database and read it and check if the two objects are the same.

Conclusion

Now you should know how the Room library works, how you can insert, update, get and delete entities in the database. You have also learned how to test your database using JUnit.

If you have found this useful, please consider recommending and sharing it with other fellow developers.

If you have any questions or critics, you can reach me in the comment section.

Read these next: