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
Usage
- 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 )
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@Update suspend fun update(user: User) @Delete suspend fun delete(user: User) >
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 >> >
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, val fetch = db.queryAll() Log.d(TAG, fetch.toString()) >GlobalScope.launch < db.delete(User(firstName = "Aayush", lastName = "Gupta", age = 22, val fetch = db.queryAll() Log.d(TAG, fetch.toString()) >> >
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.