Android Firebase Full CRUD Course.

Lesson 1: Creating and Adding Firebase App

In this lesson we will see how to create our android studio as well as firebase app. Then we will add the firebase app to android studio. We will see two techniques of creating and adding the firebase app to android studio. Then we will also see the libraries we will use in the app we are building.

Note that this lesson is part of an Android Firebase Realtime Database CRUD course we are currently covering. In the course we are creating a full Android app, powered by Firebase realtime database that supports:

  1. Adding Data to Firebase Realtime database.
  2. Retrieving Data from Firebase
  3. Updating Existing firebase data.
  4. Deleting data from firebase data.

Objectives of this Lesson

After this lesson, you will be able to:

  1. Create Android Studio Project and Add Firebase to it in two ways.
  2. Use several cool libraries in your project.

This lesson as we’ve said is part of a multi-episode course series.

Video Lesson

Watch video lesson here

Step 1: Create Android Studio Project

  1. Start your android studio.
  2. Invoke the New project dialog.
  3. Type your project name and choose its location.
  4. Check boxes that ask us if our app supports androidx and instant app features.

You should have a brand new project in your android studio.

Step 2: Add Firebase To your android studio

There are two ways:

  1. Using Android studio firebase assistant.
  2. Directly Creating firebase app in firebase console then downloading google-services.json and adding to app folder.

PLEASE WATCH THE VIDEO IF YOU ARE NOT SURE HOW TO DO ANY OF THE ABOVE.

Step 3: Add Gradle dependencies

Here are our gradle dependencies:

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    testImplementation 'junit:junit:4.12'
    implementation 'androidx.appcompat:appcompat:1.0.2'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'

    implementation 'androidx.recyclerview:recyclerview:1.0.0'
    implementation 'com.google.android.material:material:1.0.0'
    implementation 'androidx.cardview:cardview:1.0.0'

    implementation 'com.google.firebase:firebase-database:18.0.0'

    implementation 'com.squareup.picasso:picasso:2.71828'
    implementation 'com.github.ivbaranov:materiallettericon:0.2.3'
    implementation "android.helper:datetimepickeredittext:1.0.0"
    implementation 'io.github.inflationx:calligraphy3:3.1.0'
    implementation 'io.github.inflationx:viewpump:1.0.0'
    implementation 'com.yarolegovich:lovely-dialog:1.1.0'
}

Here are some of the dependencies we’ve added:

No. Name Role
1. AppCompat Our main androidx appcompat library which will give us access to appcompatactivity as well as other classes.
2. RecyclerView Androidx recyclerview to give us the adapterview we will use to render our Firebase data.
3. CardView To represent a single recyclerview row or item.The recyclerview will thus comprise several cardviews. Moreover we use cardviews in our dashboard screen, in our detail activity and in our crud activity.
4. Firebase Database Well our Firebase realtime database.
5. MaterialLetterIcon To show name initials icons in our recyclerview rather than images.
6. DateTimePickerEditText We use it to pick dates that will be saved in Firebase database.
7. Calligraphy and ViewPump To allow us load custom fonts for our app.
8. LoveLyDialog To allow us use lovely standard as well as choice dialogs.

Full Code

Here is the full code for this lesson:

(a). build.gradle (app)

The app level build.gradle:

apply plugin: 'com.android.application'

android {
    compileSdkVersion 28
    defaultConfig {
        applicationId "info.camposha.firebasedatabasecrud"
        minSdkVersion 15
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        targetCompatibility 1.8
        sourceCompatibility 1.8
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    testImplementation 'junit:junit:4.12'
    implementation 'androidx.appcompat:appcompat:1.0.2'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'

    implementation 'androidx.recyclerview:recyclerview:1.0.0'
    implementation 'com.google.android.material:material:1.0.0'
    implementation 'androidx.cardview:cardview:1.0.0'

    implementation 'com.google.firebase:firebase-database:18.0.0'

    implementation 'com.squareup.picasso:picasso:2.71828'
    implementation 'com.github.ivbaranov:materiallettericon:0.2.3'
    implementation "android.helper:datetimepickeredittext:1.0.0"
    implementation 'io.github.inflationx:calligraphy3:3.1.0'
    implementation 'io.github.inflationx:viewpump:1.0.0'
    implementation 'com.yarolegovich:lovely-dialog:1.1.0'
}

apply plugin: 'com.google.gms.google-services'

(a). build.gradle (project)

The project level build.gradle:

// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
    repositories {
        google()
        jcenter()

    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.4.0'
        classpath 'com.google.gms:google-services:3.2.0'

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        google()
        jcenter()

    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

Above you can see we have added our google-services classpath. Please don’t forget it. You may use later versions of it.

Lesson 2: Model Class

A Model class is a data object or business object class. It defines the entity we want to work with as the core of what our application is about. For example in this case we are creating Scientists CRUD App. This means we will adding,updating, reading and deleting Scientist objects to and from our Firebase Realtime Database. Thus our model class will Scientist class.

NOTE/= This class is part of a multi-episode course on Firebase CRUD. In the course we are developing a full android application with full CRUD capability.

That class will define properties for a single Scientist. And we obtain that single scientist by instantiating that class. The only requirement of our model class for it to be readable by Firebase is that it should have atleats an empty constructor. By readable by Firebase we mean that we will pass that class’s instance to Firebase’s setValue() method to save our data.

For example consider the below example:

mDatabaseRef.child("Scientists").push().setValue(scientist)

In the above code we are passing a whole Scientist object to the setValue() method to be persisted in Firebase . The class defining that scientist must have an empty constructor.

Video Lesson

Watch video lesson here

Step 1: Create Scientist Class

import com.google.firebase.database.Exclude;

import java.io.Serializable;

public class Scientist

Then make it implement the java.io.Serializable interface:

public class Scientist  implements Serializable {

That interface is called a marker interface, thus we don’t need to implement any method. Serializable interface will allow the instance of our Scientist class to be serializable and deseriable. We will need to do serialize it so that we can pass a whole object across activities. Then in the target activity we can deserialize it and render its fields.

Step 2: Define instance fields

Let’s now define our instance fields. These fields represent the properties of our Scientist:

    private String mId;
    private String name;
    private String description;
    private String galaxy;
    private String star;
    private String dob;
    private String dod;
    private String key;

Step 3: Create An Empty Constructor

    public Scientist() {
        //empty constructor needed
    }

As we had said earlier , you need to provide an empty construcor to our model class. If you needed to make injections into your class via the constructor then you can create a seperate constructor to do that. See for example how we’ve created one:

    public Scientist(String name,String description,String galaxy,String star, String dob,
                String dod ) {
        if (name.trim().equals("")) {
            name = "Scientist NoName";
        }
        this.name = name;
        this.description = description;
        this.galaxy = galaxy;
        this.star = star;
        this.dob = dob;
        this.dod = dod;
    }

Step 4: Generate Accessor methods

We now come to generate our accessor methods. These are simply getters and setters. However there are two special ones we will talk about in a short while:

    public String getId() {
        return mId;
    }
    public void setId(String id) {
        mId = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getDescription() {
        return description;
    }
    public void setDescription(String description) {
        this.description = description;
    }
    public String getGalaxy() {
        return galaxy;
    }
    public void setGalaxy(String galaxy) {
        this.galaxy = galaxy;
    }

    public String getStar() {
        return star;
    }

    public void setStar(String star) {
        this.star = star;
    }

    public String getDob() {
        return dob;
    }

    public void setDob(String dob) {
        this.dob = dob;
    }

    public String getDod() {
        return dod;
    }

    public void setDod(String dod) {
        this.dod = dod;
    }

Well there is nothing special about the above accessor methods. Now add the below two:

    @Exclude
    public String getKey() {
        return key;
    }
    @Exclude
    public void setKey(String key) {
        this.key = key;
    }

The special thing about them is the @Exclude annotation. This annotation is provided to us by the com.google.firebase.database annotation we had included in our imports. It is telling Firebase to ignore generating the key node while persisting our data. We have to do this because Firebase will be generating that key automatically. In fact we’ll simply use the two methods to get and set that key locally but we won’t be persisting it.

FULL CODE

Here is the full code:

package info.camposha.firebasedatabasecrud.Data;

import com.google.firebase.database.Exclude;

import java.io.Serializable;

public class Scientist  implements Serializable {

    private String mId;
    private String name;
    private String description;
    private String galaxy;
    private String star;
    private String dob;
    private String dod;
    private String key;

    public Scientist() {
        //empty constructor needed
    }
    public Scientist(String name,String description,String galaxy,String star, String dob,
                String dod ) {
        if (name.trim().equals("")) {
            name = "Scientist NoName";
        }
        this.name = name;
        this.description = description;
        this.galaxy = galaxy;
        this.star = star;
        this.dob = dob;
        this.dod = dod;
    }

    public String getId() {
        return mId;
    }
    public void setId(String id) {
        mId = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getDescription() {
        return description;
    }
    public void setDescription(String description) {
        this.description = description;
    }
    public String getGalaxy() {
        return galaxy;
    }
    public void setGalaxy(String galaxy) {
        this.galaxy = galaxy;
    }

    public String getStar() {
        return star;
    }

    public void setStar(String star) {
        this.star = star;
    }

    public String getDob() {
        return dob;
    }

    public void setDob(String dob) {
        this.dob = dob;
    }

    public String getDod() {
        return dod;
    }

    public void setDod(String dod) {
        this.dod = dod;
    }

    @Override
    public String toString() {
        return getName();
    }
    @Exclude
    public String getKey() {
        return key;
    }
    @Exclude
    public void setKey(String key) {
        this.key = key;
    }
}
//end

Conclusion

We have created our model class. This simple class is at the center of our application because it defines the central entity we are working with. It is this class’s instance that will be persisted, read and deleted. We’ve talked about constructors for the class, the fields as well as public accessor methods.

Now move over to the next class.

Lesson 3 : Utility Methods and App Class

In this lesson we are creating two important methods. The first method is our app class. The second is the Utils class. This lesson is part of the android firebase realtime database crud full app development course we are currently covering. In the course we see how to create a full project from scratch based on Firebase Realtime database and supporting all CRUD operations.

Objectives of this Lesson

Here are the objectives of this lesson:

  1. Create utility methods that will be needed throughout the project.
  2. Load a custom font into our android app across all activities.

(a). App.java

The role of this class is simple: Load custom font into our app. We will use a library we had earlier on added in our dependecies: Calligraphy.

Step 1: Add Imports:

import android.app.Application;

import io.github.inflationx.calligraphy3.CalligraphyConfig;
import io.github.inflationx.calligraphy3.CalligraphyInterceptor;
import io.github.inflationx.viewpump.ViewPump;

You can see among our imports include the Application class. Well this is an android component and from it we will derive.

Step 2: Create Class

Our class will derive from Application class:

public class App extends Application {

Step 3: Initialize Calligraphy

We now need to initialize our Calligraphy. The best place to that is in the onCreate() of our application class:

    @Override
    public void onCreate() {
        super.onCreate();
        ViewPump.init(ViewPump.builder()
                .addInterceptor(new CalligraphyInterceptor(
                        new CalligraphyConfig.Builder()
                                .setDefaultFontPath("fonts/Verdana.ttf")
                                .setFontAttrId(R.attr.fontPath)
                                .build()))
                .build());
    }

FULL CODE:

Here is the full code of App.java

package info.camposha.firebasedatabasecrud;

import android.app.Application;

import io.github.inflationx.calligraphy3.CalligraphyConfig;
import io.github.inflationx.calligraphy3.CalligraphyInterceptor;
import io.github.inflationx.viewpump.ViewPump;

public class App extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        ViewPump.init(ViewPump.builder()
                .addInterceptor(new CalligraphyInterceptor(
                        new CalligraphyConfig.Builder()
                                .setDefaultFontPath("fonts/Verdana.ttf")
                                .setFontAttrId(R.attr.fontPath)
                                .build()))
                .build());
    }
}
//end

(b). Utils.java

This class as we said is to contain our utility methods. It is best practice ton confine methods usable across the whole app in one simple class from which they can be called. Such methods are normally made static so that we don’t need to every now and then instantiate them to invoke our methods.

Step 1: Create the Class

public class Utils {

Step 2: Show Toast message

This method will allow us show toast messages throughout various activities.

    public static void show(Context c,String message){
        Toast.makeText(c, message, Toast.LENGTH_SHORT).show();
    }

Step 3: Validate Edittexts

Suppose we want a simple method that can validate us any number of edittexts. We just need to pass that method a param of edittexts:

    public static boolean validate(EditText... editTexts){
        EditText nameTxt = editTexts[0];
        EditText descriptionTxt = editTexts[1];
        EditText galaxyTxt = editTexts[2];

        if(nameTxt.getText() == null || nameTxt.getText().toString().isEmpty()){
            nameTxt.setError("Name is Required Please!");
            return false;
        }
        if(descriptionTxt.getText() == null || descriptionTxt.getText().toString().isEmpty()){
            descriptionTxt.setError("Description is Required Please!");
            return false;
        }
        if(galaxyTxt.getText() == null || galaxyTxt.getText().toString().isEmpty()){
            galaxyTxt.setError("Galaxy is Required Please!");
            return false;
        }
        return true;

    }

In our case three edittexts must not be empty for the validation to pass.

Step 4: Opening a new activity

     public static void openActivity(Context c,Class clazz){
        Intent intent = new Intent(c, clazz);
        c.startActivity(intent);
    }

Step 5: Showing InfoDialog

    public static void showInfoDialog(final AppCompatActivity activity, String title,
                                      String message) {
        new LovelyStandardDialog(activity, LovelyStandardDialog.ButtonLayout.HORIZONTAL)
                .setTopColorRes(R.color.indigo)
                .setButtonsColorRes(R.color.darkDeepOrange)
                .setIcon(R.drawable.m_info)
                .setTitle(title)
                .setMessage(message)
                .setPositiveButton("Relax", v -> {})
                .setNeutralButton("Go Home", v -> openActivity(activity, DashboardActivity.class))
                .setNegativeButton("Go Back", v -> activity.finish())
                .show();
    }

Step 6: Showing SingleChoice Dialog

    public static void selectStar(Context c,final EditText starTxt){
        String[] stars ={"Rigel","Aldebaran","Arcturus","Betelgeuse","Antares","Deneb",
                "Wezen","VY Canis Majoris","Sirius","Alpha Pegasi","Vega","Saiph","Polaris",
                "Canopus","KY Cygni","VV Cephei","Uy Scuti","Bellatrix","Naos","Pollux",
                "Achernar","Other"};
        ArrayAdapter<String> adapter = new ArrayAdapter<>(c,
                android.R.layout.simple_list_item_1,
                stars);
        new LovelyChoiceDialog(c)
                .setTopColorRes(R.color.darkGreen)
                .setTitle("Stars Picker")
                .setTitleGravity(Gravity.CENTER_HORIZONTAL)
                .setIcon(R.drawable.m_star)
                .setMessage("Select the Star where the Scientist was born.")
                .setMessageGravity(Gravity.CENTER_HORIZONTAL)
                .setItems(adapter, (position, item) -> starTxt.setText(item))
                .show();
    }

Step 7: Converting String to Date

    public static Date giveMeDate(String stringDate){
        try {
            SimpleDateFormat sdf=new SimpleDateFormat(DATE_FORMAT);
            return sdf.parse(stringDate);
        }catch (ParseException e){
            e.printStackTrace();
            return null;
        }
    }

Step 8: Sending Serialized Object to another activity

Well first make sure that object’s class is implementing the Serializable interface.

Then:

    public static void sendScientistToActivity(Context c, Scientist scientist,
                                               Class clazz){
        Intent i=new Intent(c,clazz);
        i.putExtra("SCIENTIST_KEY",scientist);
        c.startActivity(i);
    }

Take note of the key we have passed we will need it in the next method when receiving that object.

Step 9: Receiving a Serialized Object then deserializing it.

Well the method above had serialized our object. Now this method will deserialize it.

    public  static Scientist receiveScientist(Intent intent, Context c){
        try {
            return (Scientist) intent.getSerializableExtra("SCIENTIST_KEY");
        }catch (Exception e){
            e.printStackTrace();
            show(c,"RECEIVING-SCIENTIST ERROR: "+e.getMessage());
        }
        return null;
    }

You can see we’ve used the getSerializableExtra(), passing in the key we had supplied earlier on.

Step 10: Showing and Hiding a ProgressBar

Well you simply use the setVisibility() method, passing in the appropriate constants.

    public static void showProgressBar(ProgressBar pb){
        pb.setVisibility(View.VISIBLE);
    }
    public static void hideProgressBar(ProgressBar pb){
        pb.setVisibility(View.GONE);
    }

Step 11: Getting a Firebase Database reference

    public static DatabaseReference getDatabaseRefence() {
        return FirebaseDatabase.getInstance().getReference();
    }

FULL CODE

Here is the full code for Utils.java:

package info.camposha.firebasedatabasecrud.Helpers;

import android.content.Context;
import android.content.Intent;
import android.view.Gravity;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.ProgressBar;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;

import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;
import com.yarolegovich.lovelydialog.LovelyChoiceDialog;
import com.yarolegovich.lovelydialog.LovelyStandardDialog;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import info.camposha.firebasedatabasecrud.Data.Scientist;
import info.camposha.firebasedatabasecrud.R;
import info.camposha.firebasedatabasecrud.Views.DashboardActivity;

public class Utils {

    public static final String DATE_FORMAT = "yyyy-MM-dd";
    public static List<Scientist> DataCache =new ArrayList<>();

    public static String searchString = "";

    public static void show(Context c,String message){
        Toast.makeText(c, message, Toast.LENGTH_SHORT).show();
    }
    public static boolean validate(EditText... editTexts){
        EditText nameTxt = editTexts[0];
        EditText descriptionTxt = editTexts[1];
        EditText galaxyTxt = editTexts[2];

        if(nameTxt.getText() == null || nameTxt.getText().toString().isEmpty()){
            nameTxt.setError("Name is Required Please!");
            return false;
        }
        if(descriptionTxt.getText() == null || descriptionTxt.getText().toString().isEmpty()){
            descriptionTxt.setError("Description is Required Please!");
            return false;
        }
        if(galaxyTxt.getText() == null || galaxyTxt.getText().toString().isEmpty()){
            galaxyTxt.setError("Galaxy is Required Please!");
            return false;
        }
        return true;

    }
    public static void openActivity(Context c,Class clazz){
        Intent intent = new Intent(c, clazz);
        c.startActivity(intent);
    }
    /**
     * This method will allow us show an Info dialog anywhere in our app.
     */
    public static void showInfoDialog(final AppCompatActivity activity, String title,
                                      String message) {
        new LovelyStandardDialog(activity, LovelyStandardDialog.ButtonLayout.HORIZONTAL)
                .setTopColorRes(R.color.indigo)
                .setButtonsColorRes(R.color.darkDeepOrange)
                .setIcon(R.drawable.m_info)
                .setTitle(title)
                .setMessage(message)
                .setPositiveButton("Relax", v -> {})
                .setNeutralButton("Go Home", v -> openActivity(activity, DashboardActivity.class))
                .setNegativeButton("Go Back", v -> activity.finish())
                .show();
    }

    /**
     * This method will allow us show a single select dialog where we can select and return a
     * star to an edittext.
     */
    public static void selectStar(Context c,final EditText starTxt){
        String[] stars ={"Rigel","Aldebaran","Arcturus","Betelgeuse","Antares","Deneb",
                "Wezen","VY Canis Majoris","Sirius","Alpha Pegasi","Vega","Saiph","Polaris",
                "Canopus","KY Cygni","VV Cephei","Uy Scuti","Bellatrix","Naos","Pollux",
                "Achernar","Other"};
        ArrayAdapter<String> adapter = new ArrayAdapter<>(c,
                android.R.layout.simple_list_item_1,
                stars);
        new LovelyChoiceDialog(c)
                .setTopColorRes(R.color.darkGreen)
                .setTitle("Stars Picker")
                .setTitleGravity(Gravity.CENTER_HORIZONTAL)
                .setIcon(R.drawable.m_star)
                .setMessage("Select the Star where the Scientist was born.")
                .setMessageGravity(Gravity.CENTER_HORIZONTAL)
                .setItems(adapter, (position, item) -> starTxt.setText(item))
                .show();
    }

    /**
     * This method will allow us convert a string into a java.util.Date object and
     *  return it.
     */
    public static Date giveMeDate(String stringDate){
        try {
            SimpleDateFormat sdf=new SimpleDateFormat(DATE_FORMAT);
            return sdf.parse(stringDate);
        }catch (ParseException e){
            e.printStackTrace();
            return null;
        }
    }

    /**
     * This method will allow us send a serialized scientist objec  to a specified
     *  activity
     */
    public static void sendScientistToActivity(Context c, Scientist scientist,
                                               Class clazz){
        Intent i=new Intent(c,clazz);
        i.putExtra("SCIENTIST_KEY",scientist);
        c.startActivity(i);
    }

    /**
     * This method will allow us receive a serialized scientist, deserialize it and return it,.
     */
    public  static Scientist receiveScientist(Intent intent, Context c){
        try {
            return (Scientist) intent.getSerializableExtra("SCIENTIST_KEY");
        }catch (Exception e){
            e.printStackTrace();
            show(c,"RECEIVING-SCIENTIST ERROR: "+e.getMessage());
        }
        return null;
    }

    public static void showProgressBar(ProgressBar pb){
        pb.setVisibility(View.VISIBLE);
    }
    public static void hideProgressBar(ProgressBar pb){
        pb.setVisibility(View.GONE);
    }

    public static DatabaseReference getDatabaseRefence() {
        return FirebaseDatabase.getInstance().getReference();
    }

}
//end

Now move over to the next lesson.

Lesson 4: ClientSide Search/Filter of Firebase Data

In this lesson we will learn how to filter Firebase data on the clientside. Clientside filtering is an in-memory search filter. This means we hold our data in a data structure like an arraylist and then filter from there.

NOTE/= This class is part of multi-series course we are doing right here at Camposha about FirebaseCRUD. See the course contents on the sidebar.

This is a very fast search because we don’t need to make any connections to the database or online server. However this type of search is only suitable for small datasets. You don’t want to be holding tens or thousands of rows of data statically throughout the lifetime of your app.

In that type of scenario you would need to perform a server side search.

How our search will work.

  1. We download Firebase Data once the first time the user visits our Listing page.
  2. We hold the data statically. Rather than holding it as an instance field,we hold it as a class member. Thus the list is not garbage collected.
  3. We search against that list.

Components Required For our Search

Here are the components required to search:

No. Widget Description
1. SearchView It makes sense since we need a widget for entering our search terms.SearchView is required since it allows us to hook up to various events like textChanged event.
2. RecyclerView This is the widget that displays or lists all our data. It also displays the search results.

Other Required Classes

Because this is part of a full project, here are the classes directly needed for the code here to work

  1. MyAdapter – Our adapter. Will be responsible for binding search results to the recyclerview. Will also highlight our search results.
  2. ScientistsActivity – Responsible of referencing our recyclerview, setting its adapter, listening to searchview events and reacting to invoke the search.

Video Tutorial

Here is the video tutorial:

Steps 1 – Create a FilterHelper Class

Create a FilterHelper class that extends the android.widget.Filter:

import android.widget.Filter;

import java.util.ArrayList;
import java.util.List;

import info.camposha.firebasedatabasecrud.Data.MyAdapter;
import info.camposha.firebasedatabasecrud.Data.Scientist;

public class FilterHelper extends Filter {

As you can see we have provided the necessary imports including our MyAdapter as well as our model class. The class is extending the Filter class, which is abstract. Thus we will be required to override two classes we will talk about in a short while.

Step 2 : Define our Class members

    static List<Scientist> currentList;
    static MyAdapter adapter;

We then create two class members:

  1. currentList – The current list that should be filtered.
  2. adapter – The adapter instance which will be used for two purposes. First to pass our search results back to the MyAdapter class and secondly to the MyAdapter classes of changes in our dataset.

Step 3 : Perform our Actual Filtering

We now come and override the first of the two methods we promised. That method is the performFiltering() method. It will take one parameter:

  1. constraint – A CharSequence representing our search term or constraint.

So overrie the method:

    @Override
    protected FilterResults performFiltering(CharSequence constraint) {

Then instantiate the FilterResults class:

        FilterResults filterResults=new FilterResults();

That class will hold our filter results.

Then ensure our Constraint is not null:

        if(constraint != null && constraint.length()>0)
        {

Turn that constraint into an uppercase(or lowercase) to ensure consistency:

            constraint=constraint.toString().toUpperCase();

Create an arraylist that will hold our found filters, or rather our search results:

            ArrayList<Scientist> foundFilters=new ArrayList<>();

Then filter:

            String galaxy,name,star,description;

            //ITERATE CURRENT LIST
            for (int i=0;i<currentList.size();i++)
            {
                galaxy= currentList.get(i).getGalaxy();
                name= currentList.get(i).getName();

                //SEARCH
                if(galaxy.toUpperCase().contains(constraint)){
                    foundFilters.add(currentList.get(i));
                }else if(name.toUpperCase().contains(constraint)){
                    foundFilters.add(currentList.get(i));
                }
            }

Now set our results to our filter list:

//SET RESULTS TO FILTER LIST
            filterResults.count=foundFilters.size();
            filterResults.values=foundFilters;

Otherwise if no search hit was made:

}else
        {
            //NO ITEM FOUND.LIST REMAINS INTACT
            filterResults.count=currentList.size();
            filterResults.values=currentList;
        }

Finally we return our results:

        //RETURN RESULTS
        return filterResults;
    }

Step : Publishing Search Results

The last step is to publish search results back to the adapter. It is the adapter responsibility to bind data to recyclerview thus we have to pass it our search results for them to be rendered:

    @Override
    protected void publishResults(CharSequence charSequence, FilterResults filterResults) {

        adapter.scientists= (ArrayList<Scientist>) filterResults.values;
        adapter.notifyDataSetChanged();
    }
}
//end

Full Code

/Helpers/FilterHelper.java

package info.camposha.firebasedatabasecrud.Helpers;

import android.widget.Filter;

import java.util.ArrayList;
import java.util.List;

import info.camposha.firebasedatabasecrud.Data.MyAdapter;
import info.camposha.firebasedatabasecrud.Data.Scientist;

public class FilterHelper extends Filter {
    static List<Scientist> currentList;
    static MyAdapter adapter;
    public static FilterHelper newInstance(List<Scientist> currentList, MyAdapter adapter) {
        FilterHelper.adapter=adapter;
        FilterHelper.currentList=currentList;
        return new FilterHelper();
    }
    /*
    - Perform actual filtering.
     */
    @Override
    protected FilterResults performFiltering(CharSequence constraint) {
        FilterResults filterResults=new FilterResults();

        if(constraint != null && constraint.length()>0)
        {
            //CHANGE TO UPPER
            constraint=constraint.toString().toUpperCase();

            //HOLD FILTERS WE FIND
            ArrayList<Scientist> foundFilters=new ArrayList<>();

            String galaxy,name,star,description;

            //ITERATE CURRENT LIST
            for (int i=0;i<currentList.size();i++)
            {
                galaxy= currentList.get(i).getGalaxy();
                name= currentList.get(i).getName();

                //SEARCH
                if(galaxy.toUpperCase().contains(constraint)){
                    foundFilters.add(currentList.get(i));
                }else if(name.toUpperCase().contains(constraint)){
                    foundFilters.add(currentList.get(i));
                }
            }

            //SET RESULTS TO FILTER LIST
            filterResults.count=foundFilters.size();
            filterResults.values=foundFilters;
        }else
        {
            //NO ITEM FOUND.LIST REMAINS INTACT
            filterResults.count=currentList.size();
            filterResults.values=currentList;
        }

        //RETURN RESULTS
        return filterResults;
    }

    @Override
    protected void publishResults(CharSequence charSequence, FilterResults filterResults) {

        adapter.scientists= (ArrayList<Scientist>) filterResults.values;
        adapter.notifyDataSetChanged();
    }
}
//end

The lesson is part of a full project course. Please follow the course to see its usage, specifically our MyAdapter and ScientistsActivity classes.

Conclusion

We have seen how to perform a search and filtering agains Firebase data. And we’ve said it is a client side search. This means we obtain all our data and hold statically and search against it. This means our search is fast but suitable for small datasets.

Now move over to the next lesson in this series.

Lesson 5 : RecyclerView Adapter

Move to the next page to proceed with this course.