Blog

Android Room Tutorial: Simplifying How You Work with App Data

Information is probably the most important resource that users trust with apps. For app developers, that information tells us who the user is, which empowers us to provide a good user experience (UX). Also, by applying business rules to this information, we define the behavior that the app should have. This information can be sensible and expose users’ private data, so it’s very important that we correctly handle it to ensure its integrity, privacy, and proper storage. Within this Android Room tutorial, we will show you how you can work with this data more easily, while ensuring its integrity and security, all by using Android Room. Android Room is part of the Android Architecture Components.

 

Do You Need Android Room?

When we talk about storing information in a persistent way on Android, the most important and “easy” option is to use plain SQLite. However, to have a good database implementation using SQLite implies the generation of a lot of code that doesn’t provide real value. The architecture that follows often isn’t as clean or clear as it could be.

You could also use object-relational mapping, or ORM, but you will still need to manually define and create the database, subclassing SQLiteOpenHelper, and creating the contract classes. 

So, the question is: Is there any way to simplify this process? The answer is: Yes, Andorid Room is your solution. 

 

What is Android Room and How Does it Work?

Room is one of the most important tools in the Android Architectural Components. Released in the Google I/O 2016, Room is a powerful tool you can use to store and manipulate information on Android apps. It provides a very easy way to work with data and always ensures its security and integrity.

Is Android Room an ORM?

Room isn’t an ORM; instead, it is a whole library that allows us to create and manipulate SQLite databases more easily. By using annotations, we can define our databases, tables, and operations. Room will automatically translate these annotations into SQLite instructions/queries to perform the corresponding operations into the database engine.

The three major components of Android Room are:

• Entity: Represents a table within the Room Database. It should be annotated with @Entity.

• DAO: An interface that contains the methods to access the database. It is annotated with @Dao.

• Database: Represents the database. It’s an object that holds a connection to the SQLite database, and all the operations are executed through it. It is annotated with @Database. 

Room architecture looks like this: 

 

Android Room architecture

 

Too Much Talk—Let’s Look at an Example of Android Room

Let’s dive into this tutorial with an example of Android Room in action. Imagine that we need to create an app to store your gym routine. We’re going to have four entities in our database (as we’ll show below). All the sample code is written using Kotlin (if you don’t know Kotlin or you want to learn more, I invite you to read my article about it).

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

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'

android {
    //.. Omitted since it is not relevant for the example
}

dependencies {

    //... some dependencies were omitted due to they are not relevant for the example

    def room_version = "2.2.0-rc01"

    implementation "androidx.room:room-runtime:$room_version"
    kapt "androidx.room:room-compiler:$room_version"

    compile 'com.google.code.gson:gson:2.2.4'
    implementation 'io.reactivex.rxjava2:rxandroid:2.0.2'
    implementation 'io.reactivex.rxjava2:rxjava:2.1.17'
}

 

Let’s Analyze Each of the Three Major Android Room Components: Entities, DAOs, and Database

 

Entities:

For our example, we’re going to use four entities: Gender, Exercise, Routine, and Trainee.

 

Gender.kt

Represents the gender of the trainee

@Entity
data class Gender(
        @PrimaryKey(autoGenerate = true)
        val id: Int? = null,
        val name: String)

Things to notice:

• All the classes that represent an entity of the database have to be annotated with @Entity

• With the annotation @PrimaryKey(autoGenerate = true) we are indicating that the id is the primary key of the entity and should be autoGenerated by the database engine.

 

Exercise.kt  

Represents an exercise that is part of a routine.

@Entity
data class Exercise(
        @PrimaryKey(autoGenerate = true)
        val exerciseId: Int,
        val name: String,
        val repetitions:Int,
        @ColumnInfo(name = "machine_name")
        val machineName: String,
        val liftedWeight: Int)

Things to notice:

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

 

Routine.kt

Basically a container of exercises that together create an exercise routine.

@Entity(tableName = "traineeRoutine")
data class Routine(
        @PrimaryKey(autoGenerate = true)
        val routineId: Int,
        @ColumnInfo(name = "due_day")
        val dueDay: Date,
        @TypeConverters(ListConverter::class)
        val exercises: List)

Things to notice:

• When a class is annotated with @Entity, the name of the table will be the name of the class. If we want to use a different name, we have to add the tableName property along with the @Entity annotation. 

• The @TypeConverters annotation has to be used when we declare a property for which the type is a custom class, a list, date type, or any other type that Room and SQL don’t know how to serialize. In this case, we’re using the annotation at the class field level whereby only that field will be able to use it. Depending on where the annotation is placed, it will behave differently as explained here.

 

Trainee.kt

It represents the owner of the routine.

@Entity(indices = [Index("name"), Index("age")],
        foreignKeys = [ForeignKey(entity = Gender::class, 
                                  parentColumns = ["id"], childColumns = ["gender"])])
data class Trainee(
        @PrimaryKey(autoGenerate = true)
        val id: Int,
        val name: String,
        val age: Int,
        val gender: Int?,
        @Embedded
        val routine: Routine)

 

Data Access Objects (DAOs):

Data Access Objects (DAOs) are used to access our data when we implement Room. Each DAO has to include a set of methods to manipulate the data (insert, update, delete, or get).

A DAO can be implemented as an interface or as an abstract class. In our case, we’re using an interface. Since all DAOs are basically identical, we will show only one.

GenderDao.kt

@Dao
interface GenderDao {
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insertGender(gender: Gender)

    @Update
    fun updateGender(gender: Gender)

    @Delete
    fun deleteGender(gender: Gender)

    @Query("SELECT * FROM Gender WHERE name == :name")
    fun getGenderByName(name: String): List

    @Query("SELECT * FROM Gender")
    fun getGenders(): List
}

A few things to notice:

• All the DAOs have to be annotated with @Dao.

• A function annotated with @Insert, @Update, or @Delete has to receive an instance of the desired class as a parameter, which represents the object that we want to insert, update, or delete respectively.

• In the case of insert or update operations, we can use the property onConflict to indicate what to do when a conflict performing the operation happens. The strategies available to use are: REPLACE, ABORT, FAIL, IGNORE, and ROLLBACK.

• If we want to get specific information from one or more entities, we can annotate a function with @Query and provide a SQL script as parameter.

 

Database:

Represents the database. It holds a connection to the actual SQLite database.

AppDatabase.kt

@Database(entities = [Exercise::class, Gender::class, Routine::class, Trainee::class], version = 1)
@TypeConverters(DateTypeConverter::class)
abstract class AppDatabase : RoomDatabase() {
    abstract fun exerciseDao(): ExerciseDao
    abstract fun genderDao(): GenderDao
    abstract fun routineDao(): RoutineDao
    abstract fun traineeDao(): TraineeDao

    companion object {
        var INSTANCE: AppDatabase? = null

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

        fun destroyDataBase(){
            INSTANCE = null
        }
    }
}

Things to notice here:

• This is an abstract class that has to extend RoomDatabase.

• It has to be annotated with @Database, and 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 corresponding DAO (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.

 

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 a ‘date’ data type.

DateTypeConverter.kt

class DateTypeConverter {
    @TypeConverter
    fun fromTimestamp(value: Long?): Date? {
        return if (value == null) null else Date(value)
    }

    @TypeConverter
    fun dateToTimestamp(date: Date?): Long? {
        return date?.time
    }
}

 

Using the Android Room Database

Now let’s look at a very simple example of how to use the Room database we just created: 

MainActivity.kt
class MainActivity : AppCompatActivity() {

    private var db: AppDatabase? = null
    private var genderDao: GenderDao? = null

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

        Observable.fromCallable({
            db = AppDatabase.getAppDataBase(context = this)
            genderDao = db?.genderDao()

            var gender1 = Gender(name = "Male")
            var gender2 = Gender(name = "Female")

            with(genderDao){
                this?.insertGender(gender1)
                this?.insertGender(gender2)
            }
            db?.genderDao()?.getGenders()
        }).doOnNext({ list ->
            var finalString = ""
            list?.map { finalString+= it.name+" - " }
            tv_message.text = finalString

        }).subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe()


    }
}

What are we doing?

• Getting the instance of the database and GenderDao.

• Creating two Gender instances: Male and Female.

• Inserting the two instances created into the database.

• Querying the database to get all the genders stored in it.

• Merging the name of all the genders we got from the database, and setting the text of the TextView with that value.

 

Room to Do More with Less

Room is one of the important elements of the Android Architectural Components. It gives us a very robust framework to work with and persistent information, always ensuring data security and integrity. It also provides ease-of-use to developers, so they can write readable and self-explanatory code. If you want to do more with less code, and also ensure user data security, you should be using Room as your persistence layer on your app.

And that’s it! This is almost everything you need to know to create and use a database on Android with Room. Get the whole code for this project here. Thanks for reading this Android Room tutorial! 

Ready to be Unstoppable? Partner with Gorilla Logic, and you can be.

TALK TO OUR SALES TEAM