Android Room using Kotlin

Room is a part of the Android Architecture components which provides an abstraction layer over SQLite which allows for more robust database access while still providing the full power of SQLite.

Room architecture looks like the following:

There are basically 3 major components in Room.

“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.

@Entity(tableName = "user")
data class Users(@PrimaryKey(autoGenerate = true)var userId: Int? = null,
                 val userName: String, var location: String, val email: String)

“@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 UserDao {

    @Insert
    fun insertUser(users: Users)

    @Query("Select * from user")
    fun gelAllUsers(): List<Users>

}

“@Database”:- This is an abstract class that extends RoomDatabase, this is where you define the entities (tables)and the version number of your database. It contains the database holder and serves as the main access point for the underlying connection.

@Database(entities = [Users::class], version = 1, exportSchema = false)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {

    abstract fun userDao() : UserDao
}

Now, coming to the implementation part.

I am going to create a sample app for user management using the room in kotlin. In the app, I have added functionalities to add and list all the users. So let’s get it.

The first thing we need to do is to update our gradle file. It should look like the following

App.gragle

apply plugin: 'kotlin-kapt'

dependencies {
    kapt 'androidx.room:room-compiler:2.0.0'
    implementation "android.arch.persistence.room:runtime:1.0.0"
}

Entities

For our example, we are going to use four entities: userId, userName, location, and email

Model.kt
@Entity(tableName = "user")
data class Users(@PrimaryKey(autoGenerate = true)var userId: Int? = null,
                 val userName: String, var location: String, val email: String)

Things to know:

  • All the classes that represent an entity of the database have to be annotated with @Entity.
  • When a class is annotated with @Entity the name of the tablet will be the name of the class, if we want to use a different one we have to add the tableName property along with the @Entity annotation.
  • With the annotation @PrimaryKey(autoGenerate = true) we are indicating that id is the primary key of the entity and should be autoGenerate by the database engine.
  • By default, Room uses the field names as the column names in the database. If you want a column to have a different name, add the @ColumnInfo annotation to a field.

DAO

Next, we can start building our DAO which will contain our data queries.

UserDao.kt
@Dao
interface UserDao {

    @Insert
    fun insertUser(users: Users)

    @Query("Select * from user")
    fun gelAllUsers(): List<Users>
}

Here we just define basic SQL database functionality like inserting and deleting entries. You can see that the @Query annotation is used to annotate functions which are using queries. You can also use parameters in your queries using :parametername as you can see in the findByTitle function.


Type Converters

Type Converters are used when we declare a property which Room and SQL don’t know how to serialize. Let’s see an example of how to serialize datedata type.

Converters.kt
class Converters {

    @TypeConverter
    fun fromString(value: String): List<String> {
        val listType = object : TypeToken<List<String>>() {

        }.type
        return Gson().fromJson(value, listType)
    }

    @TypeConverter
    fun fromArrayList(list: List<String>): String {
        val gson = Gson()
        return gson.toJson(list)
    }
}

Database

After that, we can start writing the database which contains all your DAOs as abstract methods.

AppDatabase.kt
@Database(entities = [Users::class], version = 1, exportSchema = false)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {

    abstract fun userDao() : UserDao

    companion object {
        private var INSTANCE: AppDatabase? = null

        fun getInstance(context: Context): AppDatabase? {
            if (INSTANCE == null) {
                synchronized(AppDatabase::class) {
                    INSTANCE = Room.databaseBuilder(context.applicationContext,
                        AppDatabase::class.java, "user.db").allowMainThreadQueries()
                        .build()
                }
            }
            return INSTANCE
        }

        fun destroyInstance() {
            INSTANCE = null
        }
    }
}

Things to notice here:

  • This is an abstract class that has to extend from RoomDatabase.
  • It has to be annotated with @Database, it receives a list of entities with all the classes that compose the database (all these classes have to be annotated with @Entity). We also have to provide a database version.
  • We have to declare an abstract function for each of the entities included in the @Database annotation, this function has to return the correspondentDAO (A class annotated with @Dao).
  • Finally, we declare a companion object to get static access to the method getAppDataBase which gives us a singleton instance of the database.

Using the Room database

Create UserRepositoty class to perform all the user operations like insert and get all the users.

UserRepository.kt
class UserRepository(context: Context) {

    var db: UserDao = AppDatabase.getInstance(context)?.userDao()!!

    fun getAllUsers(): List<Users> {
        return db.gelAllUsers()
    }

    fun insertUser(users: Users) {
        insertAsyncTask(db).execute(users)
    }

    private class insertAsyncTask internal constructor(private val usersDao: UserDao) :
        AsyncTask<Users, Void, Void>() {

        override fun doInBackground(vararg params: Users): Void? {
            usersDao.insertUser(params[0])
            return null
        }
    }
}

If you try running the above code with the created database above, your app will crash as the operation performed is on the main thread. By default, Room keeps a check of that and doesn’t allow operations on the main thread as it can makes your UI laggy.

You can avoid that by using AsyncTask or Handler or Rxjava with io schedulers or any other options which puts the operation on any other thread.

There is one more option, which allows you to do the operation on the main thread. You can use the same for testing but should avoid. To do that you can add allowMainThreadQueries() on the builder.


We are done creating all the components. Now, lets make use of it.

Listing all the users

 MainActivity.kt
class MainActivity : AppCompatActivity() {

    lateinit var adapter: UserListAdapter
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        adapter = UserListAdapter()
        recyclerview_users.layoutManager = LinearLayoutManager(this)
        recyclerview_users.adapter = adapter


        floatingActionButton.setOnClickListener {
            val intent = Intent(this@MainActivity,AddUserActivity::class.java)
            startActivity(intent)
        }
    }

    override fun onResume() {
        super.onResume()
        val repo = UserRepository(this)
        val allUsers = repo.getAllUsers()
        adapter.setUsers(allUsers)

    }
}

Add Users

 AddUserActivity.kt
class AddUserActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_add_user)


        val repo = UserRepository(this)

        button_save_user.setOnClickListener {
            if (ed_username.text.isNotEmpty() && ed_email.text.isNotEmpty() && ed_location.text.isNotEmpty()) {
                val user = Users(
                    userName = ed_username.text.toString(),
                    location = ed_location.text.toString(),
                    email = ed_email.text.toString()
                )
                repo.insertUser(user)
            } else {
                Toast.makeText(this, "Invalid Input", Toast.LENGTH_SHORT).show()
            }
            finish()
        }
    }

}

What are we doing?

  1.  Create a UserRepository instance, That will create the instance of the database.
  2. First, we are displaying all the user from the room using repository repo.getAllUser().
  3. In the next activity, we are getting user details from the user and adding it into the room database using repo.insertUser(user).
github_link
github

Leave a Reply

Your email address will not be published. Required fields are marked *

Back to Top
%d bloggers like this: