banner



Design A Tool For Pet Adoption Java

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.

Pets to adopt empty screen

Press the button and you'll see the screen to add pets, as shown below:

Add pets screen

Dismiss the screen and press the Adopted pets option in the navigation bar. You'll see the following empty screen:

Adopted pets 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:

Add owner 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.

Android teaching diagram

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'        

Note: Installing Realm this way will add every Realm dependency. However, it's possible to add only the dependencies your app needs. You can refer to the official documentation to learn how to implement this.

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()        
  1. Declare a variable with the version for this schema.
  2. Use RealmConfiguration.Builder() to set the schema version and to build the RealmConfiguration.

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.

PetRealm database entities

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.

Note: Realm supports different types of fields. For a complete list of these fields, visit the official documentation.

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:

  1. You need to declare every entity class as open and every field as var.
  2. @PrimaryKey indicates the ID field is the primary key.
  3. Realm provides ObjectId() to create unique IDs for each object. You'll use its hex representation.
  4. @Required indicates the field requires a value. Add this annotation to name and petType.
  5. For fields that allow null values, you can use nullable types and assign null as its default value.
  6. 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.

Still empty app

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:

  1. Get an instance of Realm, using RealmConfiguration.
  2. Use executeTransactionAwait() with Dispatchers.IO to execute a transaction in a background thread and wait until it finishes.
  3. Create a new PetRealm object.
  4. 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:

Adding a new pet

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:

  1. Get an instance of Realm.
  2. Use executeTransactionAwait().
  3. Create the new object.
  4. 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:

Adding an owner

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:

  1. Get the Realm instance.
  2. Use executeTransactionAwait().
  3. Use where(PetRealm::class.java) to retrieve PetRealm objects.
  4. findAll() executes the query and returns every PetRealm object.
  5. Map PetRealm to Pet 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:

  1. Get the Realm instance.
  2. Use executeTransactionAwait().
  3. Use where(OwnerRealm::class.java) to retrieve OwnerRealm objects.
  4. findAll() executes the query.
  5. Map OwnerRealm to Owner 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:

Pet added

Owner added

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:

  1. Condition to filter PetRealm objects that have isAdopted as false.
  2. 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.
  3. Condition to filter PetRealm objects that have their petType 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:

Pets to adopt screen

Select a pet type from the top spinner:

Filtered pets screen

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:

Owners screen

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:

  1. Query the realm to get PetRealm objects.
  2. Use owner.id to filter by owner ID.
  3. 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:

PetRealm one-to-one relationship

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:

PetRealm one-to-many relationship

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:

  1. Add @LinkingObjects annotation, passing as parameter the name of the field you're adding the relationship to. The field in OwnerRealm you want to link is pets.
  2. The field should be val and of type RealmResults .

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:

  1. Create a val of type RealmMigration.
  2. Define what to do for each version change. oldVersion will hold the value for the previous schema version.
  3. Get each schema you need to modify.
  4. ownerSchema needs a new RealmList field of type PetRealm. Use addRealmListField() 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:

  1. Add a transaction to run the write operation.
  2. Query for the lucky pet.
  3. Query for the owner who will adopt the pet.
  4. Update isAdopted value.
  5. 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:

Adopted pets screen

Open the Owners screen. You can see the number of pets each owner adopted.

Owners with pets screen

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:

  1. Query for the object you want to remove. This query should be in a transaction.
  2. 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:

  1. Query for the owner you want to remove.
  2. Use deleteAllFromRealm() to delete all the pets the owner has.
  3. 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:

Realm database in file explorer

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:

OwnerRealm in Realm Studio

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:

PetRealm in Realm Studio

Click Create PetRealm at the top right and add the following pet:

Create an object in Realm Studio

Click Create and you'll see the new pet added to the database.

Pet created in Realm Studio

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.

raywenderlich.com Weekly

The raywenderlich.com newsletter is the easiest way to stay up-to-date on everything you need to know as a mobile developer.

Get a weekly digest of our tutorials and courses, and receive a free in-depth email course as a bonus!

Design A Tool For Pet Adoption Java

Source: https://www.raywenderlich.com/25768145-realm-database-on-android-getting-started

Posted by: rankintwen1982.blogspot.com

0 Response to "Design A Tool For Pet Adoption Java"

Post a Comment

Iklan Atas Artikel

Iklan Tengah Artikel 1

Iklan Tengah Artikel 2

Iklan Bawah Artikel