Realm Database Tutorial For Android

The adoption rate of Realm database continues to grow to the point where in my opinion, most new Android development project should use Realm database for data persistence. This is because Realm database solved a nagging developer pain point so efficiently that it is too good to be true.

Realm database do have its fair share of unexciting workarounds and I will point them out in this tutorial series as well as provide a thorough introduction to Realm database such that even a novice developer can start using Realm database immediately.

This tutorial covers how to use Realm database in Android. To complete the tutorial we will build a fictitious course enrollment app called Pronto School. This app will show a list of courses and students. Each student can enroll in any number of courses. Each course can only be thought by one Instructor and many students can enroll in one course. Here are some screens of the app.

Sample Android App with Realm Database

Forget about Realm database for a minute, before we worry about what database to use, we first have to figure out what to save. From the above app description and screenshot, we can begin to identify the persistable objects in the app. The obvious ones are Course, Student, and Instructor. We also need to record Enrollment events. These entities will become Java model classes in our app and with SQLite, we will create corresponding tables for them.

It is the responsibility of the developer to make a mental and programmatic mapping between the SQLite tables and Java classes and this mapping is error-prone, time-consuming and distracting. Realm database eliminates this mapping with the concept of live objects called Realms. What is this Realm and how does it work?

Download Source Code

http://bit.ly/2kVjkBe

What is Realm Database

According to the official Realm description,Realmis a mobile first database that is built from the ground-up to run directly inside phones, tablets, and wearable.Realm database does not run on top of SQLite, it is not an ORM, it is a Realm. Quoting the official Realm documentation, “Realms are our equivalent of a database: they contain different kinds of objects, and map to one file on disk”. So, Realms are databases that do not require a separate mapping from Java objects to the persisted version on the disk.

It is kind of what you see is what is saved workflow, changes to the object in the user interface are automatically saved to the database if that object is a Realm managed object. Realm managed objects are your equivalent of SQLite tables. For Java object to become a Realm managed, the class must either extend RealmObject or implement RealmModel interface. For example, here is the Student model class for our demo project.

1

2

3

4

5

6

7

8

9

10

publicclassCourse{

privatelongid;

privateStringcourseName;

privateStringcourseDescription;

privatedoublecredits;

privateInstructor instructor;

privateStringcourseUrl;

privateStringimageUrl;

}

To make this class a Realm managed object all we have to do it extend that class from Realm object, which is equivalent of creating a table in SQLite, here is what the class will now look.

1

2

3

4

5

6

7

8

9

10

11

publicclassCourseextendsRealmObject{

@PrimaryKey

privatelongid;

privateStringcourseName;

privateStringcourseDescription;

privatedoublecredits;

privateInstructor instructor;

privateStringcourseUrl;

privateStringimageUrl;

privateRealmList&lt;Enrollment&gt;enrollments;

}

SQLite Vs Realm

Perhaps the best way to understand Realm database it to compare it to SQLite which I believe will continue to have its place in Android development. Both SQLite and Realm persists data to the disk, however, the difference is that with SQLite, your data is at rest and with Realm your data is in motion or live auto updated object. This is an important difference because it significantly affects your development workflow.

For example, if we have a Java class called Product.java with the following class definition.

Java

1

2

3

4

5

6

7

8

9

10

11

12

13

publicclassProduct{

privatelongid;

privateStringproductName;

privateStringdescription;

privateStringpromoMessage;

privatedoublesalePrice;

privatedoublepurchasePrice;

privateStringimagePath;

privatelongcategoryId;

privateStringcategoryName;

privatelongdateAdded;

privatelongdateOfLastTransaction;

}

To save the above class to SQLite, here is how we can create an SQLite table, which is a distinct entity from the Java class.

Create Product SQL Statement

Java

1

2

3

4

5

6

7

8

9

10

11

12

13

14

privatestaticfinalStringCREATE_PRODUCT_TABLE=

"CREATE TABLE "+Constants.PRODUCT_TABLE+"("

+Constants.COLUMN_ID+" INTEGER PRIMARY KEY AUTOINCREMENT,"

+Constants.COLUMN_NAME+" TEXT NOT NULL, "

+Constants.COLUMN_DESCRIPTION+" TEXT, "

+Constants.COLUMN_PROMO_MESSAGE+" TEXT, "

+Constants.COLUMN_PRICE+" NUMERIC, "

+Constants.COLUMN_PURCHASE_PRICE+" NUMERIC, "

+Constants.COLUMN_IMAGE_PATH+" TEXT, "

+Constants.COLUMN_CATEGORY_ID+" INTEGER, "

+Constants.COLUMN_CATEGORY_NAME+" TEXT, "

+Constants.COLUMN_DATE_CREATED+" BIGINT, "

+Constants.COLUMN_LAST_UPDATED+" BIGINT, "

+"FOREIGN KEY(category_id) REFERENCES category(_id)"+")";

And when it is time to retrieve data, SQLiteDatabase returns a Cursor object to you and here is how you can convert a Cursor object back to a Java object.

As verbose and tedious as the above steps may seem, it enables the one clear advantage that SQLite has over Realm database, which is that it supports the concept of separation of concerns. With SQLite, there is a defined point at which you hand over Java object to SQLite to be persisted. This allows you to concentrate all your data persistent logic in say a Data Access Layer or apply the Repository pattern. If done correctly, SQLite could be a replaceable data access implementation. Realm is not replaceable – that is if you want to enjoy the full benefit of Realm for which there is many.

When you are making changes to a Realm managed object in the view, the changes are automatically applied to the persisted representation of that object in the disk – hence the auto updating label. And this changes must be contained within a Realm transaction. So if you are updating the name of a Student, you must open a Realm transaction at the point where you retrieve that value from the EditText. You will be excused if you regard that as spaghetti code, but then that is pay off to have super fast query capability, no mapping or unmapping and intelligent handling of multi-thread data access.

You can still use Realm in a tiered architecture code base, you will lose out on some of its features. My premium app template shows an example of using Realm in an MVP pattern. However, I will admit that it is challenging to integrate Realm to an existing code base. Realm shines best for greenfield projects. If you need assistance with integrating Realm into an existing codebase or starting a new one for that matter, I will be glad to gie you a quote.

Setup Realm Database In Android

It is very easy to get started with Realm database in Android. After you create a new project in Android Studio follow the two steps outlined here to add Realm to your project. After you have added Realm to your project, the next step is to initialize it. You ought to be familiar that most Android libraries need the Context object to hook into the framework and so initialize Realm, all we have to do is to pass a Context to Realm’s init method like this.

1

Realm.init(context)

However, just like you configure SQLite by passing some parameters to SQLiteOpenHelper class like this:

1

2

3

privateDatabaseHelper(Context context){

super(context,DATABASE_NAME,null,DATABASE_VERSION);

}

You configure Realm with a RealmConfiguration object and a good place to perform this initialization in a custom application class. For our demo app, we add an application class named ProntoSchoolApplication.java and in this class, we initialize and configure Realm like this:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

publicclassProntoSchoolApplicationextendsApplication{

@Override

publicvoidonCreate(){

super.onCreate();

if(LeakCanary.isInAnalyzerProcess(this)){

return;

}

initRealm();

}

privatevoidinitRealm(){

Realm.init(this);

RealmConfiguration config=newRealmConfiguration.Builder()

.name("prontoschool.realm")

.schemaVersion(1)

.deleteRealmIfMigrationNeeded()

.build();

Realm.setDefaultConfiguration(config);

}

Create Realm Database Tables

There really is no such thing as Realm database tables, we have Realm managed objects instead. The phrase “create tables” is used because of its familiarity. So in order to create tables or auto-updating objects, all we need to do is to make sure that our model classes all extend from RealmObject base class. And while we do that we can take advantage of Realm features such as easy handling of Relationships to define one to many relationships in our app such as the relationship between a Student and an Enrollment. One Student can have many Enrollments but one Enrollment can only have one Student like this.

1

2

3

4

5

6

7

8

publicclassEnrollmentextendsRealmObject{

@PrimaryKey

privatelongid;

privateCourse course;

privateStudent student;

privateStringgrade;

privateDate enrollmentDate;

}

And Student class.

Student model class

Java

1

2

3

4

5

6

7

8

9

10

publicclassStudentextendsRealmObject{

@PrimaryKey

privatelongid;

privateStringstudentName;

privateStringemailAddress;

privateStringphoneNumber;

privateStringprofileImagePath;

privateRealmList<Enrollment>enrollments;

}

Auto-Increment Primary Key

For some reason, Realm does not currently support auto increment of Primary Key. Don’t ask me why. There are two workarounds regarding auto increment of the primary key.

Query for the max primary key value each time you want to insert data and then increment that by 1 and set as the primary key.

1

2

longid=realm.where(Student.class).max("id").longValue();

student.setId(id);

Run the query to find the Id once, probably when the app launches and then keep a reference to that max id value and then increment and get that value as needed like so:

The challenge with either approach is that when you query Realm for nonexistent managed object it will fail. Even though you have extended your model class from the RealmObject super class, that managed object or rather that table is not created until you actually new up an instance of that object. That is kind of similar to how Entity Framework Code First workflow works if you are familiar with C#.

My workaround for this workaround is to surround the query in a try catch statement and when it fails I will go ahead and create the managed object explicitly like this.

Opening and Closing Realm Instances

With SQLite, you call getWritableDatabase() or getReadableDatabase() against a SQLiteOpenHelper class to obtain an instance of SQLiteDatabase. With Realm you call Realm.getDefaultInstance() to get an instance of Realm. Just like it is recommended to close SQLite database when you are done using it, it is also highly recommended to close Realm instances when you are done so the allocated memory to that instance will be reallocated.

For UI threads such the Activity or Fragment, you can close Realm in the onDestroy() method like this:

1

2

3

4

5

6

7

@Override

publicvoidonDestroy(){

if(!realm.isClosed()){

realm.close();

}

super.onDestroy();

}

For non-Looper thread such as the Presenter or Repository class, you should close Realm right where you used it like this:

Working with Realm Database Queries

Querying for data in Realm is fast and easy, that is where a lot of the productivity gains come from. Realm’s query engine uses a Fluent interface to construct multi-clause queries. You can construct your query inline or use RealmQuery object to construct your query. Realm query results are returned as a RealmResult<T> where T is a Realm managed class. The Realm objects that are contained in RealmResult are live objects. You are free to read the values where you see fit, but if you want to update the value in any object you must perform that in a transaction.

Here is a sample query that returns all the students that are enrolled in a particular course.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

privateList<Student>getStudentEnrolledInCourse(Course course){

List<Student>result=newArrayList<>();

RealmQuery<Enrollment>query=realm.where(Enrollment.class);

//Add query conditions

query.equalTo("course.id",course.getId());

//Execute query

RealmResults<Enrollment>enrollments=query.findAll();

for(Enrollment enrollment:enrollments){

result.add(enrollment.getStudent());

}

returnresult;

}

Th above query could also be re-written like this to eliminate the need for RealmQuery object.

It is easy to sort Realm results, here is how we can specify a sort field and a sort order for a list of students.

1

2

3

4

5

6

7

8

9

10

11

publicList<Student>getAllStudents(StringsortColumn,booleansortOrder){

Realm realm=Realm.getDefaultInstance();

RealmResults<Student>students=realm.where(Student.class).findAll();

if(sortOrder){

students.sort(sortColumn,Sort.ASCENDING);

}else{

students.sort(sortColumn,Sort.DESCENDING);

}

realm.close();

returnstudents;

}

Displaying Realm Results in RecyclerView.

Since RealmResults implements the Collections interface, you can pass a RealmResult<T> to any RecyclerViewAdapter that accepts a List<T>. Everything will work fine as long as the ReamlResult object is generated in the same thread where the RecyclerView is being displayed since you cannot pass Realms across threads. We will discuss thread in part 2 of this tutorial.

If you want to go all in with Realm, you can certainly use this modified version of RecyclerView and RecyclerViewAdapter, however, I prefer to stay with the framework’s RecyclerView and RecyclerViewAdapter because of familiarity and maintainability. Here is the StudentRecyclerViewAdapter

Notice that the adapter is accepting a Java List<Students> and we can pass in other parameters that we need such as a listener. Also notice that we have a method that can be used to update data in the RecyclerView, and we can pass in a RealmResult<Students> to this adapter directly like this since we will not be modifying the value.

1

2

3

4

5

6

@Override

publicvoidonResume(){

super.onResume();

RealmResults<Student>students=realm.where(Student.class).findAll();

mAdapter.replaceData(students);

}

Add Initial Data to Realm Database

To add data to Realm database, you create a Realm managed object instance of that class. This must be enclosed in a transaction to be effective. For example, here is how to save a student object to the database.

If the object has a primary key like the Student class has, then you need to provide the primary key when you create the Realm object. Realm also have a copyToRealm() method that copies an unmanaged object into Realm. You saw the usage of that when we created the primary keys in the application class.

If you want to load some initial data to the database, one way you could do that is to add a boolean flag to the SharedPreference that detects if the app is running for the first time or not. Here is an example that I added to the MainActivity.

This method is called from the onCreate() method of the Activity and if this is the first time the app is running it add some default data to the database. Notice that I added an update method to the Course class that updates the instance, this way our add to database method can accept an unmanaged Course object. Here is the update method.

1

2

3

4

5

6

7

8

9

publicvoidupdate(Course course){

courseName=course.getCourseName();

courseDescription=course.getCourseDescription();

credits=course.getCredits();

instructor=course.getInstructor();

courseUrl=course.getCourseUrl();

imageUrl=course.getImageUrl();

}

Summary

This concludes the part 1 of my Realm database in Android tutorial. In this post, I have highlighted the difference between SQLite and Realm since SQLite still remains by far the most popular database for Android. The key difference is Realm’s concept of auto-updating objects, this eliminates the need the need for translation between Java objects and persisted entities in the disk. It also means that you will be performing database access everywhere you need to update data. I have a paid courseand a premium app template where I showed an example of how to use Realm database in an MVP architecture.