Android Room Persistence Library in Kotlin

Welcome, here we are going to learn about the latest room in android. We are also going to use some of the newest android libraries like lifecycle.

In the earlier chapter, we have seen the implementation of SQLite Database which is a bit difficult task. But, to use the SQLite in the more natural way Google has provided us with a library called ROOM. This library is an abstraction layer over the SQLite Database. The Room is one of the components of Android Architecture Component.

First, the terminologies; there are three significant annotations for using room:

  • “@Entity”
  • “@Dao”
  • “@Database”

a. “@Entity” Representation of table and columns become very easy; you have to annotate “@Entity” to a class and name of the class becomes table name and, data members becomes the name of the columns. “@Entity” class represent an entity in a table.

Example:

@Entity(tableName = “habitClass”)
data class Habit(@PrimaryKey @ColumnInfo(name = “habit”) 
val mHabit: String)

Here, we have class Habit, and the name of the table is habitClass. We had made a single column habit, and for that, we used “@ColumnInfo” annotation and also made this a primary key by annotating “@PrimaryKey” to it.

b. “@Dao” — Data Access Object
An Interface where we put all our SQL queries. We don’t require to write whole queries now; we need to make a method and annotate with specific annotations like “@Insert”, “@Delete”, “@Query(SELECT FROM *)

@Dao
interface HabitDao {  
@Insert 
fun insert(habit: Habits)  
@Query("DELETE FROM habitClass") 
fun deleteAll()  
@Query("SELECT * FROM habitClass ORDER BY habit ASC" ) 
fun getAllHabits() : List<Habits>
}

Here, we have an interface HabitDao and some methods which we will call to perform our queries. To insert the data we annotated “@Insert” to insert method. The room doesn’t give us annotations which can help us in deleting everything, so we have “@Query” to do some custom queries.

c. “@Database”
We need to create an abstract class (Room class) which extends RoomDatabase. It is a database layer over the SQLite database; this helps us in all the work which we use to do in SQLiteOpenHelper class. Room helps us with the compile-time checking of SQL statements; we need only a single instance for the whole app.

@Database(entities = arrayOf(Habit::class), version = 1)
abstract class HabitRoomDatabase : RoomDatabase{
abstract fun wordDao(): HabitDao
}

Here, we have a Room class HabitRoomDatabase in which we had to declare all our entities and version of the database. getDatabase() method will return the room database instance. If you want to modify the database do have a look at Migration.

Firstly, add room dependencies.

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation 'androidx.appcompat:appcompat:1.0.2'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    // Room dependencies
    implementation 'androidx.room:room-runtime:2.0.0'
    kapt 'androidx.room:room-compiler:2.0.0'
}

also, add ‘kapt’ plugin at the top of your build.gradle file

apply plugin: 'kotlin-kapt'

Let’s start with the Entity creation

@Entity(tableName = "MindOrksDb")
data class Chapter(
    @PrimaryKey
    @ColumnInfo(name = "chapterName") val chapterName: String
)

Now, we will create the DAO, database access object, it is an interface where we define all methods use to access database

@Dao
interface ChapterDao {
    @Insert
    fun insert(chapter: Chapter)
   @Query("SELECT * FROM MindOrksDb ORDER BY chapterName ASC")
    fun getAllChapter(): List<Chapter>
}

Now, we are ready to create the database. We have table info in Entity and CRUD methods in DAO interface.

@Database(entities = arrayOf(Chapter::class), version = 1)
abstract class ChapterDatabase : RoomDatabase() {
   abstract fun chapterDao(): ChapterDao
   companion object {
        private var INSTANCE: ChapterDatabase? = null
       fun getDatabase(context: Context): ChapterDatabase? {
            if (INSTANCE == null) {
                synchronized(ChapterDatabase::class) {
                    INSTANCE = Room.databaseBuilder(
                        context.getApplicationContext(),
                        ChapterDatabase::class.java, "chapter.db"
                    ).build()
                }
            }
            return INSTANCE
        }
    }
}

Let’s start with our MainActivity where user interaction has to be done, for that we need to create our main screen, activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
   <EditText
        android:id="@+id/etEnterText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="16dp"
        android:hint="Enter here"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
   <Button
        android:id="@+id/btnSave"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="16dp"
        android:text="Save to Database"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@+id/btnDisplay"
        app:layout_constraintTop_toBottomOf="@+id/etEnterText" />
   <Button
        android:id="@+id/btnDisplay"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:text="Display"
        app:layout_constraintEnd_toStartOf="@+id/btnSave"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/etEnterText" />
   <TextView
        android:id="@+id/tvDatafromDb"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/btnSave" />
</androidx.constraintlayout.widget.ConstraintLayout>

Now, In our MainActivity, let’s fill our database and get the data out of it

class MainActivity : AppCompatActivity() {
   private var chapterDatabase: ChapterDatabase? = null
   override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        chapterDatabase = ChapterDatabase.getDatabase(this)!!
       btnSave.setOnClickListener {
            if (!etEnterText.text.toString().isEmpty()) {
                val chapterObj = Chapter(etEnterText.text.toString())
                InsertTask(this, chapterObj).execute()
            }
        }
        btnDisplay.setOnClickListener {
            GetDataFromDb(this).execute()
        }
    }
   private class InsertTask(var context: MainActivity, var chapter: Chapter) : AsyncTask<Void, Void, Boolean>() {
       override fun doInBackground(vararg params: Void?): Boolean {
            context.chapterDatabase!!.chapterDao().insert(chapter)
            return true
        }
       override fun onPostExecute(bool: Boolean?) {
            if (bool!!) {
                Toast.makeText(context, "Added to Database", Toast.LENGTH_LONG).show()
            }
        }
    }
   private class GetDataFromDb(var context: MainActivity) : AsyncTask<Void, Void, List<Chapter>>() {
       override fun doInBackground(vararg params: Void?): List<Chapter> {
            return context.chapterDatabase!!.chapterDao().getAllChapter()
        }
       override fun onPostExecute(chapterList: List<Chapter>?) {
            if (chapterList!!.size > 0) {
                for (i in 0..chapterList.size - 1) {
                    context.tvDatafromDb.append(chapterList[i].chapterName)
                }
            }
        }
    }
}

Here, we had used AsyncTask which is an Android inbuilt class used to do some operation on the background thread. We will be taking this in more detail in upcoming chapters.

Let’s run this application now and add some data to the database. We had finally implemented an application using Room database library.

In the next chapter, we will be searching our data from the database.