AndroidPub
Published in

AndroidPub

Android Architecture Components: Room — Introduction

Introduction

Recently Google announced a set of new libraries for designing Android application’s architecture — Android Architecture Components. It’s a collection of libraries, due to which you’re able to create consistent, fully testable and maintainable app. Sounds good?

In Architecture Components packet we get:

  • Room — a database library
  • Lifecycle Components (including LiveData, ViewModel, LifecycleObservers and LifecycleOwners) — a library for handling your app’s lifecycle
  • Paging Library — a small library to help you load data gradually

Today we’ll focus on Room library. Let’s start!

What’s Room?

Room is a database library. From the documentation:

Room provides an abstraction layer over SQLite to allow fluent database access while harnessing the full power of SQLite.

So basically, Room makes using SQLite much easier for you by implementing annotations, so you don’t have to worry about writing most (but not all!) of the SQLite code and checking it’s correctness.

Why another Android database?

Of course, there are a lot of different options to have database in your application. You can either create it by using native solutions (e.g. SQLiteOpenHelper), or use one of the existing library (greenDAO, OrmLite, Realm and much more). However, none of these solutions has all these advantages:

  • compile-time verification of SQL queries — each @Query and @Entity is checked at the compile time, so there’s no risk of runtime error that might crash your app (and it doesn’t check only syntax, but also e.g. missing tables)
  • very little boilerplate code
  • full integration with other Architecture components (like LiveData)

How to use it?

Step #1 — add dependencies

Ok, let’s assume I convinced you to use Room in your project. How to do it? At first, we need to add Google Maven repository to our build.gradle file:

allprojects {
repositories {
jcenter()
google()
}
}

And then we can add proper dependencies to app/build.gradle file:

implementation 'android.arch.persistence.room:runtime:1.0.0'
annotationProcessor 'android.arch.persistence.room:compiler:1.0.0'

You can check the latest dependencies version in the Android documentation.

Note: In this article I use Java. However, if you use Kotlin, you need to replace annotationProcessor with kapt and add kotlin-kapt plugin

Step #2— create entity

No we’re ready to start creating our database. Let’s assume we’d like to get all of the repo for specific user from Github. At first, we need to create our Repo entity:

import android.arch.persistence.room.Entity;
import android.arch.persistence.room.PrimaryKey;
import android.support.annotation.NonNull;

@Entity
public class Repo {
@PrimaryKey
public final String id;
public final String name;
public final String url;

public Repo(int id, String name, String url) {
this.id = id;
this.name = name;
this.url = url;
}
}

There are actually two requirements for the entity:

  • @Entity annotation on the class
  • at least one (*) @PrimaryKey field (if it’s non primitive, should be annotated with @NonNull)

(*) yeah, you can have more primary keys! Wait for the next article to check why would you need that.

Step #3— create DAO

Then, we need to create DAO for our entity. DAO stands for Data Access Object, so it’s a way of telling our database how to put the data:

import android.arch.persistence.room.Dao;
import android.arch.persistence.room.Delete;
import android.arch.persistence.room.Insert;
import android.arch.persistence.room.Query;
import android.arch.persistence.room.Update;
import java.util.List;

@Dao
public interface RepoDao {

@Query("SELECT * FROM repo")
List<Repo> getAllRepos();

@Insert
void insert(Repo... repos);

@Update
void update(Repo... repos);

@Delete
void delete(Repo... repos);
}

As you can see, we have many different methods here:

  • @Insert, @Update, @Delete for proper actions: inserting, updating and deleting records
  • @Query for creating queries — we can make select from the database (e.g. get all repos)

The important thing here is that for @Insert, @Update and@Delete you can pass many different types, e.g. for inserting you can have three different methods:

@Insert
void insert(Repo... repos);

@Insert
void insert(Repo repo);

@Insert
void insert(List<Repo> repoList);

The same applies for the @Query, you can return list of them, or even a good old Cursor!

@Query("SELECT * FROM repo")
List<Repo> getAllRepos();

@Query("SELECT * FROM repo WHERE id=:id")
Repo getRepo(int id);

@Query("SELECT * FROM repo")
Cursor getRepoCursor();

As you saw in the previous example, you can also pass parameter to get repositories by name or limit them:

@Query("SELECT * FROM repo WHERE name=:name")
List<Repo> getReposByName(String name);

@Query("SELECT * FROM repo WHERE name=:name LIMIT :max")
List<Repo> getReposByName(int max, String... name);

Step #4— create database

Now it’s time to create our database:

import android.arch.persistence.room.Database;
import android.arch.persistence.room.Room;
import android.arch.persistence.room.RoomDatabase;
import android.content.Context;
import android.support.annotation.NonNull;

@Database(entities = { Repo.class }, version = 1)
public abstract class RepoDatabase extends RoomDatabase {

private static final String DB_NAME = "repoDatabase.db";
private static volatile RepoDatabase instance;

static synchronized RepoDatabase getInstance(Context context) {
if (instance == null) {
instance = create(context);
}
return instance;
}

private static RepoDatabase create(final Context context) {
return Room.databaseBuilder(
context,
RepoDatabase.class,
DB_NAME).build();
}

public abstract RepoDao getRepoDao();
}

What do we have here? At first, we have @Database annotation with entities to our model classes and version of our database (which will be incremented every time we change something in the database schema).

Then we have getInstance() method which is nothing more than a simple singleton pattern. It’s necessary, because the documentation says that:

[…] each RoomDatabase instance is fairly expensive, and you rarely need access to multiple instances.

Next we have static create() method, where we create our database by passing Context, database class, and name.

The last thing is abstract getRepoDao() method. This is very important because database class should have these things at the minimum:

  • @Database annotation
  • class to be abstract and extend from RoomDatabase class
  • class have an abstract method with no parameters and returns the class that is annotated with @Dao (in our case: getRepoDao())

Step #5— here’s your database!

We’re finally able to make queries and inserts to our database. Let’s see this in practice!

The important thing here is that all operations must be done in the background thread. You can do it by using AsyncTask, Handler, RxJava or anything else.

To add things to the database we need to invoke:

RepoDatabase
.getInstance(context)
.getRepoDao()
.insert(new Repo(1, "Cool Repo Name", "url"));

Getting things is also pretty simple:

List<Repo> allRepos = RepoDatabase
.getInstance(MainActivity.this)
.getRepoDao()
.getAllRepos();

Extra

If you’d like to check Room in practice, but you don’t want to create a whole new project, you can try Google Codelabs for Room. There you’ll also find other cool topics, e.g. Type Converters.

In the next articles I’d write more about modeling relationships, combining Room with Lifecycle Components and RxJava, and much more!

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Paulina Szklarska

Paulina Szklarska

Flutter GDE / Flutter & Android Developer / blogger / speaker / cat owner / travel enthusiast