Creating a simple todo app in Android Part I: Room

Maxime Dupierreux
ProAndroidDev
Published in
4 min readApr 10, 2019

--

Since its announcement, I wanted to try the new notification bubbles from Android Q Beta. I thought it would be interesting to showcase a concrete use case of bubbles : a todo app.

This is what we want in the end

This story is the first of a series of three in which I’ll write a simple Android app to demonstrate the power of Room, LiveData, ViewModel and the new feature coming in Android Q: notification bubbles.

In this part, I’ll focus on the data model using Room.

What is Room ?

This is the definition you can find on the official Android developer website.

The Room persistence library provides an abstraction layer over SQLite to allow for more robust database access while harnessing the full power of SQLite.

So basically Room is an ORM for Android provided by Google.

Let’s create the app

First of all (after project creation) you need to add Room to your project. It’s done by adding those lines to your build.gradle file :

implementation "androidx.room:room-runtime:$rootProject.roomVersion"
implementation "androidx.room:room-ktx:$rootProject.roomVersion"
kapt "androidx.room:room-compiler:$rootProject.roomVersion"
androidTestImplementation "androidx.room:room-testing:$rootProject.roomVersion"

Then you can create your entity class. In this case, we create the Todo entity.

@Entity(tableName = "todo_table")
data class Todo(@PrimaryKey(autoGenerate = true) val id : Int = 0,
@ColumnInfo(name = "title") val title: String,
@ColumnInfo(name = "details") val details : String,
@ColumnInfo(name = "done") val done : Boolean)

Several things to notice here :

  • the @Entity annotation is used for defining the entity name.
  • the @PrimaryKey annotation means id is the primary key and autogenerate means Room will autoincrement the id.
  • the @ColumnInfo annotation allows you to add info to the column like it’s name.

The next step is the creation of the DAO class:

@Dao
interface TodoDao {

@Query("SELECT * from todo_table order by done desc")
fun getAllTodos() : LiveData<List<Todo>>

@Insert
suspend fun insert(todo : Todo)

@Update
suspend fun update(todo: Todo)

@Query("DELETE FROM todo_table")
fun deleteAll()
}

The@Dao annotation identifies the class as a DAO for Room. You can see the @Insert, @Update annotations (there’s also a @Delete annotation). When calling the annotated methods, Room will execute the correct operation (insert, update or delete).

There’s also a @Query annotation you can use to specify any SQL you want to be executed when calling the method.

The getAllTodos() returns a LiveData object. This will be detailed in Part II.

Note: you can see the suspend this is part of Kotlin’s coroutines. You can find more about it here and here.

Now we can create the database class. It needs to extend RoomDatabase. Here’s the complete class:

@Database(entities = [Todo::class], version = 1)
abstract class TodoRoomDatabase : RoomDatabase() {
abstract fun todoDao(): TodoDao

companion object {
@Volatile
private var INSTANCE: TodoRoomDatabase? = null

fun getDatabase(context: Context): TodoRoomDatabase {
val tempInstance = INSTANCE
if (tempInstance != null) {
return tempInstance
}
synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
TodoRoomDatabase::class.java,
"todo_database"
).build()
INSTANCE = instance
return instance
}
}
}
}

As you can see, we make the database a singleton, this is done to avoid having multiple instances of the database opened at the same time.

There’s also the @Database annotation where we can declare the version number of the database and the entities. Declaring the entities will create corresponding tables in the database.

Last step of the model part of the app is the repository class. It’s not mandatory but it’s particularly useful when you have multiple data sources like a local database and a remote one. It embeds the logic for deciding where to fetch your data.

This is the repository class for our

class TodoRepository(private val todoDao: TodoDao) {

val todos: LiveData<List<Todo>> = todoDao.getAllTodos()

@WorkerThread
suspend fun insert(todo : Todo){
todoDao.insert(todo)
}

@WorkerThread
suspend fun update(todo: Todo){
todoDao.update(todo)
}
}

In our case it’s quite simple since we only have one data source (Room). One thing to notice tough is the @WorkerThread annotation, it means that our methods can’t be called from the UI thread. If you try to call it from the UI thread, it will throw an error.

This is it for the first part, we created the data model for our app. Here’s some links if you want to go further with Room:

Codelab

Room Persistence Library

Room introduction video

--

--