Repository
https://github.com/realm/realm-java
What Will I Learn?
- You will learn about Queries in Realm
- chaining Queries on child objects
- how to query relationships
Requirements
- An Integrated Development Environment(IDE) for building Android Application(e.g Android Studio, IntelliJ)
- Android Device/Virtual Device.
- Little Experience in working with Realm Java.
- Java Programming Experience.
- Of course, willingness to learn
Resources
- Retrofit Website. https://realm.io/
- Retrofit Github. - https://github.com/realm
- Retrofit License - Apache License
- Lombok Project - https://projectlombok.org/
Difficulty
- Intermediate
Tutorial Duration - 35 - 40Mins
Tutorial Content
In Today's tutorial, we are going to continue learning about queries in Realm. Previous tutorial links can be found below :
In part 1 of this tutorial, we looked at three predicates - contains()
,beginsWith()
,in()
and we were able to understand this by creating an android application that demonstrates the uses of this predicates.
We learnt that predicates that the form of `predicateName("field","query string",OPTIONAL ARGUMENT);
e.g - contains("name","Edet Ebenezer",Case.SENSITIVE);
The above code checks if any object's name field contains the text - Edet Ebenezer
without paying attention to the cases.
We also learned how to use the third optional argument for the predicates which are Case.INSENSITIVE
and Case.SENSITIVE
where the former ignores the case of the query string to the contents of the field specified and the latter doesn't ignore it.
In the part two of this tutorial series, we looked at the - beginsWith()
, endsWith()
,like()
predicates.
The beginsWith() predicate checks to ensure that an object's field begins with the query string passed as the second argument to it and then it also takes the optional third case argument - Case.SENSITIVE
or Case.INSENSITIVE
, while the endsWith() predicate does the opposite of the beginsWith()
predicate where it checks to ensure that an object's field ends with the query string passed as the second argument to it, and it also takes the third optional case argument.
Meanwhile, the like() predicate performs a glob style wildcard matching with the following :
- ? matches a single unicode character
*
matches 0 or more unicode characters.
In the part three of this series, we looked at the between()
,greaterThan()
,lessThan()
,greaterThanOrEqualTo()
,lessThanOrEqualTo()
predicates.
We learned the following about the above predicates :
- between() predicates takes three arguments, first the name of the object's field - e.g "age", secondly a lower bound and then lastly an upper bound e.g
between("age", 5, 9)
- this gives returns objects that their age field is between 5 and 9. - greaterThan() predicate takes two arguments, first the field name and then the query integer e.g -
greaterthan("age", 56)
which returns objects that their age field's value is greater than 56 - lessThan() predicates takes the same arguments as the
greaterThan()
predicates e.g -lessThan("age", 20)
which returns object that their age field value is less than 20. - greaterThanOrEqualTo() predicates takes the same number of argument as specified above e.g
greaterThanOrEqualTo("age", 30)
which return objects that their age field has a value greater than or equal to 30. - lessThanOrEqualTo() predicate takes the same number of argument as specified above e.g
lessThanOrEqualTo("age", 75)
which returns the objects that their age field value is less than or equal to 75.
In part 4 of this tutorial, we looked at three predicates namely :
beginGroup()
andendGroup()
predicate starts and ends a group of predicates and also specifies the order of evaluation - e.g
RealmResults<Person> result = realm.where(Person.class)
.greaterThan("age", 15)
.beginGroup()
.contains("email","gmail.com")
.and()
.contains("address","Nigeria")
.endGroup()
.findAll();
The above query finds Person objects that are older than 15 and their email contains the words gmail.com AND their address contains the words Nigeria.
2.not()
predicate negates a predicate - e.g
RealmResults<Person> result = realm.where(Person.class)
.greaterThan("age", 15)
.not()
.beginGroup()
.contains("email","gmail.com")
.and()
.contains("address","Nigeria")
.endGroup()
.findAll();
The above code does the opposite of the one before it meaning it queries for Person objects that are older than 15, but their emails don't contain the word - gmail.com and their address dont contain the word - Nigeria
3.sort()
predicate is used to sort results of queries and it takes two arguments meanwhile it sorts by ASCENDING order by default -
- The field on which the sorting will be done.
- An optional sort parameter - Sort.ASCENDING or Sort.DESCENDING.
e.g result = result.sort("age",Sort.ASCENDING)
or result = result.sort("age",Sort.DESCENDING)
In part five of this tutorial series, we looked at the distinct() predicate and also how to chain queries.
We learnt the following -
- The distinct() predicate gives you unique value in the field name specified - e.g
RealmResults<Person> result = realm.where(Person.class)
.distinct("name")
.findAll();
which means that if in the realm database, there exist two objects with the name - Ebenezer only one of those objects will be returned in the result
object.
- We learned how to chain queries - where one can query a RealmQuery object for a particular match - e.g
RealmResults<Person> result = realm.where(Person.class)
.lessThan("age",Integer.valueOf(age))
.findAll();
Person singlePerson = result.where().equalTo("age",30).findFirst();
The singlePerson object is gotten by querying the result object for an object that the age field value is 30 and it returns the first object that matches the condition (i.e findFirst())
In today's tutorial, we are going to be learning how to chain queries on a child object and also how to link queries.
Querying a child object is making a query based on the relationship that exists between two models while link queries is passing the value of a query result set to another query.
This will be more explained as we go.
In order to achieve this, we are going to modify the existing Android application that we have developed in tutorials 1 - 5.
Outline
- Dependencies Used
- Modifications to Existing Files / Creation of new Files
- Relationship Query
Dependencies used
Please refer to the first tutorial for the dependencies used in this tutorial as no new dependencies have been added
Modifications to Existing Files / Creation of new Files
In order for us to begin, we have to make some modifications to some existing file and also creating some new files.
- We will be creatin two new model classes - PersonsWithPets/ Creation of new Files and Cat model classes.
- We will be modifying our MainActivity.java file to create new Realm Objects.
- We will be modifying our
NumericQuery
Fragment java class file to illustrate chaining Queries on child objects. - We will be creating a new fragment -
LinkFragment
where we will be illustrating how to query relationships.
Creating new Model Class
We are going to be creating two java class files - PersonsWIthPet and Cat java class file which will extend from RealmObject
.
PersonsWithPet
@Getter
@Setter
public class PersonsWithPet extends RealmObject{
String name;
int phone_number;
int age;
public RealmList<Cat> cats;
}
Cat
@Getter
@Setter
public class Cat extends RealmObject {
public int age;
public String name;
public String color;
@LinkingObjects("cats")
public final RealmResults<PersonsWithPet> owners = null;
}
Modifications to MainActivity.java
In our MainActivity.java class file, we are going to be adding a new method - createPersonsWithPets()
which we will be using to create two PersonsWithPet objects and four Cat object which we will be using throughout todays tutorial.
@Override
protected void onCreate(Bundle savedInstanceState) {
// Previosuly Existing Codes
createPersonsWithPets();
}
private void createPersonsWithPets() {
realm.beginTransaction();
PersonsWithPet Eunice = realm.createObject(PersonsWithPet.class);
Eunice.phone_number = 223344;
Eunice.age = 32;
Eunice.name = "Eunice Bulus";
PersonsWithPet Edet = realm.createObject(PersonsWithPet.class);
Edet.phone_number = 12345;
Edet.age = 35;
Edet.name = "Edet Ebenezer";
Cat salvador = realm.createObject(Cat.class);
salvador.name = "Salvador";
salvador.color = "White";
salvador.age = 1;
Cat jack = realm.createObject(Cat.class);
jack.name = "Jack";
jack.color = "Brown";
jack.age = 3;
Cat jill = realm.createObject(Cat.class);
jill.name = "Jill";
jill.color = "Black";
jill.age = 2;
Cat bruno = realm.createObject(Cat.class);
bruno.name = "Bruno";
bruno.color = "White";
bruno.age = 3;
Eunice.cats.add(jack);
Eunice.cats.add(jill);
Eunice.cats.add(bruno);
Edet.cats.add(salvador);
Edet.cats.add(jack);
Edet.cats.add(jill);
Edet.cats.add(bruno);
realm.commitTransaction();
}
Code Explanation
We created two PersonWithPet objects - Edet
and Eunice
and four Cat objects - salvador
,jack
,jill
,bruno
.
We then initialize a many-to-many relationship between the cats and the person objects - e.g - Eunice.cats.add(jack);
.
NumericQuery
In our NumericQuery
Fragment java file, we are going to be modifying the greaterThan()
predicate code block and illustrating how to chain queries on child objects for instance, if we were looking for all Person
objects that are greater than 25 years and have a cat thats a year old, we then have to chain a query that gets the Person
objects first and then pass the relationship we intend to query being as the field name - cats.age
to get the cats age (NB: The cat.age
indicates a path through the relationships using a period .
as the seperator).
In the onClick()
method of our GO
button , we edit the code as follows:
@OnClick(R.id.go_btn)
public void onViewClicked() {
String age = queryAge.getText().toString();
String catAge = queryCatAge.getText().toString();
StringBuilder toDisplay = new StringBuilder();
if (!(age.isEmpty() && catAge.isEmpty())){
RealmResults<PersonsWithPet> persons = realm.where(PersonsWithPet.class)
.greaterThan("age",Integer.valueOf(age))
.equalTo("cats.age",Integer.valueOf(catAge))
.findAll();
toDisplay.append("Chaining Queries in Child Objects");
toDisplay.append("There are - " + persons.size() + " Persons that are below "+age+" and have a cat that's "+catAge+" years old\n\n");
int i = 0;
while(i < persons.size()){
toDisplay.append(persons.get(i).name+" with phone number : "+persons.get(i).phone_number+" Age : "+persons.get(i).age+"\n\n\n");
i++;
}
resultTv.setText(toDisplay.toString());
}
else
showToast("No Field can be empty");
Code Explanation
- We get the value of the two EditText's and store in string variables - age and catAge.
- We declare a StringBuilder object - toDisplay
- We use an if statement to ensure that the age and catAge variables are not empty else we simply call the showToast() method with the argument -"No Field can be Empty and First Age must be greater than Second Age".
- We create a RealmResult object - persons and then we use the greaterThan() predicate with the where statement specifying the PersonsWithPet class specifying the age field passing the age entered by the user then we use the equalTo() predicate specifying the child object -
cats.age
which refers to the cat object's age field and the second argument being the age of the cat entered by the user - catAge. - We append the text - Chaining Queries in Child Objects
- We then use a while loop to loop through the objects that match our query and then display the details of each object.
- name : result.get(i).name
- phone number : result.get(i).phone_number
- age : result.get(i).age
- We then display our result in our TextView -
resultTv.setText(toDisplay.toString());
#### Creating LinkFragment We are going to be creating a new Fragment - `LinkFragment` where we are going to be illustrating different ways of querying relationships or links if you like.
To create a new blank fragment, right click on your java folder => New => Fragment => Blank Fragment or see image below -
Next, Name your fragment - LinkFragment and then do not forget to uncheck the - Include fragment factory methods ? and include interface callbacks checkboxes , see image below for details
In our LinkFragment layout file - link_fragment.xml we are going to be adding the following views -
- Four EditText
- One for cat name - id => cat_name
- One for cat age - id => _cat_age
- One for cat color 1 - id => cat_color_one
- One for cat color 2 - id => cat_color_two
- A Button for starting the start Query - id => go_btn
- Two TextViews
- One for displaying the text - Result
- The other for displaying the results gotten from our query - id => result
link_fragment.xml
//RootLayout - ScrollView
//RelativeLayout
<EditText
android:id="@+id/cat_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Enter Cat Name"
android:inputType="text" />
<EditText
android:id="@+id/cat_age"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/cat_name"
android:hint="Enter Cat Age"
android:inputType="number" />
<EditText
android:id="@+id/cat_color_one"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/cat_age"
android:hint="Enter Cat color 1"
android:inputType="text" />
<EditText
android:id="@+id/cat_color_two"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/cat_color_one"
android:hint="Enter Cat color 2"
android:inputType="text" />
<Button
android:id="@+id/go_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/cat_color_two"
android:layout_centerInParent="true"
android:text="GO" />
<TextView
android:id="@+id/result_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/go_btn"
android:layout_marginTop="10dp"
android:padding="10dp"
android:text="Result" />
<TextView
android:id="@+id/result"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/result_text"
android:layout_marginTop="10dp"
android:padding="10dp"
android:text="Result will be displayed here"
android:textColor="@android:color/black"
android:textSize="17sp" />
Next, in our LinkFragment
java class file, we are going to be injecting our views using butterknife and also the onClick method for our go button.
We are going to be illustrating how to query relationships in realm and we are going to be using the case study of Persons that have pets.
Case Studies
- We want to get
PersonWithPet
objects that haveCats
of a certain age and in that result set, we are going to find thosePersonWithPet
objects having cats of a particular color (i.e black) and still in this new result set we will finally findPersonWithPet
objects that have cats of another color (i.e brown) - We are going to be querying reverse relationships to get :
- cats that belongs to a particular owner.
- owners of a particular cat.
To inject our views and onClick method using butterknife, place your cursor on the layout name on the setContentView() method and then on windows - alt + ins => Generate ButterKnife Injection and then select the checkboxes shown in the image below:
Generated Code
Unbinder unbinder;
@BindView(R.id.cat_color_one)
EditText cat_color_one;
@BindView(R.id.cat_age)
EditText catAge;
@BindView(R.id.result)
TextView result;
@BindView(R.id.cat_color_two)
EditText catColorTwo;
@BindView(R.id.cat_name)
EditText catName;
private Realm realm;
...
@OnClick(R.id.go_btn)
public void onViewClicked() {}
Next, we need to create a Realm object and get a default instance inside our onCreate()
method - private Realm realm;
and realm = Realm.getDefaultInstance();
Next, in generated onCLick()
method for our GO
button, we need to edit it as below:
@OnClick(R.id.go_btn)
public void onViewClicked() {
String string_color_one = cat_color_one.getText().toString();
String string_color_two = catColorTwo.getText().toString();
String cat_age = catAge.getText().toString();
String cat_name = catName.getText().toString();
StringBuilder toDisplay = new StringBuilder();
if (!(string_color_one.isEmpty() && cat_age.isEmpty() && cat_age.isEmpty() && cat_name.isEmpty())) {
RealmResults<PersonsWithPet> persons = realm.where(PersonsWithPet.class)
.equalTo("cats.age", Integer.valueOf(cat_age))
.findAll()
.where()
.equalTo("cats.color", string_color_one)
.findAll()
.where()
.equalTo("cats.color", string_color_two)
.findAll();
toDisplay.append("Query Relationship Result\n\n");
toDisplay.append("There are - " + persons.size() + " Persons that are below " + string_color_one + " and have a cat thats " + cat_age + " years old\n\n");
int i = 0;
while (i < persons.size()) {
toDisplay.append((i +1)+ ". Name : " + persons.get(i).name + " with phone number : " + persons.get(i).phone_number + " Age : " + persons.get(i).age + "\n\n\n");
i++;
}
toDisplay.append("Reverse Relationships Linking\n\n");
RealmResults<Cat> cats = realm.where(Cat.class).contains("owners.name", "Edet").findAll();
toDisplay.append("There are - " + cats.size() + " cats that has an owner with the name such as Edet\n\n");
i = 0;
while (i < cats.size()) {
toDisplay.append((i +1)+ ". Name : " + cats.get(i).name + " Age : " + cats.get(i).age + " Color : " + cats.get(i).color + "\n\n\n");
i++;
}
toDisplay.append("Reverse Relationships Linking 2\n\n");
i = 0;
RealmResults<Cat> kittens = realm.where(Cat.class).equalTo("color", string_color_one).equalTo("name", cat_name).findAll();
for (Cat kitten : kittens) {
RealmResults<PersonsWithPet> owners = kitten.getOwners();
toDisplay.append((i +1)+ ". Name : " +owners.get(i).name + " with phone number : " + owners.get(i).phone_number + " Age : " + owners.get(i).age + "\n\n\n");
}
result.setText(toDisplay.toString());
} else
showToast("No Fields can be empty");
Code Explanation
- We get the value of the four EditText's and store in string variables - string_color_one,string_color_two,cat_age and cat_name.
- We declare a StringBuilder object - toDisplay
- We use an if statement to ensure that none of our string variables are empty else we simply call the showToast() method with the argument -"No Fields can be empty".
- We create a RealmResult object - persons and then we use the equalTo() predicate with the where statement specifying the PersonsWithPet class specifying a child object as the first argument - cats.age as the field name and then using the integer value of the cat_age variable we then use the
findAll()
method to get all the objects matching the query and then link that to another query using thewhere()
method again specifying a child object - cats.colr as the first argument and then passing the string_color_one variable as the second value and lastly, we link that to another query after getting all the objects that match the second condition -findAll()
to anequalTo()
predicate specying another child object - cats.color as the first argument and then the second argument the string_color_two variable.
The above code matches our first scenario already explained above.We are passing the result set gotten from each query and then linking that to another query - i.e
- We firstly got the
PersonWithPet
obkects that have cats with the age entered by the user and in that result set returned, we findPersonWithPet
objects that have cats that their color field matches the color entered by the user and in our newly return set, we finally findPersonWithPet
objects that have cats with their color field match the second color entered by the user.
- We then append the text - "Query Relationship Result" to the StringBuilder - toDisplay and also append the size of the
persons
object -persons.size()
. - Next we get the details of the return objects using a while loop:
- name => persons.get(i).name
- phone number => persons.get(i).phone_number
- age => persons.get(i).age
- Next, we use reverse relationship to get a list of cats owned a particular
PersonWithPet
object.- We use the
where()
method and specify theCat
class as the class object and then we use thecontains()
predicate and specify a reverse relationship link -owners.name
as the first argument and Edet as the second argument. Thie means that we are looking for cat objects that have an owner that their name field contains the words - Edet.
- We use the
- We then display the cat details returned by appending it to the toDisplay StringBuilder object as follows :
- name => cats.get(i).name
- age => cats.get(i).age
- color => cats.get(i).color
- Next we use reverse Relationship linking again to get the owners of a speific cat object.
- We set the
Kittens
object which is of typeCat
and get objects that have the color specified by the user in string_color_one variable and also the name specified by the user in cat_name variable.
- We set the
- Next, we use a for each loop to loop through our returned objects.
- Inside our for each loop, we intialize a
PersonWithPet
object - owners to the owners RealmList variable in ourCat
class with -kitten.getOwners
and then we loop through the details of each owner adding those detaiils to our StringBuilder object - toDisplay.
- We then display our result in our TextView -
result.setText(toDisplay.toString());
Application Execution
Curriculum
- Understanding Queries in Realm Java using Android Studio (PART 1)
- Understanding Queries in Realm Java using Android Studio (PART 2)
- Understanding Queries in Realm Java using Android Studio (PART 3)
- Understanding Queries in Realm Java using Android Studio (PART 4)
- Understanding Queries in Realm Java using Android Studio (PART 5)
Complete Source code:
https://github.com/generalkolo/Realm-JSON/tree/master/Realm%20Relationships
Hey @edetebenezer
Thanks for contributing on Utopian.
We’re already looking forward to your next contribution!
Contributing on Utopian
Learn how to contribute on our website or by watching this tutorial on Youtube.
Want to chat? Join us on Discord https://discord.gg/h52nFrV.
Vote for Utopian Witness!
Thank you for your contribution.
While I liked the content of your contribution, I would still like to extend one advice for your upcoming contributions:
Looking forward to your upcoming tutorials. Good Job!!!
Your contribution has been evaluated according to Utopian policies and guidelines, as well as a predefined set of questions pertaining to the category.
To view those questions and the relevant answers related to your post, click here.
Need help? Write a ticket on https://support.utopian.io/.
Chat with us on Discord.
[utopian-moderator]
Thanks @portugalcoin for the tip
Would definitely do that in subsequent contributions.
Thanks also for taking time to moderate my post.
Your series so far on the realm database has been impressive @edetebenezer
Thanks @enyason am glad you like it.
More coming!