Understanding Queries in Realm Java using Android Studio (PART 1)

in #utopian-io6 years ago (edited)

realmDark.jpg

Repository

https://github.com/realm/realm-java

What Will I Learn?

  1. You will learn about Queries in Realm
  • contains() predicate
  • beginsWith() predicate
  • in() predicate

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

Difficulty

  • Intermediate
Tutorial Duration - 30 - 35Mins

Tutorial Content

In Todays tutorial, we are going to be learing about queries in Realm. An inorder to learning this, we are going to be develioping an android application that will comprise of a Realm database that will have a Person class that will extend the RealmObject class and will hold the name, address, email and phone number of a person object.

For this tutorial, we are going to be looking at three queries/predicates - contains, beginsWith and in.

For each of this predicates, they take a first argument which is the name of a field in the database and then the second argument which be the query string and an optional last argument to specify Case Sensitiviy or not.

e.g - contains("name","Edet Ebenezer",Case.SENSITIVE)

The above indicates that we are looking at the name field is it contains the a String Edet Ebenezer and that the cases shouldn't be ignored the upper case E in Edet must match and the E in Ebenezer must also match.

The android application will have three buttons - Single Query Button,Double Query Button,Multiple Querry Button and once the user clicks any of this button, the appropriate fragment will be loaded.

Outline

  • Dependencied Used.
  • Create Three Buttons in activity_main Layout
  • Create Three Fragments.
  • Add FragmentTransaction codes in MainActivity.java
  • Create the Necessary Model Classes.
  • Create Realm Datbase and Populate using JSON
  • Show Predicate Usage in Fragments.

Depenedencies used

  • implementation 'com.jakewharton:butterknife:8.8.1'
    annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'

The ButterKnife dependency should be placed in your application level gradle file - "build.gradle" which will make the injection of views (e.g ImageView, TextView) as easy as possible which we will be seeing in this tutorial.

  • implementation 'org.projectlombok:lombok:1.16.20'
    annotationProcessor 'org.projectlombok:lombok:1.16.20'

The lombok dependency also is placed in the application level gradle file which makes the generator of getter and setter methods for our model classes by just adding the annotations @Getter for getters and @Setter for the setter methods.

  • Realm dependency

    Steps

  • Head to your project level gradle file and add the classpath dependency:
    classpath "io.realm:realm-gradle-plugin:5.1.0"

  • Next, head to your application level Gradle file "build.gradle" and add the realm-android plugin to the top of the file.

apply plugin:'realm-android'

Finally, refresh your Gradle dependencies.

After you have added the necessary dependencies, your application level Gradle file should look like this -

Create Three Buttons in activity_main Layout

We need to add Three Button views to our main activity layout file and then set the id's which we will be using to inject their repestive onClick methods using ButterKnife.

activity_layout.xml
//RelativeLayout Root View with id - mainLayout

//LinearLayout with orientation - veritcal

<Button
    android:id="@+id/single_query"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginTop="30dp"
    android:text="Single String Search" />

<Button
    android:id="@+id/double_string_query"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_centerInParent="true"
    android:text="Double String Search" />

<Button
    android:id="@+id/multiple_string_query"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_alignParentBottom="true"
    android:layout_marginBottom="30dp"
    android:text="Multiple String Search" />

In the layout code above, we have inserted three buttons which are children views of a LinearLayout with orientation of vertical.

The root layout is a RelativeLayout with an id of mainLayout which we will be using in our FragmentTransaction later in this tutorial.

The id's of the three buttons are - single_query,double_string_query,multiple_string_query and thier text are - Single String Search,Double String Search,Multiple String Search respectively

Create Three Fragments

We will be creating three fragments which we will be using to

To create a new blank fragment, right click on your java folder => New => Fragment => Blank Fragment.

BlankFragment1.PNG

Next, Name your fragment - SingleQuery and then do not forget to uncheck the - Include fragment factory methods ? and include interface callbacks checkboxes

Repeat the process for the remaining fragment and make sure to give it a unique name as two fragments can't have the same name.

You can name the remaining fragments - DoubleStringQuery and MultipleStringQuery in order to follow along with the tutorial.

Add Fragment Transactions in MainActivity.java class

Next, in our mainActivity.java we will be using butterknife to inject the onClick methods for our three buttons and then we will be using FragmentTransaction to call the aappropriate fragment based on the button that was clicked.

To inject the onClick methods of the new buttons 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:

Injected Code

The MainActivity.java class file should now look like this

@OnClick({R.id.single_query, R.id.double_string_query, R.id.multiple_string_query})
    public void onViewClicked(View view) {
        switch (view.getId()) {
            case R.id.single_query:
                showSingleStringFragment();
                break;
            case R.id.double_string_query:
                showDoubleStringFragment();
                break;
            case R.id.multiple_string_query:
                showMultipleStringFragment();
                break;
        }
}

Note: The showSingleStringFragment(),showDoubleStringFragment() and showMultipleStringFragment wasn't added by butterknife.

showSingleStringFragment()

private void showSingleStringFragment() {
        SingleQuery singleQuery = new SingleQuery();
        showFragment(singleQuery);
}   

In this method, we create a SingleQuery Fragment object and then pass it as an argument to the showFragment() method.

The same step is taken in all the other methods - showDoubleStringFragment() and showMultipleStringFragment -
For the showDoubleStringFragment() method, we will create a DoubleStringQuery Fragment object and pass it as an argument to the showFragment() method and create a MultipleStringQuery object and pass to the showFragment() method for the showMultipleStringFragment() method.

showFragment()

private void showFragment(Fragment fragment) {
    FragmentManager  fragmentManager = getFragmentManager();
    FragmentTransaction ft = fragmentManager.beginTransaction();

    ft.replace(R.id.mainLayout,fragment).addToBackStack(fragment.getTag());
    ft.commit();
}

Firstly, we get a FragmentManager object and set it to the getFragmentManger() and then we get a FragmentTransaction object - ft and begin a transaction with it on the FragmentManager object - fragmentManager.

We then call the replace method on the FragmentTransaction object passing the id of the view we want replaced and the the fragment to replace it as the arguments.

Note: The mainLayout id is the id of the RelativeLayout which is the root layout for our activity_main.xml file.

We then add the fragment to backStack using the addToBackStack() method and getting the tag from the fragment as the argument and finally we call commit() on the transaction.

Create the Necessary Model Classes

As explained earlier, we will be creating a Person class that will have a name, email, address and phone number fields and then we will be using the Lombok library to inject our Getter and Setter methods for the fields by using the @Getter and @Setter annotations as shown below:

Person.java

@Getter
@Setter
public class Person extends RealmObject {

    String name;
    String address;
    String email;
    int phone_number;
}

Create Realm Datbase and Populate using JSON

We will have to initialize and populate our realm database in our onCreate() method of our MainActivity java class file.

Firstly, we have to create a Realm object at the top of our class file - private Realm realm; and in our onCreate() method, we add the following code -

Realm.init(this);
Realm.deleteRealm(Realm.getDefaultConfiguration());

realm = Realm.getDefaultInstance();

createRealmFromJSON();

Code Explanation

First, we initialize the Realm in our Activity, we then start with a clean slate by calling the deleteRealm() method on the defaultConfiguration file and then we initialize our realm object to a defaultInstance object.

We then call the createRealmFromJSON() method.

createRealmFromJSON()

Please refer here and here for more explanations for using JSON to create and populate Realm database.

  1. You have to first create the raw android resource directory:
    • right click on res folder => New => Android Resource Directory
    • From resource drop-down , select raw and click OK button.
  2. Create JSON file
    • right click on the raw folder located under res folder => New => File => persons.json
    • Insert the following into the persons.json file -
[
  {
    "name":"Ebenezer Edet",
    "address":"Garki Abuja",
    "phone_number":242424,
    "email":"[email protected]"
  },
  {
    "name":"Smith Williams",
    "address":"Wuse Abuja",
    "phone_number":123456,
    "email":"[email protected]"
  },
  {
    "name":"Mark John",
    "address":"Gwagwalada Abuja",
    "phone_number":890234,
    "email":"[email protected]"
  },
  {
    "name":"Edet Lucas",
    "address":"Gwarinpa Abuja",
    "phone_number":345678,
    "email":"[email protected]"
  }
]
private void createRealmFromJSON() {
        realm.executeTransaction(new Realm.Transaction() {
            @Override
            public void execute(Realm realm) {
                InputStream inputStream = getResources().openRawResource(R.raw.persons);
                try {
                    realm.createAllFromJson(Person.class,inputStream);
                } catch (IOException e) {
                    if (realm.isInTransaction())
                        realm.cancelTransaction();
                }
            }
        });
    }

Code Explanation

We execute a realm transaction on the realm variable by calling the executeTransaction() method and override the execute() method.

We use an InputStream object - inputStream to get the json file resource by calling the getResources() method and then calling the openRawResource() method with the persons json file as an argument.

realm.createAllFromJson(Person.class,inputStream); creates a realm database of Person class and gets its realm objects from the inputStream.

Show Predicate Usage in Fragments

SingleQuery Fragment

For our SingleQuery Fragment, we will be receiving a single string query from the user and when the user clicks the GO button, we then check the database for object that contains the query string using the contains() predicate and then we will be using the beginsWith() predicate to get objects that starts with the query string.

For the contains() predicate , we will be using the third optional query of Case.INSENSITIVE which ignores the case of the query string and for the beginsWith() predicate we will be using Case.SENSITIVE which doesnt ignore the case of the query string.

For us to be able to accept input from the user, we will be adding an EditText view to our SingleQuery xml layout file - single_query_fragment.xml with the id - query_name and we also have to add a Button view with the id - go_btn and then for us to display the result, we will be adding a TextView with the id - result which we will be inject all of them using ButterKnife.

single_query_fragment.xml

<EditText
android:layout_marginTop="30dp"
android:id="@+id/query_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Enter First Name" />

<Button
android:id="@+id/go_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/query_name"
android:layout_centerHorizontal="true"
android:layout_marginTop="20dp"
android:text="GO" />

<TextView
android:id="@+id/result"
android:layout_below="@+id/go_btn"
android:layout_marginTop="30dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:text="Result Will appear Here"
android:textColor="#000"
android:textSize="18sp" />
SingleQuery.java

Firstly, we declear a Realm object - private Realm realm; and then we get a defaultInstance in our onCreate() method - realm = Realm.getDefaultInstance(); and then in the injected onClick() method of our button with the id -go_btn we add the following codes -

@OnClick(R.id.go_btn)
    public void onViewClicked() {
        String string_queryName = queryName.getText().toString();
        StringBuilder toDisplay = new StringBuilder();

        if (!string_queryName.isEmpty()){
            RealmResults<Person> result = realm.where(Person.class)
                    .contains("name", string_queryName, Case.INSENSITIVE)
                    .findAll();

            toDisplay.append("There are - "+result.size()+" Persons with a name like that\n\n");

            int i = 0;

            while(i < result.size()){
                toDisplay.append(result.get(i).name+" with phone number : "+result.get(i).phone_number+" email : "+result.get(i).email+" and Address :"+result.get(i).address+"\n\n\n");
                i++;
            }

            result = realm.where(Person.class)
                    .beginsWith("name",string_queryName,Case.SENSITIVE)
                    .findAll();

            toDisplay.append("There are - "+result.size()+" Persons that their name starts with - "+string_queryName+"\n\n");

            i = 0;
            while(i < result.size()){
                toDisplay.append(result.get(i).name+" with phone number : "+result.get(i).phone_number+" email : "+result.get(i).email+" and Address :"+result.get(i).address+"\n\n\n");
                i++;
            }
        }
        else
            showToast("Name cannot be empty");

        result.setText(toDisplay.toString());
    }

Code Explanation

Firstly, once the user clicks the GO button, we ensure that the EditText is not empty by saving its value into a String variable -string_queryName and then calling the isEmpty() method inside an if statement to verify that something was entered.

Next, we create an empty StringBuilder object - toDisplay that we will append the result gotten from the query.

If the String is empty, inside our else statament, we call the method - showToast() with the argument - Name cannot be empty.

If the String is not empty, we then create a RealmResult<> object - result which uses the contains() predicate with the where() statement checking the name field for the string stored in the string_queryName variable and then passes the Case.INSENSITIVE optional argument telling it to ignore the case of the query string and we lastly add the findAll() method meaning this should return a List and not just a single object.

Next, we append the size of the result and we then use a while loop to loop through all the objects in our result variable and then display the nessary details using result.get(i).name to display the person's name, result.get(i).phone_number to display the person's phone number, result.get(i).email for the email and result.get(i).address for the address, we then append three new line characters.

We then use the result object to get objects in the realm database that their name field begins with the string_queryName and then we use the Case.SENSITIVE optional argument to pay attention to the case matching and then we finally use findAll().

The same step for the contains() predicate is done for the beginsWith() predicate also.

Lastly, we set the text of our TextView with id result to the StringBuilder object - toDisplay that now contains well formatted details of objects that match our searches from the contains() and beginsWith() predicate.

showToast()
private void showToast(String message) {
        Toast.makeText(getActivity(), message, Toast.LENGTH_SHORT).show();
    }
DoubleStringQuery Fragment

For the DoubleStringQuery Fragment, we are going to be accepting two strings from the user and then we are going to use the contain() predicate to check if there are objects that match the strings entered using the contains() predicate with the or() statement.

The or() statement allows you to join two queries together.

DoubleString.java

After declearing our realm object and initializing it in our onCreate() method, we then inject our two EditTexts and an onClick method for the GO Button and add the following code to the onClick method.

String string_queryname,string_queryname2;

        string_queryname = queryName.getText().toString();
        string_queryname2 = queryName2.getText().toString();

        StringBuilder toDisplay = new StringBuilder();

        if (!(string_queryname.isEmpty() && string_queryname2.isEmpty())){
            RealmResults<Person> result = realm.where(Person.class)
                    .contains("name", string_queryname, Case.INSENSITIVE)
                    .or()
                    .contains("name",string_queryname2,Case.SENSITIVE)
                    .findAll();

            toDisplay.append("There are - "+result.size()+" Persons with names like that\n\n");

            int i = 0;

            while(i < result.size()){
                toDisplay.append(result.get(i).name+" with phone number : "+result.get(i).phone_number+" email : "+result.get(i).email+" and Address :"+result.get(i).address+"\n\n\n");
                i++;
            }
        }
        else
            showToast("No Field can be left empty");

        result.setText(toDisplay.toString());

Every Explanation is still the same with the SingleQueryFragment Fragment except that we use the or() statement here to combine two contains() predicate, one to check for the string_queryname value in the name field of the Person class ignoring cases and the other to check for the string_queryname2 value in the name field but not ignoring cases this time.

The results are appended to the toDisplay StringBuilder object and displayed on the result TextView in our double_string_query_fragment.

NB: queryName and queryName2 are injected into this class using ButterKnife they represent two EditText with the id's - query_name and query_name2.

MultipleStringQuery Fragment

To explain the in() predicate, we will require the user to enter multiple strings separated by commas and then click the GO button, we will then save that string in a String array using the spilt() method and , as the delimeter, we will then pass the String array as the second argument for the in() predicate and then use the Case.INSENSITIVE as the third optional argument.

MultipleStringQuery.java

String string_queryname = queryName.getText().toString();

String[] names = string_queryname.split(",");

StringBuilder toDisplay = new StringBuilder();

if (!string_queryname.isEmpty()){

    RealmResults<Person> result = realm.where(Person.class)
            .in("name",names,Case.INSENSITIVE)
            .findAll();

    toDisplay.append("There are - "+result.size()+" Persons with names like that\n\n");

    int i = 0;

    while(i < result.size()){
        toDisplay.append(result.get(i).name+" with phone number : "+result.get(i).phone_number+" email : "+result.get(i).email+" and Address :"+result.get(i).address+"\n\n\n");
        i++;
    }

}
else {
    showToast("Please enter a name");
}

result.setText(toDisplay.toString());

NB: The queryName is injected using ButterKnife which represents an EditText which the user has to enter the names separated by commas.

We then append the result of the query to the toDisplay StringBuilder object and then set it as the text of our result TextView which was injected by ButterKnife

NB: We have to close the realm database on the onDestroy method of our fragment for both the SingleQuery, DoubleStringQuery and MultipleStringQuery Fragments.

@Override
public void onDestroyView() {
    super.onDestroyView();
    realm.close();
}

Application Execution

Sort:  

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!

While I liked the content of your contribution, I would still like to extend one advice for your upcoming contributions:

  • There are parts of the code that have little explanation, try to explain as much as possible.

Looking forward to your upcoming tutorials.

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 taking time out to moderate my post.
Adequate improvements will be made on subsequent changes.

You have a minor misspelling in the following sentence:

To explain the in() predicate, we will require the user to enter multiple strings seperated by commas and then click the GO button, we will then save that string in a String array using the spilt() method and , as the delimeter, we will then pass the String array as the second argument for the in() predicate and then use the `Case.
It should be separated instead of seperated.

Coin Marketplace

STEEM 0.31
TRX 0.11
JST 0.033
BTC 64275.02
ETH 3139.81
USDT 1.00
SBD 4.14