This is part of our Udemy premium course: Android Firebase MVVM Jetpack – Many Offline First CRUD Apps. In the course we are covering step by step how to create full android applications based on Firebase Realtime Database and Android Jetpack.

This tutorial will cover the first app we create in that course. The app is an offline-first CRUD app with Search and Pagination capability. In the app the user can add a scientist with his details to the database, fetch them, update them, delete them. The user can also search against already existing Scientists. The data when being read is paginated, making the app efficient. Pagination is taking place at the Firebase level.

The app has several screens/pages:

No. Page/Screen Type Role
1. Splash Screen Activity Use it to display your brand
2. Dashboard Screen Activity Use it as the center or your app .
3. CRUD Screen Activity Used for posting new data, updating and deleting data
4. Listing Sceen Activity Used for hosting our Fragments
5. LatestFragment Fragment Used for showing the last seven added items. This data is read primarily for our local cache.
6. ListingsFragment Fragment Used for showing all items in our Firebase realtime database. However the data is paginated, with each page containing seven items or any number you want.
7. SearchFragment DialogFragment Is our search dialog. First search is performed locally, then if no match is found we connect to Firebase and search there.
8. Detail Page Activity For showing details of a single scientist

 

Let’s now take a look at organization of this project:

Here’s the app demo:

Android MVVM Firebase CRUD Cache
Android MVVM Firebase CRUD Cache

/App.java

MAIN ROLE: Load our custom fonts and initialize our LRU caching library.

This App.java is our custom application class. It will derive from android.app.Application. We will load our custom fonts right here using a third party library called Calligraphy.

            ViewPump.init(ViewPump.builder()
                .addInterceptor(new CalligraphyInterceptor(
                        new CalligraphyConfig.Builder()
                                .setDefaultFontPath("fonts/Verdana.ttf")
                                .setFontAttrId(R.attr.fontPath)
                                .build()))
                .build());

We will also initialize our Hard Disk Caching here:

        if(!CacheManager.DISK_CACHE_INITIALIZED){
            CacheManager.initializeCache(this);
        }

/data/model/entity/Scientist.java

MAIN ROLE: Is our data object class.

public class Scientist  implements Serializable {

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

This class will represent a single Scientit. It is our model class is therefore basically a java bean. It will define the properties of our scientist like name, description, galaxy, star , date of birth and death of death.

The key property will be autogenerated by Firebase realtime database.

We will make this class implement serializable interface. This will allow us to easily pass it across activities.

/data/model/process/RequestCall.java

MAIN ROLE: Represent a network request against our Firebase realtime database

We want to request this operation using a class to make our code cleaner and manageable.
We are interested in three poperties of this operation:

  1. State – The state of operation e.g PROGRESS, ERROR or SUCCESS
  2. Message – The associate message of the operation
  3. An optional downloaded list of scientists.
public class RequestCall {
    private int status;
    private String message;
    private List<Scientist> scientists;
    ...

/data/repository/ScientistsRepository.java

This is where we will perform our business logic. We are interested in performing CRUD operations agains Firebase Realtime database so we will write logic for doing those right here:

For example to select data we start by defining our method:

    public MutableLiveData<RequestCall> select() {
        RequestCall r = new RequestCall();
        List<Scientist> scientists=new ArrayList<>();

We’ve also instantiate a RequestCall object to represent our network request as well as a List of scientists that will hold our downloaded data.

Then we set the properties of our RequestCall:

        r.setStatus(Constants.OPERATION_IN_PROGRESS);
        r.setMessage("Fetching Scientists Please Wait..");
        r.setScientists(scientists);
        downloadMutableLiveData.setValue(r);

You can see we’ve then set the RequestCall object to our MutableLiveData object. By the way our method will be returning this mutableLievData object.

Then:

        DB.child("Scientists").addValueEventListener(new ValueEventListener() {
            @Override
            public void onDataChange(DataSnapshot dataSnapshot) {

                if (dataSnapshot.exists() && dataSnapshot.getChildrenCount() > 0) {
                    for (DataSnapshot ds : dataSnapshot.getChildren()) {
                        //Now get Scientist Objects and populate our arraylist.
                        Scientist scientist = ds.getValue(Scientist.class);
                        Objects.requireNonNull(scientist).setKey(ds.getKey());
                        scientists.add(scientist);
                    }
                    r.setStatus(Constants.OPERATION_COMPLETE_SUCCESS);
                    r.setMessage("DOWNLOAD COMPLETE");
                    r.setScientists(scientists);

                } else {
                    r.setStatus(Constants.OPERATION_COMPLETE_SUCCESS);
                    r.setMessage("NO DATA FOUND");
                }

                downloadMutableLiveData.postValue(r);
            }
            @Override
            public void onCancelled(DatabaseError databaseError) {
                Log.d("FIREBASE CRUD", databaseError.getMessage());
                r.setStatus(Constants.OPERATION_COMPLETE_FAILURE);
                r.setMessage(databaseError.getMessage());
                downloadMutableLiveData.postValue(r);
            }
        });
        return downloadMutableLiveData;
    }

DB in this case is our Firebase realtime database reference. You can see we’ve specified the node where our data will be held using the child() method. Then we’ve added a value event listener and overidden two methods:

  1. onDataChange() – SUCCESS
  2. onFailure() – FAILURE

As we said we are returning a MutableLiveData object regardless of the success or failure. After all our RequestCall object which is the generic parameter of our RequestCall accomodates success, failure as well as progress. Thus observers will check for whether the current state is progress, failure or success and react accordingly.

Please take the whole course here or download code here.