Android Architecture Components: Working with Room Persistence Library

 


What is Android Architecture Components?

> Architecture components are a set of Android libraries that help you structure your app in a way that is robust, testable, and maintainable.

What is Room Persistence Library?

Google introduce Room Persistence Library in Google I/O 2017. Room provides an abstraction layer over SQLite to allow fluent database access while harnessing the full power of SQLite. There are several problems we faced when we used core framework with SQLite.

1). No compile time validation of query against the schema.so that broken SQL queries result in compile time errors, instead of runtime failures.

2). When database contains large amount of data it generates issues that can negatively impact the performance of apps, such as accessing persistent storage from the main thread.

3). As your schema changes, you need to update the affected SQL queries manually. This process can be time consuming and error prone.

New Room Persistence Library takes care of these concerns for you.

There are 3 major components in Room Persistence Library:

  • Database: Contains the database holder and serves as the main access point for the underlying connection to your app’s persisted, relational data.
  • Entity: Represents a table within the database.
  • DAO: Contains the methods used for accessing the database.

 

android room architecture

 

Some important Annotations in Room Persistence Library

  • @Entity : Create a SQLite table in database using a data model class.
  • @Dao : Create a Data Access Object in the Database using a interface class.
  • @Database : A class with this annotation will create an abstraction for the Data Access Object.
  • @PrimaryKey : A variable with this annotation will set a primary key for the table
  • @Insert : Inserts parameters into the table
  • @Update : Updates parameters of an existing table.
  • @Delete : Deletes parameters of an existing table.
  • @Query : Running SQL query method within the table.
  • @Ignore : Ignores the parameter from the Room database.

 

Now Let’s start with the example

Implementation


How to integrate Room Persistence Library into Android Project?

Step 1:

 Add the gradle dependencies in the build.gradle file.

dependencies {
    def room_version = "1.1.1"
    implementation "android.arch.persistence.room:runtime:$room_version"
    kapt "android.arch.persistence.room:compiler:$room_version" //use kapt if you are using kotlin otherwise use implementation
    // Test helpers
    testImplementation "android.arch.persistence.room:testing:$room_version"
}

 

Add “kotlin-kapt” plugin on the top of the build.gradle if you are going to use kotlin language.

apply plugin: 'kotlin-kapt'

Step 2:

Now we are going to make User Table Using Entity Annotation.

package com.android4dev.roomjetpack.db.entity

import android.arch.persistence.room.Entity
import android.arch.persistence.room.ColumnInfo
import android.arch.persistence.room.PrimaryKey

/***
 * Android4Dev
 */
@Entity(tableName = "user")
class User(
        //For autoincrement primary key
        @PrimaryKey(autoGenerate = true)
        var uid: Int = 0,

        @ColumnInfo(name = "first_name")
        var firstName: String? = null,


        @ColumnInfo(name = "last_name")
        var lastName: String? = null

) {
    constructor() : this(0, "", "")
}

 

Step 3:

Now create Data Access Object using interface. We can use @Query@Insert@Update@Delete annotation for CRUD Operation.

package com.android4dev.roomjetpack.db.dao

import android.arch.persistence.room.Dao
import android.arch.persistence.room.Delete
import android.arch.persistence.room.Insert
import android.arch.persistence.room.Query
import com.android4dev.roomjetpack.db.entity.User

/***
 * Android4Dev
 */
@Dao
interface UserDAO {
    @Query("SELECT * FROM user")
    fun getAll(): List<User>

    @Query("SELECT * FROM user WHERE uid IN (:userIds)")
    fun loadAllByIds(userIds: IntArray): List<User>

    @Query("SELECT * FROM user WHERE first_name LIKE :first AND " + "last_name LIKE :last LIMIT 1")
    fun findByName(first: String, last: String): User

    @Insert
    fun insertAll(vararg users: User)

    @Delete
    fun delete(user: User)
}

Tip: When inserting data, you can provide a conflict strategy.
In this example, you do not need a conflict strategy, because the uid is your primary key, and the default SQL behavior is ABORT, so that you cannot insert two items with the same primary key into the database.
If the table has more than one column, you can use
@Insert(onConflict = OnConflictStrategy.REPLACE)
to replace a row.

Step 4:

Now create database using @Database annotation. Now create one class and extend it using RoomDatabase. It is good practice to use singleton approach. Because RoomDatabase class is very heavy. 

package com.android4dev.roomjetpack.db

import android.arch.persistence.room.Database
import android.arch.persistence.room.Room
import android.arch.persistence.room.RoomDatabase
import android.content.Context
import com.android4dev.roomjetpack.db.dao.UserDAO
import com.android4dev.roomjetpack.db.entity.User

/***
 * Android4Dev
 */
@Database(entities = [(User::class)], version = 1)
abstract class ApplicationDatabase : RoomDatabase() {

    //Generate Singleton Instance
    companion object {
        private var INSTANCE: ApplicationDatabase? = null

        fun getInstance(context: Context): ApplicationDatabase {
            if (INSTANCE == null) {
                INSTANCE = Room.databaseBuilder(context,
                        ApplicationDatabase::class.java, "android4dev.db").allowMainThreadQueries().build()
            }
            return INSTANCE!!
        }

    }
    abstract fun userDao(): UserDAO
}

 

allowMainThreadQueries() is not a good practice. It is always advisable to run database operation on separate thread. allowMainThreadQueries() this will allow to run database operation on main thread.

Step 5:

Now create one xml file called activity_main.xml and add below code.

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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">

    <TextView
        android:id="@+id/title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:fontFamily="sans-serif"
        android:text="Android4Dev"
        android:textColor="@color/colorPrimaryDark"
        android:textSize="22sp"
        android:textStyle="bold"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <EditText
        android:id="@+id/editFirstName"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:hint="Enter First Name"
        android:inputType="text"
        android:textColor="@android:color/black"
        android:textSize="14sp"
        app:layout_constraintTop_toBottomOf="@id/title" />

    <EditText
        android:id="@+id/editLastName"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:hint="Enter First Name"
        android:inputType="text"
        android:textColor="@android:color/black"
        android:textSize="14sp"
        app:layout_constraintTop_toBottomOf="@id/editFirstName" />

    <Button
        android:id="@+id/buttonAdd"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Add"
        android:textSize="16sp"
        app:layout_constraintTop_toBottomOf="@id/editLastName" />

    <Button
        android:id="@+id/buttonList"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="User List"
        android:textSize="16sp"
        app:layout_constraintTop_toBottomOf="@id/buttonAdd" />
</android.support.constraint.ConstraintLayout>

Step 6:

Now create one activity name as MainActivity.kt

package com.android4dev.roomjetpack

import android.content.Intent
import android.os.AsyncTask
import android.os.Bundle
import android.view.View
import android.widget.Toast
import com.android4dev.roomjetpack.appinterface.AsyncResponseCallback
import com.android4dev.roomjetpack.base.BaseActivity
import com.android4dev.roomjetpack.db.ApplicationDatabase
import com.android4dev.roomjetpack.db.dao.UserDAO
import com.android4dev.roomjetpack.db.entity.User
import com.android4dev.roomjetpack.db.helper.RoomConstants
import kotlinx.android.synthetic.main.activity_main.*

/***
 * Android4Dev
 */
class MainActivity : BaseActivity(), View.OnClickListener, AsyncResponseCallback {

    private var db: ApplicationDatabase? = null
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        initView()
    }

    private fun initView() {
        db = ApplicationDatabase.getInstance(this)

        buttonAdd.setOnClickListener(this)
        buttonList.setOnClickListener(this)
    }

    override fun onClick(clickView: View?) {
        when (clickView?.id) {
            R.id.buttonAdd -> {
                val user = User(firstName = editFirstName.text.toString(), lastName = editLastName.text.toString().trim())
                InsertUserAsync(db!!.userDao(), RoomConstants.INSERT_USER, this).execute(user)
            }
            R.id.buttonList -> {
                val intent = Intent(this, UserListActivity::class.java)
                startActivity(intent)
            }
        }
    }


    override fun onResponse(isSuccess: Boolean, call: String) {
        if (call == RoomConstants.INSERT_USER) {
            if (isSuccess) {
                editFirstName.text.clear()
                editLastName.text.clear()
                Toast.makeText(this@MainActivity, "Successfully added", Toast.LENGTH_SHORT).show()
            } else {
                Toast.makeText(this@MainActivity, "Some error occur please try again later!!!", Toast.LENGTH_SHORT).show()
            }
        }
    }

    class InsertUserAsync(private val userDao: UserDAO, private val call: String, private val responseAsyncResponse: AsyncResponseCallback) : AsyncTask<User, Void, User>() {
        override fun doInBackground(vararg user: User?): User? {
            return try {
                userDao.insertAll(user[0]!!)
                user[0]!!
            } catch (ex: Exception) {
                null
            }
        }

        override fun onPostExecute(result: User?) {
            super.onPostExecute(result)
            if (result != null) {
                responseAsyncResponse.onResponse(true, call)
            } else {
                responseAsyncResponse.onResponse(false, call)
            }
        }
    }
}

 

As you can see in code we can create database instance using below code.

db = ApplicationDatabase.getInstance(this)

 

We have created this AsyncTask For inserting data into table. AsyncTask will run database operation in separate thread.

class InsertUserAsync(private val userDao: UserDAO, private val call: String, private val responseAsyncResponse: AsyncResponseCallback) : AsyncTask<User, Void, User>() {
        override fun doInBackground(vararg user: User?): User? {
            return try {
                userDao.insertAll(user[0]!!)
                user[0]!!
            } catch (ex: Exception) {
                null
            }
        }

        override fun onPostExecute(result: User?) {
            super.onPostExecute(result)
            if (result != null) {
                responseAsyncResponse.onResponse(true, call)
            } else {
                responseAsyncResponse.onResponse(false, call)
            }
        }
    }

 

Step 7:

Now create another xml file for display list of users. Name this xml file as activity_user_list.xml.

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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=".UserListActivity">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recyclerUserList"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</android.support.constraint.ConstraintLayout>

 

Step 8:

Create another xml file called list_item_user.xml and add below code.

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tool="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginEnd="10dp"
    android:layout_marginStart="10dp"
    android:layout_marginTop="10dp">

    <android.support.constraint.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:padding="10dp">

        <TextView
            android:id="@+id/textNameTitle"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:fontFamily="sans-serif"
            android:text="Name: "
            android:textSize="14sp" />

        <TextView
            android:id="@+id/textName"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:fontFamily="sans-serif"
            android:textSize="14sp"
            app:layout_constraintStart_toEndOf="@id/textNameTitle"
            tool:text="Lokesh" />


        <TextView
            android:id="@+id/textCountryTitle"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="5dp"
            android:fontFamily="sans-serif"
            android:text="Country: "
            android:textSize="14sp"
            app:layout_constraintTop_toBottomOf="@id/textNameTitle" />

        <TextView
            android:id="@+id/textCountry"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="5dp"
            android:fontFamily="sans-serif"
            android:textSize="14sp"
            app:layout_constraintStart_toEndOf="@id/textCountryTitle"
            app:layout_constraintTop_toBottomOf="@id/textNameTitle"
            tool:text="India" />

        <TextView
            android:id="@+id/textDelete"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="16dp"
            android:layout_marginStart="16dp"
            android:layout_marginTop="10dp"
            android:fontFamily="sans-serif"
            android:padding="5dp"
            android:text="Delete"
            android:textColor="@android:color/holo_red_light"
            android:textSize="16sp"
            android:textStyle="bold"
            app:layout_constraintTop_toBottomOf="@id/textCountryTitle" />

    </android.support.constraint.ConstraintLayout>
</android.support.v7.widget.CardView>

 

Step 9:

Now create one Activity called as UserListActivity.kt and add below code.

package com.android4dev.roomjetpack

import android.os.AsyncTask
import android.os.Bundle
import android.support.v7.widget.LinearLayoutManager
import android.widget.Toast
import com.android4dev.roomjetpack.adapter.UserListAdapter
import com.android4dev.roomjetpack.appinterface.AsyncResponseCallback
import com.android4dev.roomjetpack.base.BaseActivity
import com.android4dev.roomjetpack.db.ApplicationDatabase
import com.android4dev.roomjetpack.db.dao.UserDAO
import com.android4dev.roomjetpack.db.entity.User
import com.android4dev.roomjetpack.db.helper.RoomConstants
import kotlinx.android.synthetic.main.activity_user_list.*

class UserListActivity : BaseActivity(), AsyncResponseCallback {

    private lateinit var userListAdapter: UserListAdapter
    private var db: ApplicationDatabase? = null
    private lateinit var arrayUser: List<User>
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_user_list)
        initView()
    }

    private fun initView() {
        db = ApplicationDatabase.getInstance(this)
        recyclerUserList.layoutManager = LinearLayoutManager(this)
        userListAdapter = UserListAdapter()
        userListAdapter.onItemDeleteClick = { position ->
            DeleteUserAsync(db!!.userDao(), RoomConstants.DELETE_USER, this).execute(arrayUser[position])
        }
        arrayUser = db?.userDao()?.getAll()!!
        userListAdapter.setUserList(arrayUser.toMutableList())
        recyclerUserList.adapter = userListAdapter
    }

    override fun onResponse(isSuccess: Boolean, call: String) {
        if (call == RoomConstants.DELETE_USER) {
            if (isSuccess) {
                arrayUser = db?.userDao()?.getAll()!!
                userListAdapter.setUserList(arrayUser.toMutableList())
                userListAdapter.notifyDataSetChanged()
                Toast.makeText(this@UserListActivity, "Successfully deleted", Toast.LENGTH_SHORT).show()
            } else {
                Toast.makeText(this@UserListActivity, "Some error occur please try again later!!!", Toast.LENGTH_SHORT).show()
            }
        }
    }

    class DeleteUserAsync(private val userDao: UserDAO, private val call: String, private val responseAsyncResponse: AsyncResponseCallback) : AsyncTask<User, Void, User>() {
        override fun doInBackground(vararg user: User?): User? {
            return try {
                userDao.delete(user[0]!!)
                user[0]!!
            } catch (ex: Exception) {
                null
            }
        }

        override fun onPostExecute(result: User?) {
            super.onPostExecute(result)
            if (result != null) {
                responseAsyncResponse.onResponse(true, call)
            } else {
                responseAsyncResponse.onResponse(false, call)
            }
        }
    }
}

 

For getting all user from database we will use

db?.userDao()?.getAll()!!

 

Below class will use for delete user entry. Again we have created AsyncTask for deleting user data.

class DeleteUserAsync(private val userDao: UserDAO, private val call: String, private val responseAsyncResponse: AsyncResponseCallback) : AsyncTask<User, Void, User>() {
        override fun doInBackground(vararg user: User?): User? {
            return try {
                userDao.delete(user[0]!!)
                user[0]!!
            } catch (ex: Exception) {
                null
            }
        }

        override fun onPostExecute(result: User?) {
            super.onPostExecute(result)
            if (result != null) {
                responseAsyncResponse.onResponse(true, call)
            } else {
                responseAsyncResponse.onResponse(false, call)
            }
        }
    }

 

Step 10:

Create one constant file for RoomDatabase Operation.

package com.android4dev.roomjetpack.db.helper

class RoomConstants {
    companion object {
        const val INSERT_USER="INSERT_USER"
        const val DELETE_USER="DELETE_USER"
    }
}

At the end i can just say that Room Persistence Library provides many features i can’t explain all features in one blog this is basic Database Operation which i have explained in this blog.

Hope you will like this blog if you have any question please ask me in comments or on my twitter, facebook account.

Source Code

Demo Link For Room Persistence Library

 

https://android4dev.com

Hi, I am Android Developer and Founder of Android4Dev. Right now my interest is in RxKotlin, MVVM and Dagger2. I am Kotlin Lover. Kotlin made me more productive in terms of coding. Do you want me to code for you ? Please don’t hesitate to contact me.