Realm Database on Android: Getting Started
Learn how to use Realm database on Android for data persistence. You’ll learn how to add it to your android app and utilize its features. By Rodrigo Guerrero.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Realm Database on Android: Getting Started
30 mins
- Getting Started
- Introducing Realm
- Understanding Realm
- Initializing Realm
- Creating a Schema
- Inserting Objects to the Realm
- Querying the Realm
- Filtering Results
- Sorting Results
- Making Calculations
- Adding Relationships
- Defining One-to-One Relationships
- Defining One-to-Many Relationships
- Using Inverse Relationships
- Migrating the Database
- Updating Objects
- Deleting Objects
- Using Realm Studio
- Comparing Realm with Room
- Where to Go From Here
Realm, a persistence library by MongoDB, lets you easily store objects locally. In this tutorial, you’ll create an app named PetRealm. It uses a Realm database to store information about pets and owners. The app lets the user add pets up for adoption, add owners and show a list of adopted pets. It has the option to adopt a pet by assigning an owner to it.
Over the course of this tutorial, you’ll learn about:
- Realm database
- Schema and entities
- Inserting objects
- Querying and deleting objects
- Relationships between entities
- Realm Studio
- Comparing Realm and Room
Getting Started
Download the materials by clicking the Download Materials button at the top or bottom of this tutorial. Open Android Studio Arctic Fox or later and import the starter project.
Below is a summary of what each package does:
- common: Contains classes used in different sections of the app.
- di: You’ll find classes for providing dependency injection here.
- owners: Contains classes related to owners.
- pets: Contains classes related to pets.
- realm: Here, you’ll find all classes related to Realm.
Build and run the app. You’ll see a screen with the bottom navigation bar, a button to add pets and a spinner at the top.
Press the button and you’ll see the screen to add pets, as shown below:
Dismiss the screen and press the Adopted pets option in the navigation bar. You’ll see the following empty screen:
Finally, press the Owners icon at the bottom navigation bar. This will show the screen for owners. Press the add button and you’ll see the following screen:
The app is pretty much empty. It’s time to learn about Realm and use it to make this app work.
Introducing Realm
It’s time to begin your journey through the Realm. Although it might sound like some kind of magic, a Realm database is an object database management system. This type of database represents its entities as objects.
Importantly, Realm is ACID-compliant. This means a Realm database establishes:
- Atomicity: by grouping all operations in a transaction. If any of the operations fails, then the database rolls back all the operations.
- Consistency: by validating all changes with a schema.
- Isolation: by allowing only one write operation at a time.
- Durability: by saving immediately any change to disk.
To start using Realm, it’s important to learn some concepts to understand how it works.
Understanding Realm
A realm is a set of related objects that have a predefined schema. You can have multiple realms in a single app. Each realm can have different permissions and can include different data types or objects.
A schema is a set of rules that define the types of data the database can store. You define this schema as a set of fields and their data types, in one or several objects called Realm objects.
A Realm object reads and writes data to and from the database. The database persists these objects. They always contain the latest values.
Each schema has a version. A change in the schema consists of adding or removing a field or adding a new realm object. You have to increment the schema version and create a migration to tell the database what changed in the schema.
Now that you know these basic concepts, it’s time to start using Realm. In the following section, you’ll add Realm to the sample project.
Initializing Realm
Open the project build.gradle file. In the dependencies section, add the following line after the navigation-safe-args-gradle-plugin
dependency:
classpath "io.realm:realm-gradle-plugin:10.6.0"
Open the module build.gradle file to apply the plugin. Add the following line after the last apply plugin:
apply plugin: 'realm-android'
Click sync now to synchronize the project. After the sync completes, open PetApplication.kt and add the following line in onCreate()
, importing io.realm.Realm
:
Realm.init(this)
This line initializes Realm and makes it available throughout the app. You can now get an instance of Realm and start adding objects and making queries.
Realm also needs a RealmConfiguration
to setup the database. Open PetsModule.kt from the di package and add the following code, importing io.realm.RealmConfiguration
:
// 1.
private val realmVersion = 1L
@Singleton
@Provides
fun providesRealmConfig(): RealmConfiguration =
// 2.
RealmConfiguration.Builder()
.schemaVersion(realmVersion)
.build()
- Declare a variable with the version for this schema.
- Use
RealmConfiguration.Builder()
to set the schema version and to build theRealmConfiguration
.
Now, it’s time to build and run the app. It runs, but it still shows empty screens. The first step in adding functionality to the app is to create a schema.
Creating a Schema
PetRealm uses two entities: pets and owners. These entities create the schema of the database. The following diagram shows these two entities along with their fields.
Both entities have an id to identify each element, thus making it the primary key. As you can see, there’s a mix of String
, int
and boolean
data types, as well as NULL
and NOT NULL
fields. You need to define this information in each realm object.
You’ll create the pet object first. In the realm package, create a new class and name it PetRealm.kt. Add the following code:
// 1.
open class PetRealm(
@PrimaryKey // 2.
var id: String = ObjectId().toHexString(), // 3.
@Required // 4.
var name: String = "",
@Required
var petType: String = "",
var age: Int = 0,
var isAdopted: Boolean = false,
@DrawableRes
var image: Int? = null // 5.
): RealmObject() // 6.
Import androidx.annotation.DrawableRes
, io.realm.RealmObject
, io.realm.annotations.PrimaryKey
, io.realm.annotations.Required
and org.bson.types.ObjectId
.
There are several things going on here:
- You need to declare every entity class as
open
and every field asvar
. -
@PrimaryKey
indicates the ID field is the primary key. - Realm provides
ObjectId()
to create unique IDs for each object. You’ll use its hex representation. -
@Required
indicates the field requires a value. Add this annotation toname
andpetType
. - For fields that allow
null
values, you can use nullable types and assignnull
as its default value. - Each entity must be a
RealmObject()
.
Create a class in the realm package and name it OwnerRealm.kt. Add the following code:
open class OwnerRealm(
@PrimaryKey
var id: String = ObjectId().toHexString(),
@Required
var name: String = "",
@DrawableRes
var image: Int? = null
) : RealmObject()
Import androidx.annotation.DrawableRes
, io.realm.RealmObject
, io.realm.annotations.PrimaryKey
, io.realm.annotations.Required
and org.bson.types.ObjectId
.
With this code, you add the OwnerRealm
entity to the Realm database.
Build and run the app. Behind the scenes, Realm adds PetRealm
and OnwerRealm
to the schema. However, you’ll still see an empty screen.
Now that you have created the schema, it’s time to insert some objects.
Inserting Objects to the Realm
Inserting objects to the database is part of Realm’s write transactions. All operations should happen in a transaction. A transaction is a group of read and write operations Realm executes as a single operation. Every operation in the transaction should succeed for the transaction to complete successfully. If any operation fails, then the entire transaction fails.
Open PetDatabaseOperations.kt file in the realm package. Add the following parameter to the class constructor:
private val config: RealmConfiguration
With this line, you provide RealConfiguration
to this class.
Modify insertPet()
as follows:
suspend fun insertPet(name: String, age: Int, type: String, image: Int?) {
// 1.
val realm = Realm.getInstance(config)
// 2.
realm.executeTransactionAwait(Dispatchers.IO) { realmTransaction ->
// 3.
val pet = PetRealm(name = name, age = age, petType = type, image = image)
// 4.
realmTransaction.insert(pet)
}
}
Import io.realm.Realm
, io.realm.kotlin.executeTransactionAwait
and kotlinx.coroutines.Dispatchers
.
In this code, you:
- Get an instance of Realm, using
RealmConfiguration
. - Use
executeTransactionAwait()
withDispatchers.IO
to execute a transaction in a background thread and wait until it finishes. - Create a new
PetRealm
object. - Insert the new
PetRealm
object.
It’s crucial that you use the Realm instance, called realmTransaction
in the code above, provided in the lambda function to insert the newly created object.
Build and run the app. Press the add button and add a new pet as follows:
Press Add Pet and you’ll insert the new pet to the database. You’ll have to trust me on this, because the app doesn’t display anything yet.
Add the code to insert an owner. Open OwnerDatabaseOperations.kt and add the following parameter to its constructor:
private val config: RealmConfiguration
This line provides RealmConfiguration
to this class.
Modify insertOwner()
as follows:
suspend fun insertOwner(name: String, image: Int?) {
// 1.
val realm = Realm.getInstance(config)
// 2.
realm.executeTransactionAwait(Dispatchers.IO) { realmTransaction ->
// 3.
val owner = OwnerRealm(name = name, image = image)
// 4.
realmTransaction.insert(owner)
}
}
This code follows the same steps as adding a pet:
- Get an instance of Realm.
- Use
executeTransactionAwait()
. - Create the new object.
- Insert it in the database.
Build and run the app. Press the Owners button. Add a name and long-press an image to select it, as follows:
Press Add Owner and you’ll have to trust me again that you added the new owner to the database.
Don’t worry. The next step is to learn how to query the database and display the information.
Querying the Realm
Realm provides a query engine that allows you to find, filter and sort objects. Each query result is a live object. This means it contains the latest data and, if you decide to modify the result, it will modify the stored object.
Open PetDatabaseOperations.kt. Add the following code at the end of the class:
private fun mapPet(pet: PetRealm): Pet {
return Pet(
name = pet.name,
age = pet.age,
image = pet.image,
petType = pet.petType,
isAdopted = pet.isAdopted,
id = pet.id
)
}
mapPet()
maps the PetRealm
object to the Pet
UI object.
Modify retrievePetsToAdopt()
the following way:
suspend fun retrievePetsToAdopt(): List<Pet> {
// 1.
val realm = Realm.getInstance(config)
val petsToAdopt = mutableListOf<Pet>()
// 2.
realm.executeTransactionAwait(Dispatchers.IO) { realmTransaction ->
petsToAdopt.addAll(realmTransaction
// 3.
.where(PetRealm::class.java)
// 4.
.findAll()
// 5.
.map {
mapPet(it)
}
)
}
return petsToAdopt
}
To retrieve pets up for adoption, you:
- Get the Realm instance.
- Use
executeTransactionAwait()
. - Use
where(PetRealm::class.java)
to retrievePetRealm
objects. -
findAll()
executes the query and returns everyPetRealm
object. - Map
PetRealm
toPet
objects.
Now that you’re on a roll, open OwnerDatabaseOperations.kt and modify retrieveOwners()
as follows:
suspend fun retrieveOwners(): List<Owner> {
// 1.
val realm = Realm.getInstance(config)
val owners = mutableListOf<Owner>()
// 2.
realm.executeTransactionAwait(Dispatchers.IO) { realmTransaction ->
owners.addAll(realmTransaction
// 3.
.where(OwnerRealm::class.java)
// 4.
.findAll()
// 5.
.map { owner ->
Owner(
name = owner.name,
image = owner.image,
id = owner.id
)
}
)
}
return owners
}
To retrieve the owners, you:
- Get the Realm instance.
- Use
executeTransactionAwait()
. - Use
where(OwnerRealm::class.java)
to retrieveOwnerRealm
objects. -
findAll()
executes the query. - Map
OwnerRealm
toOwner
objects.
Now, build and run the app. You’ll see the pets you added previously. If you navigate to the Owners screen, you see the owner you added too:
Well done! Now you can see data from the database. However, there’s a bug, and you can make a couple improvements.
Filtering Results
The bug with retrieving pets is that the query will return every pet, both adopted and in adoption. But Realm provides filtering operators that help filter the results based on certain values.
In PetDatabaseOperations.kt, modify retrievePetsToAdopt()
by adding the following line after .where()
:
.equalTo("isAdopted", false)
This operation will return only the instances of PetRealm
that have isAdopted
as false
.
Now, modify retrieveAdoptedPets()
, like this:
suspend fun retrieveAdoptedPets(): List<Pet> {
val realm = Realm.getInstance(config)
val adoptedPets = mutableListOf<Pet>()
realm.executeTransactionAwait(Dispatchers.IO) { realmTransaction ->
adoptedPets.addAll(realmTransaction
.where(PetRealm::class.java)
.equalTo("isAdopted", true)
.findAll()
.map {
mapPet(it)
}
)
}
return adoptedPets
}
To retrieve the adopted pets, isAdopted
should be true
.
You can now implement the filter by pet type in the “Pets to adopt” list. Modify retrieveFilteredPets()
as follows:
suspend fun retrieveFilteredPets(petType: String): List<Pet> {
val realm = Realm.getInstance(config)
val filteredPets = mutableListOf<Pet>()
realm.executeTransactionAwait(Dispatchers.IO) { realmTransaction ->
filteredPets.addAll(realmTransaction
.where(PetRealm::class.java)
// 1.
.equalTo("isAdopted", false)
// 2.
.and()
// 3.
.beginsWith("petType", petType)
.findAll()
.map {
mapPet(it)
}
)
}
return filteredPets
}
This code executes a transaction to retrieve PetRealm
objects. The filtering works the following way:
- Condition to filter
PetRealm
objects that haveisAdopted
asfalse
. -
and()
is a logical operator that indicates the result should meet both conditions. Here you can find a list of all the logical operators Realm supports. - Condition to filter
PetRealm
objects that have theirpetType
field with the provided value.
After executing the query and mapping the results to Pet
objects, the method returns the list with pets of the selected petType.
Build and run the app. Add different kinds of pets to the list as shown in the next image:
Select a pet type from the top spinner:
Great! You have fixed the bug and implemented the filtering functionality. Now, you can add a slight improvement to the owners list.
Sorting Results
Sometimes you have to sort results from the queries before displaying them on the screen. Go to OwnerDatabaseOperations.kt and add the following line to the query in retrieveOwners()
after .findAll()
:
.sort("name", Sort.ASCENDING)
Import io.realm.Sort
.
This operation sorts the results by the name field in ascending order.
Build and run the app. Add other owners to the list. You’ll see the owners sorted by name, as in the following image:
Each owner displays the number of pets it owns, but so far, that number is always zero. How can you get the number of pets using Realm?
Making Calculations
Another value you need to display is the number of pets each owner has. Realm has different aggregate operators. These operators traverse a list of Realm objects and calculate a value.
Open OwnerDatabaseOperations.kt and add the following code:
private fun getPetCount(realm: Realm, ownerId: String): Long {
// 1.
return realm.where(PetRealm::class.java)
// 2.
.equalTo("owner.id", ownerId)
// 3.
.count()
}
In this code, you:
- Query the realm to get
PetRealm
objects. - Use
owner.id
to filter by owner ID. - Count the number of pets the owner has using
.count()
Modify the Owner
creation in retrieveOwners()
to add numberOfPets
, as follows:
Owner(
name = owner.name,
image = owner.image,
id = owner.id,
numberOfPets = getPetCount(realmTransaction, owner.id)
)
You now have pets up for adoption and owners. It’s time to let the owners adopt some pets.
Adding Relationships
Realm provides a way to implement one-to-one, one-to-many and inverse relationships.
Defining One-to-One Relationships
A one-to-one relationship is when an object relates at most with one instance of another object. In PetRealm, this happens with a pet that can have at most one owner, as shown in the following diagram:
Open PetRealm.kt and add the following parameter to the class constructor:
var owner: OwnerRealm? = null
This line tells the PetRealm
object it can have at most one OwnerRealm
. But there’s another relationship in this database: One owner can have multiple pets.
Defining One-to-Many Relationships
A one-to-many relationship is when one object relates to multiple objects. This is the scenario when a single owner can have multiple pets, as shown in the following diagram:
Open OwnerRealm.kt and add the following parameter to the class constructor:
var pets: RealmList<PetRealm> = RealmList()
Import io.realm.RealmList
.
A RealmList
allows the OwnerRealm
to have a one-to-many relationship with PetRealm
.
Good job! You have implemented two relationships in PetRealm. However, Realm provides a third type of relationship that can make your life easier.
Using Inverse Relationships
The relationships you implemented are unidirectional. This means when you get the query results for pets, their owners won’t be available in the results. This is a bit of a problem, because you have to show the pet owner in the adopted pets list.
Realm provides inverse relationships to solve this. Go to PetRealm.kt and replace the owner
parameter in the constructor with the following lines:
@LinkingObjects("pets") // 1.
val owner: RealmResults<OwnerRealm>? = null // 2.
Import io.realm.annotations.LinkingObjects
and io.realm.RealmResults
.
To add an inverse relationship, you must:
- Add
@LinkingObjects
annotation, passing as parameter the name of the field you’re adding the relationship to. The field inOwnerRealm
you want to link ispets
. - The field should be
val
and of typeRealmResults
.
With this improvement, you now can get the OwnerRealm
information in the PetRealm
query.
It’s time to build and run the app. Oh no! The app crashes. Taking a look at Logcat, you’ll find the following error:
Process: com.raywenderlich.android.petrealm, PID: 16049
io.realm.exceptions.RealmMigrationNeededException: Migration is required due to the following errors:
- Property 'OwnerRealm.pets' has been added.
After adding new fields, you have to create a migration to tell the database what has changed.
Migrating the Database
Realm maps each state of the database schema to a specific version. If this schema changes, like it did when you added the relationships in the previous section, the version needs to increment. You also have to tell Realm how to handle the differences in the schema by creating a migration.
Create a new file in the realm package and name it Migrations.kt. Add the following code:
// 1.
val migration = RealmMigration { realm, oldVersion, newVersion ->
// 2.
if (oldVersion == 1L) {
// 3.
val ownerSchema = realm.schema.get("OwnerRealm")
val petSchema = realm.schema.get("PetRealm")
// 4.
petSchema?.let {
ownerSchema?.addRealmListField("pets", it)
}
}
}
Import io.realm.RealmMigration
.
To add a migration, you must:
- Create a
val
of typeRealmMigration
. - Define what to do for each version change.
oldVersion
will hold the value for the previous schema version. - Get each schema you need to modify.
-
ownerSchema
needs a newRealmList
field of typePetRealm
. UseaddRealmListField()
with the name of the field and the schema type the field needs.
Open PetsModule.kt in the di package. Increase the schema version like this:
private val realmVersion = 2L
Modify providesRealmConfig()
as follows:
RealmConfiguration.Builder()
.schemaVersion(realmVersion)
.migration(migration)
.build()
Use migration()
to add the migration. If you build and run the app, it runs again. Everything’s working as expected. It’s time for the most important step: adopting pets.
Updating Objects
To update Realm objects, you need to query for the object you want to update and assign the new values.
To adopt a pet, isAdopted
should change to true
and the pet gets assigned to the owner. Open OwnerDatabaseOperations.kt and modify updatePets()
like this:
suspend fun updatePets(petId: String, ownerId: String) {
val realm = Realm.getInstance(config)
// 1.
realm.executeTransactionAwait(Dispatchers.IO) { realmTransaction ->
// 2.
val pet = realmTransaction
.where(PetRealm::class.java)
.equalTo("id", petId)
.findFirst()
// 3.
val owner = realmTransaction
.where(OwnerRealm::class.java)
.equalTo("id", ownerId)
.findFirst()
// 4.
pet?.isAdopted = true
// 5.
owner?.pets?.add(pet)
}
}
In this code, you:
- Add a transaction to run the write operation.
- Query for the lucky pet.
- Query for the owner who will adopt the pet.
- Update
isAdopted
value. - Add the pet to the owner’s pet list.
Open PetDatabaseOperations.kt and modify mapPet()
as follows:
private fun mapPet(pet: PetRealm): Pet {
return Pet(
name = pet.name,
age = pet.age,
image = pet.image,
petType = pet.petType,
isAdopted = pet.isAdopted,
id = pet.id,
ownerName = pet.owner?.firstOrNull()?.name
)
}
You can take advantage of the relationships you added previously and add the owner’s name to each adopted pet.
Build and run the app. Press Adopt Me in any pet and select an owner. Make a single owner to adopt several pets. Navigate to the Adopted Pets screen and you’ll see the lucky pets:
Open the Owners screen. You can see the number of pets each owner adopted.
Great job! You helped the pets find an owner. However, there could be times when a pet runs away or an owner is no longer interested in adopting. In this case, you need to be able to remove them from the app.
Deleting Objects
Open PetDatabaseOperations.kt and modify removePet()
as follows:
suspend fun removePet(petId: String) {
val realm = Realm.getInstance(config)
realm.executeTransactionAwait(Dispatchers.IO) { realmTransaction ->
// 1.
val petToRemove = realmTransaction
.where(PetRealm::class.java)
.equalTo("id", petId)
.findFirst()
// 2.
petToRemove?.deleteFromRealm()
}
}
To delete an object, you must:
- Query for the object you want to remove. This query should be in a transaction.
- Use
deleteFromRealm()
in that object to remove it from the database.
Removing objects that have a relationship requires an extra step. Open OwnerDatabaseOperations.kt and update removeOwner()
like this:
suspend fun removeOwner(ownerId: String) {
val realm = Realm.getInstance(config)
realm.executeTransactionAwait(Dispatchers.IO) { realmTransaction ->
// 1.
val ownerToRemove = realmTransaction
.where(OwnerRealm::class.java)
.equalTo("id", ownerId)
.findFirst()
// 2.
ownerToRemove?.pets?.deleteAllFromRealm()
// 3.
ownerToRemove?.deleteFromRealm()
}
}
You must:
- Query for the owner you want to remove.
- Use
deleteAllFromRealm()
to delete all the pets the owner has. - Finally, delete the owner object.
Build and run the app and delete some pets and owners. You can see that the pets from the owner get deleted if you remove the owner.
Now, PetRealm is complete. However, how can you debug the data in the Realm database?
Using Realm Studio
Realm Studio is a visual tool that helps you view, edit and create Realm databases. Head to Realm Studio website, and download and install version 11.1.0.
In Android Studio, go to View ▸ Tool Windows ▸ Device File Explorer. Select the emulator you’re using from the dropdown. Navigate to data ▸ data ▸ com.raywenderlich.android.petrealm ▸ files. Here, you can find the database with the name default.realm as shown in the next image:
Right-click the Realm filename and select Save As. Select a location on your computer and save the file. Now, open Realm Studio and press Open Realm file. Navigate to the saved file and select it. You’ll see the OwnerRealm schema first:
In it, you can see the current values for ID, name, image and pets. As you can see, the pets value is a list of pet IDs.
Select PetRealm in the left sidebar and you’ll see the following:
Click Create PetRealm at the top right and add the following pet:
Click Create and you’ll see the new pet added to the database.
Other operations you can do in Realm Studio are:
- Creating new classes
- Adding properties to existing classes
- Querying the classes
If you want to learn more about Realm Studio, visit the official documentation.
Now that you have learned about Realm, it’s time to talk about Room, one of the most popular persistence libraries in Android. What are the differences between Room and Realm?
Comparing Realm with Room
Jetpack provides Room, a data persistence library that uses SQLite behind the scenes.
You can store only primitive types and strings in Room. You can use type converters to convert an object to primitive types and store it in the database. For example, if you need to store a Date
, first you must convert it to Long
.
Room doesn’t allow entity objects to reference one another. This means when you work with relationships, you have to create intermediate classes to model these relationships between the entities. Here, you can find more information about relationships in Room.
On the other hand, Realm handles the object persistence itself. This means you can store primitive types, strings and any type of object and lists. Each object has to be an instance of RealmObject.
As you learned in the relationships section, you can use an inverse relationship to get the data of the related entity. You don’t have to do multiple queries to obtain the information you need.
Realm has other advanced functions like:
- Creating Realm Apps to sync data in real time between different platforms.
- Using App Services to authenticate and manage permissions.
- Working seamlessly with MongoDB.
Where to Go From Here
You can download the final project by using the Download Materials button at the top or bottom of the tutorial.
Realm database lets you store and access objects locally. You can insert data, query, filter and sort results, update and delete objects. It also provides a simple way to create relationships between entities.
If you want to learn more about Room, you can watch the Room Database: Getting Started video course. If you want to learn about DataStore, another way to store data in Android, visit DataStore Tutorial For Android: Getting Started. To learn more about other options to save data in Android, head to the Saving Data on Android video course.
I hope you enjoyed this tutorial on Realm. If you have any questions or comments, please join the forum discussion below.
All videos. All books.
One low price.
A Kodeco subscription is the best way to learn and master mobile development — plans start at just $19.99/month! Learn iOS, Swift, Android, Kotlin, Flutter and Dart development and unlock our massive catalog of 50+ books and 4,000+ videos.
Learn more