Room Database

Android studio room database with kotlin and coroutine.

dependencies

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

plugins {
    id 'kotlin-kapt'
}
dependencies {
    def room_version = "2.4.3"
    def activityVersion = '1.3.1'

    implementation("androidx.room:room-runtime:$room_version")
    kapt("androidx.room:room-compiler:$room_version")
    implementation("androidx.room:room-ktx:$room_version")
    implementation "androidx.activity:activity-ktx:$activityVersion"
    implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1'
}

Primary components

There are three major components in Room:

  • The database class that holds the database and serves as the main access point for the underlying connection to your app's persisted data.
  • Data entities that represent tables in your app's database.
  • Data access objects (DAOs) that provide methods that your app can use to query, update, insert, and delete data in the database.

1: Data Entities

We define entities to represent the objects that we want to store.

By default, Room uses the class name as the database table name. If you want the table to have a different name, set the tableName property of the @Entity annotation.Similarly, Room uses the field names as column names in the database by default. If you want a column to have a different name, add the @ColumnInfo annotation to the field and set the name property.

The following code defines a User data entity. Each instance of User represents a row in a user table in the app's database.

@Entity
data class UserEntity(
    @PrimaryKey(autoGenerate = true)
    val id: Int = 0,
    val name: String = "",
    @ColumnInfo(name = "email_id")
    val email: String = ""
)
@Entity(tableName ="user-table")

Define a primary key

Each Room entity must define a primary key that uniquely identifies each row in the corresponding database table. The most straightforward way of doing this is to annotate a single column with **@PrimaryKey

@PrimaryKey val id: Int

// OR autogenerate it
@PrimaryKey(autoGenerate = true)
val id: Int = 0,

Ignore fields

@Ignore val picture: Bitmap?

2: Data access objects (DAOs)

The following code defines a DAO called UserDao. UserDao provides the methods that the rest of the app uses to interact with data in the user table.

@Dao
interface UserDao {
    @Insert
    suspend fun insert(userEntity: UserEntity)

    @Update
    suspend fun update(userEntity: UserEntity)

    @Delete
    suspend fun delete(userEntity: UserEntity)

    @Query("SELECT * FROM `user-table`")
    fun fetchAllEmployees():  Flow<List<UserEntity>>

    @Query("SELECT * FROM `user-table` Where id=:id")
    fun fetchEmployee(id:Int):  Flow<UserEntity>
}

Here we are using Flow to receive live updates from a database.

As a suspend function cannot return multiple consecutive values, we creates and returns a flow to fulfill this requirement.

Database class

The database class must satisfy the following conditions:

  • The class must be annotated with a @Database annotation that includes an entities array that lists all of the data entities associated with the database.
  • The class must be an abstract class that extends RoomDatabase.

For each DAO class that is associated with the database, the database class must define an abstract method that has zero arguments and returns an instance of the DAO class.

@Database(entities = [UserEntity::class], version = 1)
abstract class UserDatabase : RoomDatabase() {
    abstract fun userDao(): UserDao
}
// defining companion object to add functions to our DB
companion object {
    @Volatile
    private var INSTANCE : EmployeeDB? = null

    fun getInstance(context: Context) : EmployeeDB {
            var instance = INSTANCE
            if (instance==null) {
                instance= Room.databaseBuilder(
                    context.applicationContext,
                    UserDatabase::class.java,
                    "user_database"
                ).fallbackToDestructiveMigration().build()
                INSTANCE=instance
            }
            return instance
    }
}

Here we are first checking if our DB has already an instance. If yes than just return it.

Using in our app

override fun onCreate(savedInstanceState: Bundle?) {...

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

    lifecycleScope.launch {
        userDao.insert(UserEntity(name=name, email=email))
    }

Fetching the data and setting recyler view.

lifecycleScope.launch {
    userDao.fetchAllUsers().collect {
        val list = ArrayList(it)
        val itemAdapter = ItemAdapter(list)
        binding.rvItemsList.adapter = itemAdapter
    }
}

deleting and updating

 lifecycleScope.launch {
    userDao.delete(UserEntity(id))
    userDao.update(UserEntity(id,name,email))
}