Android Room Persistence Library using Kotlin
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() } }
Amazingly good article for room in kotlin
Very good demo
Highly recommended!