Share & Follow

Get new posts when they come!

Thank you for subscribing.

Something went wrong.

Database example app with Room ORM

With Android Architecture Components getting a stable 1.0 version, there are now no excuses of not using it 🙂 Along with handling lifecycle events, realtime data updates in UI (ViewModel with LiveData) and pagination of loaded data (Paging), comes Room – small, yet powerful SQLite ORM. In this post I’m gonna demonstrate its core capabilities on an example Android application.

Remember those times implementing SQLiteOpenHelper and checking SQL queries in run-time? Good news is that you don’t have to do it anymore! Room performs compile-time checks on your SQL queries and you don’t have to write any SQLite code which is not in a direct relation with your data queries. Great, lets use it!

First of all, Room is a part of Architecture Components, which means it works really well with ViewModel, LiveData and Paging (but does not depend on them!). Also, RxJava and Kotlin are perfectly fine too. In order to add Room to the project, I’m adding the following lines in app’s build.gradle file:

In my example application (jump to the source code if you wish) I have one Activity with BottomNavigationView, holding two tabs – Movies and Directors. There is a RecyclerView to show the lists of each category, FloatingActionButton – to add a new movie/director, and two options in ActionBar’s overflow menu – Delete list data and Re-create database. Also, when clicking on a list item, you can update its data.

I use two tables – Director and Movie. Same director can be the author of many movies, so it’s one-to-many relationship. I assume the full name of the director to be unique and mandatory. Movie’s title doesn’t have to be unique, but needs to be provided as well.

Data structure classes such as Director should be annotated with @Entity to become a table in database. You can provide optional parameters such as tableName and indices. Adding an index on a column which will be used in search queries, speeds the search. I have checked this with full_name value and the search was indeed faster. Read more on indices here.

Note the unique = true for full_name index. Room does not have @Unique annotation, but you can enforce uniqueness on a index, so it’s one way to do it.

Each table needs to have at least one primary key. In our case it’s the id. @ColumnInfo(name = “did”) renames id to did for a column’s name. I don’t want to bother with providing it myself, hence autoGenerate = true for Room to do that.

I also have an ignored field age with @Ignore annotation. This will exclude it from being added into database’s schema.

Movie’s class is not much different, except it contains foreignKeys param. Remember our assumption that Director can have many movies? We define this relation with foreignKeys, by providing parentColumns and childColumns values (there is an alternative to foreignKeys and it’s a @Relation annotation). You can provide two more params: onUpdate and onDelete. By default their values are 1 or ForeignKey.NO_ACTION. By writing onDelete = ForeignKey.CASCADE I basically tell Room to delete a movie if its director got removed from the database. Other possible values are: RESTRICT, SET_NULL or SET_DEFAULT.

That’s it for the entity classes. Now lets define methods that we’re gonna use to manipulate our data. Create a DirectorDao interface with @Dao annotation (it also can be an abstract class instead of interface). Room provides four different annotations: @Insert, @Update, @Delete and @Query. For insert and update you can provide OnConflictStrategy value, customizing the behavior in case of arisen conflict. By default the transaction is aborted, but in our case, we ignore it. As a parameter of insert(), update() and delete() you can provide a single object, a few of them or a list. In case you need an id of a newly inserted row(s), just return long/long[]. For update() you can return int, indicating how many rows were affected.

In case of DirectorDao, I return long for insert(Director director), as I will need the id of a newly created row for a movie.

There are a few queries I will need as well: findDirectorById(), findDirectorByName() and getAllDirectors(). full_name column is used for searching and ordering. It is a good candidate for an index.

Notice the return type of getAllDirectors(). I wrap the list of results in LiveData, as I would like the list to update automatically when the underlined data changes.

MovieDao is very similar, so I won’t talk about it. Now, that we have entity and dao classes created, lets create a database class itself. It should be abstract and extend RoomDatabase. With @Database annotation we provide the array of entities and a version. Usually, there’s no need for more than one instance of a DB, thus make it a singleton.

For db initialization, use Room.databaseBuilder(). Just for simplifying the code, I’ve added allowMainThreadQueries(). If not provided, each DB transaction will throw

java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long period of time

I’m also adding a callback hooked to db’s creation (there’s also another one for opening), so some example data is populated at the first start of the app.

What you definitely need to do in RoomDatabase class, is to provide abstract methods for getting DAOs. In our case it’s movieDao() and directorDao().

What’s interesting in this code, is the DirectorsViewModel, that I take from ViewModelProviders and attach an observer on it. Whenever onChanged() gets called, new directors list is set on the list adapter and we see the change in UI.

Depending if we came from a list item click (the directorFullNameExtra is present then) or FloatingActionButton click, there’s only one method to save the director:

I don’t need to check if the name is unique when inserting, as Room is doing it for me. It will ignore the transaction if the same name already exists in DB (thanks to OnConflictStrategy.IGNORE). I could do the same with update actually 😉

The method for saving movie is a bit more complicated, because I need an id of either newly inserted or updated or just the old director from DB. Once I have it, I provide it to new movie, or update an existing one.

Now, download the app, click Run, add some directors and movies. Check if the movie is removed when you delete its director. Check if update works, add your custom fields and queries. Check how database schema looks in movies-room\app\schemas\com.lomza.moviesroom.db.MoviesDatabase\1.json.