Android Room Persistence Library using Kotlin

In this article, you can learn how to use android room persistence library in kotlin language. You can also check out sample app at HERE.

The android room persistence library provides an abstraction layer on SQLite to allow it to use more robust databases using the full power of SQLite. This library helps you create a cache of your app’s data on an app that is running your app. This cache, which serves as your app’s single source of truth, allows users to view a coherent copy of important information within your app, even if users have an Internet connection.

In this example, I am using data binding to bind and display data. If you are new to data binding then you can refer this article.

 

Add the below dependencies in your app level build.gradle file

apply plugin: 'kotlin-kapt'

dependencies {
    implementation "androidx.room:room-runtime:2.2.4"
    kapt "androidx.room:room-compiler:2.2.4"
    implementation 'com.android.support:recyclerview-v7:28.1.1'
    implementation 'com.android.support:cardview-v7:28.1.1'
}

 

Create a Entity class which represents a table in the database

A table is created for each entity in database. We also pass entity reference in database class through entities array.

package com.example.roomsample.models

import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import java.io.Serializable

@Entity(tableName = "Employees")
data class Employee(
    @ColumnInfo(name = "emp_name") var name: String,
    @ColumnInfo(name = "emp_salary") var salary:String,
    @ColumnInfo(name = "emp_age") var age:String)
    : Serializable {
    @PrimaryKey(autoGenerate = true) var id: Int = 0
}
 
 

Create a DAO interface which contains all queries

DAO means data access object. DAO provides different functions which have access to our app’s database. DAO saves time by eliminating boiler plate code to perform various database operations.

package com.example.roomsample.db

import androidx.room.*
import com.example.roomsample.models.Employee

@Dao
interface EmployeeDAO {

    @Insert
    fun addEmployee(employee: Employee) : Long

    @Update
    fun updateEmployee(employee: Employee)

    @Query("select * from Employees WHERE id LIKE :emp_id")
    fun getEmployee(emp_id: Int) : Employee

    @Query("select * from Employees")
    fun getAllEmployees() : MutableList<Employee>

    @Delete
    fun deleteEmployee(employee: Employee)

    @Query("DELETE FROM Employees")
    fun deleteAllEmployees()

}
 
 

Create a Database class which contains all your DAOs

Now we are creating abstract class APPDataBase that extends RoomDatabase class. @Database annotation defines associated tables array using entities key and database version.

package com.example.roomsample.db

import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import com.example.roomsample.models.Employee

@Database(entities = [Employee::class],version = 1)
abstract class AppDataBase : RoomDatabase() {

    abstract fun getDAO() : EmployeeDAO

    companion object{
        @Volatile
        private var INSTANCE: AppDataBase? = null

        fun getDatabase(ctx: Context) : AppDataBase{
            val tempInstance = INSTANCE
            if (tempInstance != null) {
                return tempInstance
            }
            synchronized(this){
                val instance = Room.databaseBuilder(
                    ctx.applicationContext,
                    AppDataBase::class.java,
                    "emp_database.db"
                ).allowMainThreadQueries().build()
                INSTANCE = instance
                return instance
            }
        }

    }

}
 
 

Create an activity layout file

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>

    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

        <androidx.appcompat.widget.Toolbar
            android:id="@+id/app_bar"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            android:background="@color/colorPrimary" />

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/recycler_main"
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:paddingTop="15dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/app_bar" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>
 
 

Create dialog layout for add or update item

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:padding="20dp">

    <EditText
        android:id="@+id/edt_name"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:hint="Please enter name"/>

    <EditText
        android:id="@+id/edt_salary"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/edt_name"
        android:layout_marginTop="15dp"
        android:hint="Please enter salary"/>

    <EditText
        android:id="@+id/edt_age"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/edt_salary"
        android:layout_marginTop="15dp"
        android:hint="Please enter age"/>

    <Button
        android:id="@+id/btn_add"
        android:layout_width="100dp"
        android:layout_height="50dp"
        android:text="Submit"
        app:layout_constraintTop_toBottomOf="@+id/edt_age"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        android:layout_marginTop="30dp"/>

</androidx.constraintlayout.widget.ConstraintLayout>
 
 

Create dialog for add or update item

We are displaying dialog to add or edit employee details. Async tasks are used to add or update employee details.

package com.example.roomsample.ui

import android.app.Dialog
import android.content.Context
import android.text.TextUtils
import android.view.ViewGroup
import android.view.Window
import android.widget.Button
import android.widget.EditText
import android.widget.Toast
import com.example.roomsample.R
import com.example.roomsample.async.InsertEmployeeAsyncTask
import com.example.roomsample.async.UpdateEmployeeAsyncTask
import com.example.roomsample.db.EmployeeDAO
import com.example.roomsample.models.Employee


object AddDialog {

    fun showDialog(
        employeeDAO: EmployeeDAO,
        context: Context,
        isEdit: Boolean,
        oldEmployee: Employee?,
        empPos: Int
    ) {
        val dialog = Dialog(context)
        dialog.setTitle("Add Employee")
        dialog.setCancelable(false)
        dialog.setContentView(R.layout.employee_add_dialog)
        val edtName = dialog.findViewById(R.id.edt_name) as EditText
        val edtSalary = dialog.findViewById(R.id.edt_salary) as EditText
        val edtAge = dialog.findViewById(R.id.edt_age) as EditText

        if (isEdit) {
            edtName.setText(oldEmployee?.name)
            edtSalary.setText(oldEmployee?.salary)
            edtAge.setText(oldEmployee?.age)
        }

        val submit = dialog.findViewById(R.id.btn_add) as Button

        submit.setOnClickListener {
            val name = edtName.text.toString()
            val salary = edtSalary.text.toString()
            val age = edtAge.text.toString()
            if (!TextUtils.isEmpty(name) || !TextUtils.isEmpty(salary) || !TextUtils.isEmpty(age)) {
                val newEmployee = Employee(name, salary, age)
                if (isEdit) {
                    newEmployee.id = oldEmployee?.id!!
                    UpdateEmployeeAsyncTask(context, employeeDAO, newEmployee, empPos, dialog).execute()
                } else {
                    InsertEmployeeAsyncTask(context, employeeDAO, newEmployee, dialog).execute()
                }
            } else {
                Toast.makeText(context, "All Fields Required", Toast.LENGTH_LONG).show()
            }

        }
        dialog.show()
        val window: Window = dialog.window
        window.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
    }

}
 
 

Create layout of RecyclerView adapter

We are using data binding to display employee info in recycler view. We need to give entity class info in variable tag to display data.

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>
        <variable
            name="Employee"
            type="com.example.roomsample.models.Employee" />
    </data>

    <androidx.cardview.widget.CardView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="15dp"
        app:cardCornerRadius="10dp">

        <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="15dp">

            <TextView
                android:id="@+id/txt_employee_name"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@{Employee.name}"
                app:layout_constraintTop_toTopOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                android:textSize="25sp"
                android:textStyle="bold"/>

            <TextView
                android:id="@+id/txt_employee_info"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@{`Salary: ` + Employee.salary + ` | ` + `Age: ` + Employee.age}"
                app:layout_constraintTop_toBottomOf="@+id/txt_employee_name"
                app:layout_constraintStart_toStartOf="parent"
                android:textSize="20sp"/>

            <ImageView
                android:id="@+id/img_delete"
                android:layout_width="20dp"
                android:layout_height="20dp"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintTop_toTopOf="parent"
                app:layout_constraintBottom_toBottomOf="parent"
                android:src="@android:drawable/ic_menu_delete" />

            <ImageView
                android:id="@+id/img_edit"
                android:layout_width="20dp"
                android:layout_height="20dp"
                app:layout_constraintTop_toTopOf="parent"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintEnd_toStartOf="@+id/img_delete"
                android:layout_marginEnd="15dp"
                android:src="@android:drawable/ic_menu_edit"/>

        </androidx.constraintlayout.widget.ConstraintLayout>

    </androidx.cardview.widget.CardView>

</layout>
 
 

Create an adapter class

Now we are creating adapter class to display employee info in a row.  There are two options in this row layout: edit and delete employee.  We are using async tasks to perform database operations in background to avoid any memory leak.

package com.example.roomsample.adapters

import android.content.Context
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import androidx.recyclerview.widget.RecyclerView
import com.example.roomsample.R
import com.example.roomsample.async.DeleteEmployeeAsyncTask
import com.example.roomsample.databinding.EmployeeRowBinding
import com.example.roomsample.db.EmployeeDAO
import com.example.roomsample.models.Employee
import com.example.roomsample.ui.AddDialog

class EmployeeAdapter(private val context: Context, private val employeeDAO: EmployeeDAO, private val list: MutableList<Employee>?) : RecyclerView.Adapter<EmployeeAdapter.MyViewHolder>() {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
        val inflater = LayoutInflater.from(context)
        val binding: EmployeeRowBinding = DataBindingUtil.inflate(inflater, R.layout.employee_row,parent,false)
        return MyViewHolder(binding)
    }

    override fun getItemCount(): Int {
        if(list == null){
            return 0
        }
        return list.size
    }

    override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
        val getEmployee = list?.get(position)
        holder.itemBinding.employee = getEmployee

        holder.itemBinding.imgEdit.setOnClickListener {
            AddDialog.showDialog(employeeDAO,context,true,getEmployee,position)
        }

        holder.itemBinding.imgDelete.setOnClickListener {
            getEmployee?.let { it1 -> DeleteEmployeeAsyncTask(context,employeeDAO, it1).execute() }
        }

    }

    class MyViewHolder(val itemBinding: EmployeeRowBinding) : RecyclerView.ViewHolder(itemBinding.root){

        private var binding : EmployeeRowBinding? = null

        init {
            this.binding = itemBinding
        }

    }

}
 
 

Define Asynctasks to perform database tasks in background

package com.example.roomsample.async

import android.app.Dialog
import android.content.Context
import android.os.AsyncTask
import android.widget.Toast
import com.example.roomsample.MainActivity
import com.example.roomsample.db.EmployeeDAO
import com.example.roomsample.models.Employee

class InsertEmployeeAsyncTask(
    var context: Context,
    var empDao: EmployeeDAO,
    var employee: Employee,
    val dialog: Dialog
) :
    AsyncTask<Void, Void?, Boolean?>() {

    var empId = 0L

    override fun doInBackground(vararg params: Void?): Boolean {
        empId = empDao.addEmployee(employee)
        return true
    }

    override fun onPostExecute(result: Boolean?) {
        if (result!!) {
            Toast.makeText(context, "Added to Database", Toast.LENGTH_LONG).show()
            val activity = context as MainActivity
            employee.id = empId.toInt()
            activity.addEmployee(employee)
            dialog.dismiss()
        }
    }

}

package com.example.roomsample.async

import android.app.Dialog
import android.content.Context
import android.os.AsyncTask
import android.widget.Toast
import com.example.roomsample.MainActivity
import com.example.roomsample.db.EmployeeDAO
import com.example.roomsample.models.Employee

class UpdateEmployeeAsyncTask(var context: Context, var empDao: EmployeeDAO, var employee: Employee, var pos: Int, val dialog: Dialog) :
    AsyncTask<Void, Void?, Boolean?>() {

    override fun doInBackground(vararg params: Void?): Boolean {
        empDao.updateEmployee(employee)
        return true
    }

    override fun onPostExecute(result: Boolean?) {
        if (result!!) {
            Toast.makeText(context, "Updated to Database", Toast.LENGTH_LONG).show()
            val activity = context as MainActivity
            activity.updateEmployee(employee,pos)
            dialog.dismiss()
        }
    }

}

package com.example.roomsample.async

import android.content.Context
import android.os.AsyncTask
import android.widget.Toast
import com.example.roomsample.MainActivity
import com.example.roomsample.adapters.EmployeeAdapter
import com.example.roomsample.db.EmployeeDAO
import com.example.roomsample.models.Employee

class GetAllEmployeesAsyncTask(var context: Context, var empDao: EmployeeDAO) :
    AsyncTask<Void, Void?, Boolean?>() {

    private var employeesList: MutableList<Employee> = mutableListOf()

    override fun doInBackground(vararg params: Void?): Boolean {
        employeesList = empDao.getAllEmployees()
        return true
    }

    override fun onPostExecute(result: Boolean?) {
        if (result!!) {
            Toast.makeText(context, "Get employees from Database", Toast.LENGTH_LONG).show()
            val activity = context as MainActivity
            activity.getAllEmployees(employeesList)
        }
    }

}

package com.example.roomsample.async

import android.content.Context
import android.os.AsyncTask
import android.widget.Toast
import com.example.roomsample.MainActivity
import com.example.roomsample.db.EmployeeDAO
import com.example.roomsample.models.Employee

class DeleteEmployeeAsyncTask(var context: Context, var empDao: EmployeeDAO, var employee: Employee) :
    AsyncTask<Void, Void?, Boolean?>() {

    override fun doInBackground(vararg params: Void?): Boolean {
        empDao.deleteEmployee(employee)
        return true
    }

    override fun onPostExecute(result: Boolean?) {
        if (result!!) {
            Toast.makeText(context, "Deleted from Database", Toast.LENGTH_LONG).show()
            val activity = context as MainActivity
            activity.removeEmployee(employee)
        }
    }

}
 
 

Define database functions in Activity

package com.example.roomsample

import android.app.Dialog
import android.os.Bundle
import android.text.TextUtils
import android.view.Menu
import android.view.MenuItem
import android.widget.Button
import android.widget.EditText
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import androidx.recyclerview.widget.LinearLayoutManager
import com.example.roomsample.adapters.EmployeeAdapter
import com.example.roomsample.async.GetAllEmployeesAsyncTask
import com.example.roomsample.databinding.ActivityMainBinding
import com.example.roomsample.db.AppDataBase
import com.example.roomsample.db.EmployeeDAO
import com.example.roomsample.models.Employee
import com.example.roomsample.ui.AddDialog
import kotlinx.android.synthetic.main.activity_main.*


class MainActivity : AppCompatActivity() {

    private lateinit var employeeDAO: EmployeeDAO
    private lateinit var employeesList: MutableList<Employee>
    private lateinit var adapter: EmployeeAdapter

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

        val binding: ActivityMainBinding =
            DataBindingUtil.setContentView(this@MainActivity, R.layout.activity_main)
        binding.recyclerMain.layoutManager = LinearLayoutManager(this@MainActivity)

        setSupportActionBar(app_bar)

        employeeDAO = AppDataBase.getDatabase(this).getDAO()

        employeesList = mutableListOf()

        adapter = EmployeeAdapter(
            this, employeeDAO,
            employeesList
        )
        binding.recyclerMain.adapter = adapter

        GetAllEmployeesAsyncTask(this, employeeDAO).execute()
    }

    override fun onCreateOptionsMenu(menu: Menu?): Boolean {
        menuInflater.inflate(R.menu.menu_main, menu)
        return true
    }

    override fun onOptionsItemSelected(item: MenuItem?): Boolean {
        val id = item!!.itemId

        if (id == R.id.action_add) {
            AddDialog.showDialog(employeeDAO, this, false, null,0)
            return true
        }

        return super.onOptionsItemSelected(item)
    }

    fun getAllEmployees(list: MutableList<Employee>) {
        employeesList.addAll(list)
        adapter.notifyDataSetChanged()
    }

    fun addEmployee(employee: Employee) {
        employeesList.add(employee)
        adapter.notifyDataSetChanged()
    }

    fun updateEmployee(employee: Employee,pos: Int) {
        employeesList.set(pos, employee)
        adapter.notifyDataSetChanged()
    }

    fun removeEmployee(employee: Employee) {
        employeesList.remove(employee)
        adapter.notifyDataSetChanged()
    }

}
 

android room
 
 
android room
 
 



3 thoughts on “Android Room Persistence Library using Kotlin

Leave a Reply