A lot of times we need to locally save data in our android apps. For simple datas shared preferences will suffice but when we have to store data with different models and relations, sqlite is the default way to go. Gone are the days when setting up the database in android was a pain. Room is the simplified way of setting up and accessing data from the sqlite database.
Below are the points to know when you are setting up sqlite db with room.
- Entity
- Dao
- Type Converters
- Database Class
Lets break down the 4 building blocks of room db into simple english
Entity
Each model you want to save will be an entity. It’s as simple as that. Say you want to save a user object that has name, age and phone as the fields. Its easier if you visualise each object you want to save in terms of excel sheet rows and columns
Lets consider the above as the user items we want to save. The related entity class will be as below
@Entity
data class UserEntity(
@PrimaryKey(autoGenerate = true)
val id: Int,
val name: String,
val age: Int?,
val phone: String?
)
We denote each entity class with the annotation Entity. The id is the primary key and we have set that to auto generate.
Dao
Dao stands for Data Access Objects. We will be using these classes to read, write, update and delete records for our entity. It is a good design pattern to have one Dao for each of our Entities. Though this is not necessary.
@Dao
interface UserDao{
@Query("SELECT COUNT(*) FROM UserEntity")
fun count(): Int
@Query("SELECT * FROM UserEntity")
fun listAll(): List<UserEntity>
@Query("SELECT * FROM UserEntity where id = :id")
fun getOne(id: Int): List<UserEntity>
@Insert
fun insert(obj: UserEntity)
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertAll(obj: List<UserEntity>)
@Update
fun update(obj: UserEntity)
@Delete
fun delete(obj: UserEntity)
}
We have annotations for insert, update and delete. Fetching is done using the query annotation and adding sql queries.
Type Converters
All data types supported by sqlite are supported by room out of the box (int, string etc). But what if in my entity i had a data type that is not supported. In this case we will have to convert that data type into something supported by room db before insert and on fetch it needs to changed into the data type i need. This is where type converters come in.
Let say we have another field to our user entity called gender which is an enum class.
enum class Gender (val rawValue:String) {
male("male"),
female("female"),
other("other");
companion object {
fun from(findValue: String?): Gender = Gender.values().find { it.rawValue.equals(findValue ?: "" )} ?: Gender.other
}
}
The gender enum class is represented as above. The companion object is added to find the enum by its rawValue.
class GenderTypeConverter {
@TypeConverter
fun toGender(value: String): Gender {
return Gender.from(value)
}
@TypeConverter
fun fromGender(value: Gender): String {
return value.rawValue
}
}
We annotate the methods with TypeConverter annotation so that room is aware that these are type converters.
This is just a basic example. Type converters can be used to convert data types as long as you can write logic to convert them.
Database Class
This is the class that takes care of setting up the database for us. We will need to create a class that extends the RoomDatabase class.
@Database(entities = [UserEntity::class], version = 1)
@TypeConverters(GenderTypeConverter::class)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
}
We specify the entities and type converters. And the Dao’s are abstract functions.
Bonus -> Setting up the above database class as a singleton is just good design pattern.
Inside the AppDatabase class you can just use the below code as companion object to convert this into a singleton
companion object {
private var INSTANCE: AppDatabase? = null
private val DB_NAME = "my.db"
fun getDatabase(context: Context): AppDatabase {
if (INSTANCE == null) {
INSTANCE = Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java, DB_NAME
)
.fallbackToDestructiveMigration()
.addCallback(object : RoomDatabase.Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
PegLog.d("AppDatabase", "create db called")
}
}).build()
}
return INSTANCE!!
}
fun deleteDatabase() {
synchronized(AppDatabase::class.java) {
INSTANCE?.clearAllTables()
}
}
}
If you are interested in how relations work in Roomdb check out Room db relations in android made simple with kotlin! which is a continuation of this article
Please do like clap if you like this article. Please do follow for android articles once a week.
As always! Happy Coding!