DEV Community

Cover image for Persistent SQLite Database on Android using Room library with Kotlin
Aayush Gupta
Aayush Gupta

Posted on • Updated on

Persistent SQLite Database on Android using Room library with Kotlin

Persistent storage is an important factor in apps. It allows storing data locally which persists regardless of the application lifecycle.

Room library provides an abstraction layer over SQLite which makes it easier to maintain and use SQLite database on Android. The room also provides compile-time verification of SQL queries, annotations support, and streamlined database migration paths which makes it much better. It is part of Android's Jetpack Framework and is recommended over SQLite library.

Dependencies

To use the Room library, add the following dependencies in your app module's build.gradle file.

dependencies {
    // Coroutines
    def coroutines_version = "1.4.2"
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"

    // ROOM
    def room_version = "2.2.6"
    kapt "androidx.room:room-compiler:$room_version"
    implementation "androidx.room:room-runtime:$room_version"
    implementation "androidx.room:room-ktx:$room_version"
}
Enter fullscreen mode Exit fullscreen mode

Also, enable support for annotations if you haven't done already:

plugins {
    id 'kotlin-kapt'
}
Enter fullscreen mode Exit fullscreen mode

and lastly, you need to enable the room library's incremental annotation support:

android {
    defaultConfig {
        javaCompileOptions {
            annotationProcessorOptions {
                arguments += [
                        "room.incremental":"true"]
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

You can check the complete list of arguments supported and their use cases on developer.android.com

Usage

To use the room library, I will create a new package that will store all the files related to it, named database. It will host 3 files namely:

  • User.kt which will contain the data class of the User object we will insert in the database,
  • UserDAO.kt which will contain an interface hosting our methods to perform over the database like insert, update, etc, and
  • UserDatabase.kt which will host an abstract class of the database with a companion object to make the instance singleton.

Database instance created must be a singleton as recommended by official documentation on developer.android.com

User Implementation

Create a new data class named User which will take some argument to store in the database. Annotate this class with @Entity annotation. If you wish, you can have an autogenerating id for the database using the @PrimaryKey(autoGenerate = true) annotation. I recommended this as it saves time and effort.

Here is how the class looks in my case:

package dev.theimpulson.roomexample.database

import androidx.room.Entity
import androidx.room.PrimaryKey

@Entity
data class User(
    val firstName: String,
    val lastName: String,
    val age: Int,
    @PrimaryKey(autoGenerate = true)
    val id: Int? = null
)
Enter fullscreen mode Exit fullscreen mode

UserDAO Implementation

DAO stands for Database Access Object. Create a new interface named UserDAO. Annotate this interface with @Dao annotation. This interface will hold functions that we want to perform on the database for example inserting, deleting, updating, querying an object. Annotate the same function with annotations as the job it performs.

One important thing to note is that these functions should be marked as suspend in order to ensure that they are only run inside a coroutine thread otherwise room will throw errors at runtime regarding UI blocking code.

Here is how UserDAO.kt looks in my case:

package dev.theimpulson.roomexample.database

import androidx.room.*

@Dao
interface UserDAO {

    @Insert
    suspend fun insert(user: User)

    @Query("SELECT * FROM user")
    suspend fun queryAll(): List<User>

    @Update
    suspend fun update(user: User)

    @Delete
    suspend fun delete(user: User)
}
Enter fullscreen mode Exit fullscreen mode

Query annotation requires a value argument which must be an SQL command to query on the database.

UserDatabase Implementation

Create a new abstract class named UserDatabase which extends to RoomDatabase while instantiating it. This class should be annotated with @Database annotation with arguments:

  • entities which takes a list of the entity,
  • version which is the database version, and
  • exportSchema that enables the version history of your schema in your codebase.

The class must have an abstract function of the same type as your DAO.

Create a new companion object in this class which will hold a variable of the type of the class set to null, 2 functions to get and destroy the instance of the class.

To get an instance of the database, we will use Room.databaseBuilder() which requires 3 arguments namely context, current class, and name of the database. We also need to call the build() method over it to actually build an instance of the database.

Here is how the UserDatabase.kt looks in my case:

package dev.theimpulson.roomexample.database

import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase

@Database(entities = [User::class], version = 1, exportSchema = false)
abstract class UserDatabase: RoomDatabase() {
    abstract fun userDao(): UserDAO

    companion object {
        private var INSTANCE: UserDatabase? = null

        fun getInstance(context: Context): UserDatabase {
            return if (INSTANCE == null) {
                Room.databaseBuilder(context, UserDatabase::class.java, "user_database").build()
            } else {
                INSTANCE!!
            }
        }

        fun destroyInstance() {
            INSTANCE = null
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Using the Database

To use the database, simply create an instance of the database and call the functions in a coroutines scope. You can log the response of the query function to see the changes in the database as well.

I will be performing these tasks in my MainActivity.kt file for example. Here is how it looks:

package dev.theimpulson.roomexample

import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import dev.theimpulson.roomexample.database.User
import dev.theimpulson.roomexample.database.UserDatabase
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch

class MainActivity : AppCompatActivity() {
    private var TAG = "MainActivity"

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val db = UserDatabase.getInstance(this).userDao()

        GlobalScope.launch {
            db.insert(User("Tom", "Clancy", 30))
            val fetch = db.queryAll()
            Log.d(TAG, fetch.toString())
        }

        GlobalScope.launch {
            db.update(User(firstName = "Jack", lastName = "Ryan", age = 25, id = 4))
            val fetch = db.queryAll()
            Log.d(TAG, fetch.toString())
        }

        GlobalScope.launch {
            db.delete(User(firstName = "Aayush", lastName = "Gupta", age = 22, id = 1))
            val fetch = db.queryAll()
            Log.d(TAG, fetch.toString())
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

and that's it. You can now use the room library to easily work with SQLite database on Android without writing a lot of boilerplate code with advantage of compile-time verifications.

Top comments (0)