Android Room Database

Sometimes users need to interact with your application even when the device cannot access the network. Or you want to save some relevant pieces of data locally. The Room library comes to the rescue.

Why Room is good option?

  • It will catch errors in compile time
  • Using annotations will minimize boiler plate code.
  • Streamlined database migration paths

Three main components of Room are:

  1. The @Entity classes
  2. Data Access Object @Dao
  3. Database class

Dependencies

def room_version = "2.3.0"

First, define your entity class. This will represent table in your database. One instance of the entity class is one row in the table.

@Entity(tableName = "debtor")
data class Debtor(
@PrimaryKey(autoGenerate = true)
val id: Int? = null,

@ColumnInfo(name = "debtor_name")
val personName: String,

@ColumnInfo(name = "money_amount")
val amountMoney: Int,

@ColumnInfo(name = "reference")
val reference: String,

@ColumnInfo(name = "is_recurring_payment")
val isRecurringPayment: Boolean,

@ColumnInfo(name = "due_date")
val dueDate: String
)

Above is a simple data class with @Entity annotation which tells the compiler that this is my table in DB.

We know that every table needs some primary key that is unique. On the instance, you want as a primary key just add @PrimaryKey annotation. You can set it as auto-generate and make it nullable or set some default value.

If you want a column to have a different name, add the @ColumnInfo annotation to the field and set the name property.

DAO — Data Access Object

Data access object is simple interface class which provides methods that the rest of the app uses to interact with data in the debtor table.

Dao classes don’t have properties, but they do define one or more methods for interacting with the data in your app’s database.

@Dao
interface DebtorDao {

@Insert(onConflict = OnConflictStrategy.REPLACE)
fun addDebtor(debtor: Debtor)


@Query("SELECT * FROM debtors")
fun getAllDebtors(): LiveData<List<Debtor>>


@Delete
fun deleteDebtor(debtor: Debtor)

}

Database class

Database class defines the db configuration and serves as the app’s main access point to the persisted data.

You must satisfy the following conditions when creating the database class

  • The class must be annotated with a @Database annotation that includes an entities array that lists all of the data entities associated with the database.
  • The class must be an abstract class that extends RoomDatabase.
  • For each DAO class that is associated with the database, the database class must define an abstract method that has zero arguments and returns an instance of the DAO class.
@Database(entities = [Debotr::class.java], version = 1)
abstract class DebtorDatabase : RoomDatabase() {
abstract fun DebtorDao(): DebtorDao
}

While this code above should work fine, there is important concept to follow. When we instantiate database object, we need Singleton Pattern.

Each Room database instance is expensive so we need to access the database from the single entry point.

Below is the code following Singleton pattern.

@Database(entities = [Debtor::class], version = 1, exportSchema = false)
abstract class DebtorDatabase: RoomDatabase() {
abstract fun debtorDao(): DebtorDao

companion object {

@Volatile
private var INSTANCE: DebtorDatabase? = null
private val LOCK = Any()

operator fun invoke(context: Context) =
INSTANCE ?: synchronized(LOCK) {
INSTANCE ?: createDatabase(context).also {instanceOfDb ->
INSTANCE = instanceOfDb
}
}

private fun createDatabase(context: Context) =
Room.databaseBuilder(
context.applicationContext,
DebtorDatabase::class.java,
"debtor_database"
).build()

}

}

Note: avoid accessing the database from the Main thread. It may block the UI and often your app will crash.

Happy coding!

Hi firend! I am self-taught Android Developer. Acrobatic paragliding pilot and passionate beekeeper. I love adventures, camping and spending time in nature.