Pre requisite of this tutorial is that you must have knowledge of databases and basic CRUD operations with them. Knowledge of SQLite in Android is recommended but not necessary.

This tutorial will get you started with Room in Android by teaching you all the CRUD operations and related details required to use this amazing library in your projects. Throughout this tutorial we will build a contact saving application. The complete code is available at this Github repo. The best way to learn is by doing, so open the repo or clone it in your machine and make a new project in which you will add all the functionality. If at any point you get stuck, refer the completed code.

Here is what the UI looks like in the completed app. I will not be teaching you how to make the UI, you will have to refer to the code for that.

What is Room?

Room is an ORM (object relational mapper) for SQLite database in Android. It is part of the Architecture Components released by Google. At its core, all the code that you write related to Room will eventually be converted to SQLite code. Room allows you to create and manipulate database in Android more quickly. See it as an abstraction layer on top of inbuilt SQLite database.

Setting up the project

Create a new Android Studio Project, I named it ‘ContactsRoom’.

In the your app level build.gradle file, add the following dependencies.

implementation "android.arch.persistence.room:runtime:1.0.0" annotationProcessor "android.arch.persistence.room:compiler:1.0.0" 1 2 implementation "android.arch.persistence.room:runtime:1.0.0" annotationProcessor "android.arch.persistence.room:compiler:1.0.0"

In your project level build.gradle file make sure in the repositories section google() is added.

repositories { google() jcenter() } 1 2 3 4 repositories { google ( ) jcenter ( ) }

Now sync the project and you are ready to go.

Creating schema

In Room you don’t have to write a class extending SQLiteOpenHelper or write table creation queries. Room creates the tables automatically for you. In Room we define a simple POJO class and annotate it with @Entity . Here we will create a model class for contacts with all required annotations.

@Entity(tableName = "contact") public class Contact { private String firstName; private String lastName; @PrimaryKey @NonNull private String phoneNumber; public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } @NonNull public String getPhoneNumber() { return phoneNumber; } public void setPhoneNumber(@NonNull String phoneNumber) { this.phoneNumber = phoneNumber; } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 @ Entity ( tableName = "contact" ) public class Contact { private String firstName ; private String lastName ; @ PrimaryKey @ NonNull private String phoneNumber ; public String getFirstName ( ) { return firstName ; } public void setFirstName ( String firstName ) { this . firstName = firstName ; } public String getLastName ( ) { return lastName ; } public void setLastName ( String lastName ) { this . lastName = lastName ; } @ NonNull public String getPhoneNumber ( ) { return phoneNumber ; } public void setPhoneNumber ( @ NonNull String phoneNumber ) { this . phoneNumber = phoneNumber ; } }

As you can see this is a simple java class with some annotations, nothing too complicated.

In the @Entity annotation we have provided a custom tableName, you can leave out this field. If you don’t provide a tableName, Room will use the name of class as the table name i.e. ‘Contact’ (with capital C).

When you annotate a class with @Entity you must provide a no arguments constructor. Here as we have not provided any constructor, java automatically inserts the default (no arguments) constructor at compile time.

If a class is annotated with @Entity then you have to specify a primary key using the @PrimaryKey annotation. Here we use the phoneNumber field as the primary key.

Room will take our Contact class and create a corresponding table with the provided fields as columns.

If in any case you don’t want a field to be considered in the table schema then just annotate it with @Ignore , by doing this Room will not persist this field.

By default room will use the field names are names of columns in tables, but you can change this by using @ColumnInfo(name = "phone") annotation on the field. Take a look at all options provided by ColumnInfo annotation.

Creating DAO (data access object)

Now will we create a DAO(data access object) for doing operations on our Contact table. You can take an in-depth look at the concept of a DAO, in short it is just an abstract class that defines a bunch of operations relating to an object.

Below is a simple DAO (interface) with CRUD operations on Contact.

@Dao public interface ContactDAO { @Insert public void insert(Contact... contacts); @Update public void update(Contact... contacts); @Delete public void delete(Contact contact); } 1 2 3 4 5 6 7 8 9 10 11 @ Dao public interface ContactDAO { @ Insert public void insert ( Contact . . . contacts ) ; @ Update public void update ( Contact . . . contacts ) ; @ Delete public void delete ( Contact contact ) ; }

To create a DAO, you have to create an interface and annotate it with @Dao .

Insert operation

To create an insert operation just define a method that takes Contact or Contact... (varargs) as a parameter and annotate the method with @Insert .

Update Operation

Similar to insert operation, just create a method that takes Contact or Contact... (varargs) as a parameter and annotate the method with @Update .

Delete Operation

Similar to insert operation, just create a method that takes Contact or Contact... (varargs) as a parameter and annotate the method with @Delete .

Running Queries

While Room provides annotations for simple operations on databases, it also provides the ability to custom written queries. So if you wanted to get all the contacts, you would write a select query as below.

@Query("SELECT * FROM contact") public List<Contact> getContacts(); 1 2 @ Query ( "SELECT * FROM contact" ) public List < Contact > getContacts ( ) ;

Use the @Query annotation and pass in the query you want to run. Since we want all the contacts in the table, we will set the return type to a list of contacts, List<Contact> . Remember when you ran a query in SQLite it would return a Cursor object, then you would have to iterate over the cursor and fetch all the values while adding them to a list. Room does all of this behind the scenes, it will run the query, convert the cursor result to list and return the list to you. Pretty amazing!

Now suppose you want to write a query to get the Contact details associated with a particular phone number. Here is how you would write that.

@Query("SELECT * FROM contact WHERE phoneNumber = :number") public Contact getContactWithId(String number); 1 2 @ Query ( "SELECT * FROM contact WHERE phoneNumber = :number" ) public Contact getContactWithId ( String number ) ;

You will pass the phone number in the method arguments and use it inside the query by prefixing the parameter name with : (colon). The return type here is Contact because we are looking for a single contact.

Below is the complete code for DAO interface.

@Dao public interface ContactDAO { @Insert public void insert(Contact... contacts); @Update public void update(Contact... contacts); @Delete public void delete(Contact contact); @Query("SELECT * FROM contact") public List<Contact> getContacts(); @Query("SELECT * FROM contact WHERE phoneNumber = :number") public Contact getContactWithId(String number); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @ Dao public interface ContactDAO { @ Insert public void insert ( Contact . . . contacts ) ; @ Update public void update ( Contact . . . contacts ) ; @ Delete public void delete ( Contact contact ) ; @ Query ( "SELECT * FROM contact" ) public List < Contact > getContacts ( ) ; @ Query ( "SELECT * FROM contact WHERE phoneNumber = :number" ) public Contact getContactWithId ( String number ) ; }

Room will take this interface and implement all the methods for you, all you have to do is specify the operations using the appropriate annotations.

Building the database

Now all we have to do is tie up all of the above classes and interfaces. For that we will create an abstract class named AppDatabase that will extend RoomDatabase . This will be the class that will give us access to the implementation of the DAO interface.

@Database(entities = {Contact.class}, version = 1) public abstract class AppDatabase extends RoomDatabase { public abstract ContactDAO getContactDAO(); } 1 2 3 4 @ Database ( entities = { Contact . class } , version = 1 ) public abstract class AppDatabase extends RoomDatabase { public abstract ContactDAO getContactDAO ( ) ; }

You will have to annotate this abstract class with @Database , specifying all the entities that you have created and the database version.

Inside you will specify an abstract method with return type of ContactDAO . Room will provide us with complete implementation of this abstract class along with all specified abstract methods.

Now wherever you want the instance of AppDatabase , write the below code.

AppDatabase database = Room.databaseBuilder(this, AppDatabase.class, "db-contacts") .allowMainThreadQueries() //Allows room to do operation on main thread .build() 1 2 3 AppDatabase database = Room . databaseBuilder ( this , AppDatabase . class , "db-contacts" ) . allowMainThreadQueries ( ) //Allows room to do operation on main thread . build ( )

Room.databaseBuilder() takes 3 arguments:

Reference to Context . Abstract database class to return the instance of. Name of the database.

You can see we have called a method allowMainThreadQueries() on the builder, this is because by default Room doesn’t allow you to run queries on MainThread , you will have to run them on a background thread. While Google recommends to run database queries on background threads , it is out of scope of this article to teach that, so we use the allowMainThreadQueries() method provided by Room which will allow the queries to run on MainThread .

After getting the instance of AppDatabase you can now get the ContactDAO by calling getContactDAO() which you defined earlier and start calling the CRUD methods defined.

ContactDAO contactDAO = database.getContactDAO(); //Inserting a contact Contact contact = new Contact(); contact.setFirstName("Gurleen"); contact.setLastName("Sethi"); contact.setPhoneNumber("1234567890"); contactDAO.insert(contact); //Fetching all contacts List<Contact> contact = contactDAO.getContacts(); //Getting a single contact and updating it Contact contact = contactDAO.getContactWithId("1234567890"); contact.setFirstName("Kevin"); contact.setLastName("Brew"); contactDAO.update(contact); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ContactDAO contactDAO = database . getContactDAO ( ) ; //Inserting a contact Contact contact = new Contact ( ) ; contact . setFirstName ( "Gurleen" ) ; contact . setLastName ( "Sethi" ) ; contact . setPhoneNumber ( "1234567890" ) ; contactDAO . insert ( contact ) ; //Fetching all contacts List < Contact > contact = contactDAO . getContacts ( ) ; //Getting a single contact and updating it Contact contact = contactDAO . getContactWithId ( "1234567890" ) ; contact . setFirstName ( "Kevin" ) ; contact . setLastName ( "Brew" ) ; contactDAO . update ( contact ) ;

The complete code is available at this GitHub repo, I urge you to look at the code to get full understanding.



Saving non-primitive types using Type Converters

In Room you can only save primitive types of the language. If you want to save a non-primitive type such as a List or Date , you would have to provide a type converter for it. With TypeConverter you tell Room how to convert the non-primitive type to a primate type. We will add a createdDate field of type Date in our Contact model.

@Entity(tableName = "contact") public class Contact { private String firstName; private String lastName; @PrimaryKey @NonNull private String phoneNumber; private Date createdDate; public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } @NonNull public String getPhoneNumber() { return phoneNumber; } public void setPhoneNumber(@NonNull String phoneNumber) { this.phoneNumber = phoneNumber; } public Date getCreatedDate() { return createdDate; } public void setCreatedDate(Date createdDate) { this.createdDate = createdDate; } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 @ Entity ( tableName = "contact" ) public class Contact { private String firstName ; private String lastName ; @ PrimaryKey @ NonNull private String phoneNumber ; private Date createdDate ; public String getFirstName ( ) { return firstName ; } public void setFirstName ( String firstName ) { this . firstName = firstName ; } public String getLastName ( ) { return lastName ; } public void setLastName ( String lastName ) { this . lastName = lastName ; } @ NonNull public String getPhoneNumber ( ) { return phoneNumber ; } public void setPhoneNumber ( @ NonNull String phoneNumber ) { this . phoneNumber = phoneNumber ; } public Date getCreatedDate ( ) { return createdDate ; } public void setCreatedDate ( Date createdDate ) { this . createdDate = createdDate ; } }

If you try to run this code without providing a type converter, Room will give error during compilation.

In our case we will provide type converter for Date , we will convert Date to long . When writing a type converter, you will have to provide conversion both ways, i.e. in our case one converter that converts Date to long and another that converts long to Date .

Writing type converter is easy, all you have to do is write methods with appropriate return type, argument and annotate them with @TypeConverter .

public class DateTypeConverter { @TypeConverter public long convertDateToLong(Date date) { return date.getTime(); } @TypeConverter public Date convertLongToDate(long time) { return new Date(time); } } 1 2 3 4 5 6 7 8 9 10 11 public class DateTypeConverter { @ TypeConverter public long convertDateToLong ( Date date ) { return date . getTime ( ) ; } @ TypeConverter public Date convertLongToDate ( long time ) { return new Date ( time ) ; } }

You you can see we have created a new class called DateTypeConverter with the required methods.

Now we have to tell Room to use these type converter. In our AppDatabase class we will have to tell which type converter to use. We have to do this using the annotation @TypeConverters .

@Database(entities = {Contact.class}, version = 1) @TypeConverters({DateTypeConverter.class}) public abstract class AppDatabase extends RoomDatabase { public abstract ContactDAO getContactDAO(); } 1 2 3 4 5 @ Database ( entities = { Contact . class } , version = 1 ) @ TypeConverters ( { DateTypeConverter . class } ) public abstract class AppDatabase extends RoomDatabase { public abstract ContactDAO getContactDAO ( ) ; }

Now you can run the application, with no problems.

That is it for this tutorial. We will cover more advance features about Room in the next tutorial.

Meanwhile here are some other articles that you might be interested in.

Move content to side in Drawer Layout

How to make Bottom Sheet In Android

Add 360 Photo Viewer in your Android App

Hide Floating Action Button in when scrolling in Recycler View