https://github.com/realm/realm-java
In Today's tutorial, we are going to continue learning about queries in Realm. Previous tutorial links can be found below :
Recap of Part 1
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.
Recap of Part 2
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 0 or more unicode characters.Recap of Part 3
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("age", 5, 9) - this gives returns objects that their age field is between 5 and 9.greaterthan("age", 56) which returns objects that their age field's value is greater than 56greaterThan() predicates e.g - lessThan("age", 20) which returns object that their age field value is less than 20.greaterThanOrEqualTo("age", 30) which return objects that their age field has a value greater than or equal to 30.lessThanOrEqualTo("age", 75) which returns the objects that their age field value is less than or equal to 75.Recap of Part 4
In part 4 of this tutorial, we looked at three predicates namely :
beginGroup() and endGroup() predicate starts and ends a group of predicates and also specifies the order of evaluation - e.gRealmResults<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)
Recap of Part 5
In part five of this tutorial series, we looked at the distinct() predicate and also how to chain queries.
We learnt the following -
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.
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.
Please refer to the first tutorial for the dependencies used in this tutorial as no new dependencies have been added
In order for us to begin, we have to make some modifications to some existing file and also creating some new files.
NumericQuery Fragment java class file to illustrate chaining Queries on child objects.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
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.resultTv.setText(toDisplay.toString());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 -
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
PersonWithPet objects that have Cats of a certain age and in that result set, we are going to find those PersonWithPet objects having cats of a particular color (i.e black) and still in this new result set we will finally find PersonWithPet objects that have cats of another color (i.e brown)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
findAll() method to get all the objects matching the query and then link that to another query using the where() 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 an equalTo() predicate specying another child object - cats.color as the first argument and then the second argument the string_color_two variable.PersonWithPet obkects that have cats with the age entered by the user and in that result set returned, we find PersonWithPet objects that have cats that their color field matches the color entered by the user and in our newly return set, we finally find PersonWithPet objects that have cats with their color field match the second color entered by the user.persons object - persons.size().PersonWithPet object.
where() method and specify the Cat class as the class object and then we use the contains() 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.Kittens object which is of type Cat 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.PersonWithPet object - owners to the owners RealmList variable in our Cat class with - kitten.getOwners and then we loop through the details of each owner adding those detaiils to our StringBuilder object - toDisplay.result.setText(toDisplay.toString());https://github.com/generalkolo/Realm-JSON/tree/master/Realm%20Relationships