Firebase Cloud Firestore Database Read-Write Best Practices -Android

The Firebase Cloud Firestore is a NoSQL database provided by Google Firebase. Like the Firebase realtime database, it provides real-time syncing between cloud database and end client app (Android, IOS, Web). Unlike the Firebase realtime database is a single-rooted JSON format database, Firebase Cloud Firestore stores data in hierarchical data structures.

The Firebase Cloud Firestore stores user data in the document organized into collections. The document contains the hierarchical data structure of complex JSON objects. The document also contains the sub-collection and sub-collection documents also follow the same hierarchical data structure.

Now the question is, Why the Cloud Firestore?

As we all know, the NoSQL database has increasing value in the market and the Cloud Firestore provides the best NoSQL database support with data sync listeners. The Firebase also has a NoSQL Realtime database but it has some limitations, like limited querying power, Prising, Security.

The Advantages of Cloud Firestore

  • Better querying power than a Realtime database.
  • Allows filters on multiple fields that will help to limit the data fetch size
  • The Realtime database has a higher charge rate on bandwidth and storage. But the Cloud Firestore charge only on operations performed on your database (read, write, delete).
  • As for the developers’ point of view, in the Realtime database, we need to add database indexing manually, but Cloud Firestore recognizes the database query indexes and error alert with indexing URL.

Now we will check the event listeners available in Cloud Firestore for database read-write operations.

Tasks success listener addOnSuccessListener Task Failure listener addOnFailureListener Task Complete listener addOnCompleteListener Real-time data sync snapshot listener on document or query addSnapshotListener

In this blog, we are going to see the best practice of read-write Firebase Cloud Firestore listeners with a small example. I have a database structure as below and we are going to add some data read event listeners to get data from the database.Firebase Cloud Firestore Database Read-Write Best Practices -Android

So let’s start coding. The first thing is that the Firebase Cloud Firestore listeners are asynchronous threads so we need to create some callback classes to handle event listener’s callbacks.
Create one abstract class named CallBack with two abstract functions to handle onSuccess() and onError().

public abstract class CallBack {

public abstract void onSuccess(Object object);

public abstract void onError(Object object);

}

Create another abstract callback class and name it FirebaseChildCallBack with abstract functions to handle child event listeners.

public abstract class FirebaseChildCallBack {

public abstract void onChildAdded(Object object);

public abstract void onChildChanged(Object object);

public abstract void onChildRemoved(Object object);

public abstract void onCancelled(Object object);
}

Now we are going to write all Firebase Cloud Firestore read-write listeners in one class so that it is easy to manage and flexible for future changes. Create a class FirebaseRepository.java

As you can see, the FirebaseRepository.java the class contains Firebase Cloud Firestore read-write listeners with CallBacks and event listener’s references. The function readDocumentByListener() andreadQueryDocumentsByListener() returns ListenerRegistration . This listener is useful to remove EventListener manually. If we are adding addSnapshotListener to any document or query reference then it creates an event connection between our client app and database. The connection will continue until the app is closed or listeners are removed manually.

In FirebaseRepository.java you can see all type of events like addOnSuccessListener ,addOnFailureListener ,addOnCompleteListener ,addSnapshotListener in one class.

So to better utilize addSnapshotListener listeners, we need to remove the ListenerRegistration when its task will complete.

In the class, you can see one function named batchWrite() . The Firebase Cloud Firestore provides support for a batch write operation. We can give up to 500 tasks in one batch. So the batch write is also one of the main features of Cloud Firestore, we will discuss the batch write in another Blog.

Create a class named FirebaseDatabaseReference.java which holds the reference fo our database.

protected final void batchWrite(WriteBatch batch, final CallBack callback) {

batch.commit().addOnCompleteListener(new OnCompleteListener() {

@Override

public void onComplete(@NonNull Task task) {

if (task.isSuccessful())

callback.onSuccess(Constant.SUCCESS);

else

callback.onError(FAIL);

}

});

}

Create a class named FirebaseDatabaseReference.java which holds the reference to our database.

public class FirebaseDatabaseReference {

private FirebaseDatabaseReference() {

}

public static final FirebaseFirestore DATABASE = FirebaseFirestore .getInstance();

}

Create a class named FirebaseConstants.java which holds the constants required in the database, like database collection names and field names.

public class FirebaseConstants {

public static String EMPLOYEE_TABLE = “employee”;

public static String EMPLOYEE_NAME = “empName”;

public static String CREATED_DATE_TIME = “createdDateTime”;

}

Here we are done with setting up the structure of the Cloud Firestore read-write.

Now we are going to start our example of adding updates and reading Employee details from the real-time database. We are using an MVVM design pattern to structure our code.

First, create POJO classes for Employee and BranchDetails

public class BranchDetails implements Serializable {

private String branchName;

private String branchLocation;

private String branchId;

public String getBranchName() {

return branchName;

}

public void setBranchName(String branchName) {

this.branchName = branchName;

}

public String getBranchLocation() {

return branchLocation;

}

public void setBranchLocation(String branchLocation) {

this.branchLocation = branchLocation;

}

public String getBranchId() {

return branchId;

}

public void setBranchId(String branchId) {

this.branchId = branchId;

}

}

public class Employee implements Serializable {

private String empName;

private String designation;

private String empId;

private String empKey;

private BranchDetails branchDetails;

@ServerTimestamp

private Date createdDateTime, updatedDateTime;

private int age;

private long salary;

public Employee() {

}

public String getEmpName() {

return empName;

}

public void setEmpName(String empName) {

this.empName = empName;

}

public String getDesignation() {

return designation;

}

public void setDesignation(String designation) {

this.designation = designation;

}

public String getEmpId() {

return empId;

}

public void setEmpId(String empId) {

this.empId = empId;

}

public BranchDetails getBranchDetails() {

return branchDetails;

}

public void setBranchDetails(BranchDetails branchDetails) {

this.branchDetails = branchDetails;

}

public String getEmpKey() {

return empKey;

}

public void setEmpKey(String empKey) {

this.empKey = empKey;

}

public Date getCreatedDateTime() {

return createdDateTime;

}

public void setCreatedDateTime(Date createdDateTime) {

this.createdDateTime = createdDateTime;

}

public Date getUpdatedDateTime() {

return updatedDateTime;

}

public void setUpdatedDateTime(Date updatedDateTime) {

this.updatedDateTime = updatedDateTime;

}

public int getAge() {

return age;

}

public void setAge(int age) {

this.age = age;

}

public long getSalary() {

return salary;

}

public void setSalary(long salary) {

this.salary = salary;

}

@Exclude

public HashMap<String, Object> getMap() {

HashMap<String, Object> map = new HashMap<>();

map.put("empName", getEmpName());

map.put("designation", getDesignation());

map.put("branchDetails", getBranchDetails());

map.put("updatedDateTime", FieldValue.serverTimestamp());

map.put("age", getAge());

map.put("salary", getSalary());

return map;

}

}

Then create an interface EmployeeRepository.java where we add all abstract functions which are required for the data manipulation of the employee collection. You can create separately Repository for every collection.

public interface EmployeeRepository {

void createEmployee(Employee employee, CallBack callBack);

void updateEmployee(String employeeKey, HashMap map, CallBack callBack);

void deleteEmployee(String employeeKey, CallBack callBack);

void readEmployeeByKey(String employeeKey, CallBack callBack);

void readEmployeesByDesignationAndBranch(String designation, String branch, CallBack callBack);

void readAllEmployeesBySingleValueEvent(CallBack callBack);

ListenerRegistration readAllEmployeesByDataChangeEvent(CallBack callBack);

ListenerRegistration readAllEmployeesByChildEvent(FirebaseChildCallBack firebaseChildCallBack);

void readEmployeesSalaryGraterThanLimit(long limit, CallBack callBack);

}

We are going to write the implementation of EmployeeRepository in a separate class called EmployeeRepositoryImpl.java which extends FirebaseRepository and implements EmployeeRepository. In this class, we are going to implement all read-write operations and data manipulation logic.

public class EmployeeRepositoryImpl extends FirebaseRepository implements EmployeeRepository {

private ProgressDialogClass progressDialog;

private Activity activity;

private CollectionReference employeeCollectionReference;

public EmployeeRepositoryImpl(Activity activity) {

this.activity = activity;

progressDialog = new ProgressDialogClass(activity);

employeeCollectionReference = DATABASE.collection(EMPLOYEE_TABLE);

}

@Override

public void createEmployee(Employee employee, final CallBack callBack) {

String pushKey = employeeCollectionReference.document().getId();

if (employee != null && !Utility.isEmptyOrNull(pushKey)) {

progressDialog.showDialog(getString(R.string.loading), getString(R.string.please_wait));

employee.setEmpKey(pushKey);

DocumentReference documentReference = employeeCollectionReference.document(pushKey);

fireStoreCreate(documentReference, employee, new CallBack() {

@Override

public void onSuccess(Object object) {

progressDialog.dismissDialog();

callBack.onSuccess(SUCCESS);

}

@Override

public void onError(Object object) {

progressDialog.dismissDialog();

callBack.onError(object);

}

});

} else {

callBack.onError(FAIL);

}

}

@Override

public void updateEmployee(String employeeKey, HashMap map, final CallBack callBack) {

if (!Utility.isEmptyOrNull(employeeKey)) {

progressDialog.showDialog(getString(R.string.loading), getString(R.string.please_wait));

DocumentReference documentReference = employeeCollectionReference.document(employeeKey);

fireStoreUpdate(documentReference, map, new CallBack() {

@Override

public void onSuccess(Object object) {

progressDialog.dismissDialog();

callBack.onSuccess(SUCCESS);

}

@Override

public void onError(Object object) {

progressDialog.dismissDialog();

callBack.onError(object);

}

});

} else {

callBack.onError(FAIL);

}

}

@Override

public void deleteEmployee(String employeeKey, final CallBack callBack) {

if (!Utility.isEmptyOrNull(employeeKey)) {

progressDialog.showDialog(getString(R.string.loading), getString(R.string.please_wait));

DocumentReference documentReference = employeeCollectionReference.document(employeeKey);

fireStoreDelete(documentReference, new CallBack() {

@Override

public void onSuccess(Object object) {

progressDialog.dismissDialog();

callBack.onSuccess(SUCCESS);

}

@Override

public void onError(Object object) {

progressDialog.dismissDialog();

callBack.onError(object);

}

});

} else {

callBack.onError(FAIL);

}

}

@Override

public void readEmployeeByKey(String employeeKey, final CallBack callBack) {

if (!Utility.isEmptyOrNull(employeeKey)) {

progressDialog.showDialog(getString(R.string.loading), getString(R.string.please_wait));

DocumentReference documentReference = employeeCollectionReference.document(employeeKey);

readDocument(documentReference, new CallBack() {

@Override

public void onSuccess(Object object) {

if (object != null) {

DocumentSnapshot document = (DocumentSnapshot) object;

if (document.exists()) {

Employee employee = document.toObject(Employee.class);

callBack.onSuccess(employee);

} else {

callBack.onSuccess(null);

}

} else

callBack.onSuccess(null);

progressDialog.dismissDialog();

}

@Override

public void onError(Object object) {

progressDialog.dismissDialog();

callBack.onError(object);

}

});

} else {

callBack.onError(FAIL);

}

}

@Override

public void readEmployeesByDesignationAndBranch(String designation, String branch, final CallBack callBack) {

if (!Utility.isEmptyOrNull(designation) && !Utility.isEmptyOrNull(branch)) {

progressDialog.showDialog(getString(R.string.loading), getString(R.string.please_wait));

Query query = employeeCollectionReference

.whereEqualTo("branchDetails.designation", designation)

.whereEqualTo("branch", branch)

.orderBy("empName", ASCENDING);

readQueryDocuments(query, new CallBack() {

@Override

public void onSuccess(Object object) {

if (object != null) {

/*

*Here we employees data order by empName in ASCENDING ORDER

*/

callBack.onSuccess(getDataFromQuerySnapshot(object));

} else

callBack.onSuccess(null);

progressDialog.dismissDialog();

}

@Override

public void onError(Object object) {

progressDialog.dismissDialog();

callBack.onError(object);

}

});

} else {

callBack.onError(FAIL);

}

}

@Override

public void readAllEmployeesBySingleValueEvent(final CallBack callBack) {

progressDialog.showDialog(getString(R.string.loading), getString(R.string.please_wait));

//get all employees order by employee name

Query query = employeeCollectionReference.orderBy("empName");

readQueryDocuments(query, new CallBack() {

@Override

public void onSuccess(Object object) {

if (object != null) {

callBack.onSuccess(getDataFromQuerySnapshot(object));

} else

callBack.onSuccess(null);

progressDialog.dismissDialog();

}

@Override

public void onError(Object object) {

progressDialog.dismissDialog();

callBack.onError(object);

}

});

}

@Override

public ListenerRegistration readAllEmployeesByDataChangeEvent(final CallBack callBack) {

progressDialog.showDialog(getString(R.string.loading), getString(R.string.please_wait));

//get all employees order by employee name

Query query = employeeCollectionReference.orderBy("empName");

return readQueryDocumentsByListener(query, new CallBack() {

@Override

public void onSuccess(Object object) {

if (object != null) {

callBack.onSuccess(getDataFromQuerySnapshot(object));

} else

callBack.onSuccess(null);

progressDialog.dismissDialog();

}

@Override

public void onError(Object object) {

progressDialog.dismissDialog();

callBack.onError(object);

}

});

}

@Override

public ListenerRegistration readAllEmployeesByChildEvent(final FirebaseChildCallBack firebaseChildCallBack) {

progressDialog.showDialog(getString(R.string.loading), getString(R.string.please_wait));

//get all employees order by created date time

Query query = employeeCollectionReference.orderBy("createdDateTime");

return readQueryDocumentsByChildEventListener(query, new FirebaseChildCallBack() {

@Override

public void onChildAdded(Object object) {

if (object != null) {

DocumentSnapshot document = (DocumentSnapshot) object;

if (document.exists()) {

Employee employee = document.toObject(Employee.class);

firebaseChildCallBack.onChildAdded(employee);

} else {

firebaseChildCallBack.onChildAdded(null);

}

} else {

firebaseChildCallBack.onChildAdded(null);

}

progressDialog.dismissDialog();

}

@Override

public void onChildChanged(Object object) {

if (object != null) {

DocumentSnapshot document = (DocumentSnapshot) object;

if (document.exists()) {

Employee employee = document.toObject(Employee.class);

firebaseChildCallBack.onChildChanged(employee);

} else {

firebaseChildCallBack.onChildChanged(null);

}

} else {

firebaseChildCallBack.onChildChanged(null);

}

}

@Override

public void onChildRemoved(Object object) {

if (object != null) {

DocumentSnapshot document = (DocumentSnapshot) object;

if (document.exists()) {

Employee employee = document.toObject(Employee.class);

firebaseChildCallBack.onChildRemoved(employee);

} else {

firebaseChildCallBack.onChildRemoved(null);

}

} else {

firebaseChildCallBack.onChildRemoved(null);

}

}

@Override

public void onCancelled(Object object) {

firebaseChildCallBack.onCancelled(object);

progressDialog.dismissDialog();

}

});

}

@Override

public void readEmployeesSalaryGraterThanLimit(long limit, final CallBack callBack) {

progressDialog.showDialog(getString(R.string.loading), getString(R.string.please_wait));

//get all employees order by employee name

Query query = employeeCollectionReference.whereGreaterThan("salary", limit).orderBy("salary");

readQueryDocuments(query, new CallBack() {

@Override

public void onSuccess(Object object) {

if (object != null) {

callBack.onSuccess(getDataFromQuerySnapshot(object));

} else

callBack.onSuccess(null);

progressDialog.dismissDialog();

}

@Override

public void onError(Object object) {

progressDialog.dismissDialog();

callBack.onError(object);

}

});

}

private String getString(int id) {

return activity.getString(id);

}

public List getDataFromQuerySnapshot(Object object) {

List employeeList = new ArrayList<>();

QuerySnapshot queryDocumentSnapshots = (QuerySnapshot) object;

for (DocumentSnapshot snapshot : queryDocumentSnapshots) {

Employee employee = snapshot.toObject(Employee.class);

employeeList.add(employee);

}

return employeeList;

}

}

As you can see the EmployeeRepositoryImpl.java class contains the implementation of a data read-write. In this class only we are going to write insert, update, delete, simple and compound queries on employee collection.

That’s it now you are ready to use EmployeeRepository. just create an object reference in your class as,

private EmployeeRepository employeeRepository;

employeeRepository= new EmployeeRepositoryImpl(addEmployeeActivity);

To add and update Employee details you can call as

private void saveData(Employee employee) {

   employeeRepository.createEmployee(employee, new CallBack() {

       @Override

       public void onSuccess(Object object) {

           addEmployeeActivity.finish();

       }




       @Override

       public void onError(Object object) {

           Utility.showMessage(addEmployeeActivity, addEmployeeActivity.getString(R.string.some_thing_went_wrong));

       }

   });

}




private void updateData(Employee employee) {

   employeeRepository.updateEmployee(this.employee.getEmpKey(), employee.getMap(), new CallBack() {

       @Override

       public void onSuccess(Object object) {

           addEmployeeActivity.finish();

       }




       @Override

       public void onError(Object object) {

           Utility.showMessage(addEmployeeActivity, addEmployeeActivity.getString(R.string.some_thing_went_wrong));

       }

   });

}

Now we are going to read the employee details with the addSnapshotListener and remove the listener on onDestroy() of the activity. In this example, we will use the child event listeners of Firebase Cloud Firestore. The child events listener is added in FirebaseRepository.java class as readQueryDocumentsByChildEventListener() and we are going to that function.

Now we are going to write the adapter EmployeeDetailsAdapter.java to show the list of employees.

public class EmployeeDetailsAdapter extends RecyclerView.Adapter {

   private IndexedLinkedHashMap<String, Employee> employeeList;

   private LayoutInflater layoutInflater;

   private Activity activity;




   public EmployeeDetailsAdapter(Activity activity, IndexedLinkedHashMap<String, Employee> employeeList) {

       this.activity = activity;

       this.employeeList = employeeList;

   }




   @NonNull

   @Override

   public EmployeeViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {

       if (layoutInflater == null) {

           layoutInflater = LayoutInflater.from(parent.getContext());

       }

       EmployeeInfoRowLayoutBinding employeeInfoRowLayoutBinding = DataBindingUtil.inflate(layoutInflater, R.layout.employee_info_row_layout, parent, false);

       return new EmployeeViewHolder(employeeInfoRowLayoutBinding);

   }




   @Override

   public void onBindViewHolder(@NonNull final EmployeeViewHolder holder, final int position) {

       final Employee employee = employeeList.getItemByIndex(getReversePosition(position));

       if (!Utility.isEmptyOrNull(employee.getEmpName()))

           holder.employeeInfoRowLayoutBinding.tvName.setText(employee.getEmpName());

       else

           holder.employeeInfoRowLayoutBinding.tvName.setText(R.string.na);

       if (!Utility.isEmptyOrNull(employee.getDesignation()))

           holder.employeeInfoRowLayoutBinding.tvDesignation.setText(employee.getDesignation());

       else

           holder.employeeInfoRowLayoutBinding.tvDesignation.setText(R.string.na);

       if (!Utility.isEmptyOrNull(employee.getEmpId()))

           holder.employeeInfoRowLayoutBinding.tvEmpId.setText(employee.getEmpId());

       else

           holder.employeeInfoRowLayoutBinding.tvEmpId.setText(R.string.na);

       if (null != employee.getBranchDetails() && !Utility.isEmptyOrNull(employee.getBranchDetails().getBranchName()))

           holder.employeeInfoRowLayoutBinding.tvBranch.setText(employee.getBranchDetails().getBranchName());

       else

           holder.employeeInfoRowLayoutBinding.tvBranch.setText(R.string.na);

   }





   @Override

   public int getItemCount() {

       if (employeeList != null)

           return employeeList.size();

       else return 0;

   }




   private int getReversePosition(int index) {

       if (employeeList != null && !employeeList.isEmpty())

           return employeeList.size() - 1 - index;

       else return 0;

   }




   public void reloadList(IndexedLinkedHashMap<String, Employee> list) {

       employeeList = list;

       notifyDataSetChanged();

   }




   public void reloadList(int index, String operation) {

       switch (operation) {

           case ADD:

               notifyItemInserted(getReversePosition(index));

               break;

           case UPDATE:

               notifyItemChanged(getReversePosition(index));

               break;

           case DELETE:

               notifyItemRemoved(getReversePosition(index));

               break;

           default:

               notifyDataSetChanged();

               break;

       }

   }





   public class EmployeeViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {

       private EmployeeInfoRowLayoutBinding employeeInfoRowLayoutBinding;




       private EmployeeViewHolder(EmployeeInfoRowLayoutBinding employeeInfoRowLayoutBinding) {

           super(employeeInfoRowLayoutBinding.getRoot());

           this.employeeInfoRowLayoutBinding = employeeInfoRowLayoutBinding;

           employeeInfoRowLayoutBinding.cardViewRowClick.setOnClickListener(this);

       }




       @Override

       public void onClick(View v) {

           Intent intent = new Intent(activity, AddEmployeeActivity.class);

           intent.putExtra(EMPLOYEE_MODEL, employeeList.getItemByIndex(getReversePosition(getAdapterPosition())));

           activity.startActivity(intent);

       }

   }//end of view holder




   public IndexedLinkedHashMap<String, Employee> getEmployeeList() {

       return employeeList;

   }

}

In adapter, you can see the functionreloadList() which can notify the adapter with index instead of notifyDataSetChanged(); and the operation specifies the event happen in the database which is triggered by the Firebase Cloud Firestore listener.

public void reloadList(int index, String operation) {

   switch (operation) {

       case ADD:

           notifyItemInserted(getReversePosition(index));

           break;

       case UPDATE:

           notifyItemChanged(getReversePosition(index));

           break;

       case DELETE:

           notifyItemRemoved(getReversePosition(index));

           break;

       default:

           notifyDataSetChanged();

           break;

   }

}

And in ViewModel, we call the child event listener as

private void getAllEmployees() {

   if (listenerRegistration != null)

       listenerRegistration.remove();

   listenerRegistration = employeeRepository.readAllEmployeesByChildEvent(new FirebaseChildCallBack() {

       @Override

       public void onChildAdded(Object object) {

           if (object != null) {

               Employee employee = (Employee) object;

               if (employeeDetailsAdapter == null)

                   setAdapter(new IndexedLinkedHashMap<String, Employee>());

               employeeDetailsAdapter.getEmployeeList().add(employee.getEmpKey(), employee);

               employeeDetailsAdapter.reloadList(employeeDetailsAdapter.getEmployeeList().size() - 1, ADD);

           }

       }




       @Override

       public void onChildChanged(Object object) {

           if (object != null) {

               Employee employee = (Employee) object;

               employeeDetailsAdapter.getEmployeeList().update(employee.getEmpKey(), employee);

               employeeDetailsAdapter.reloadList(employeeDetailsAdapter.getEmployeeList().getIndexByKey(employee.getEmpKey()), UPDATE);

           }

       }




       @Override

       public void onChildRemoved(Object object) {

           if (object != null) {

               Employee employee = (Employee) object;

               employeeDetailsAdapter.getEmployeeList().update(employee.getEmpKey(), employee);

               employeeDetailsAdapter.reloadList(employeeDetailsAdapter.getEmployeeList().getIndexByKey(employee.getEmpKey()), DELETE);

           }

       }




       @Override

       public void onCancelled(Object object) {

           if (object != null)

               ExceptionUtil.errorMessage(TAG, "getAllEmployees", new Exception(object.toString()));

       }

   });

}

The getAllEmployees() complete all the tasks for us, to show the list of all employees and you can see we are using readAllEmployeesByChildEvent() to fetch data. That means it handles the data changes in the database. It creates an event listener between our database query reference and client app.

Now here the manual task has come into the picture, to remove the listenerlistenerRegistration.remove(); listener when our functionality is over. you can remove the listener by calling the function on onDestroy() of your activity.

public void removeListener() {

   if (listenerRegistration != null)

       listenerRegistration.remove();

   Glide.get(employeeListActivity).clearMemory();//clear memory

}





In an Activity

@Override

protected void onDestroy() {

   super.onDestroy();

   employeeListViewModel.removeListener();

}

The EmployeeListViewModel looks like

public class EmployeeListViewModel {

   private ActivityEmployeeListBinding activityEmployeeListBinding;

   private EmployeeListActivity employeeListActivity;

   private EmployeeDetailsAdapter employeeDetailsAdapter;

   private EmployeeRepository employeeRepository;

   private ListenerRegistration listenerRegistration;

   private String TAG = "EmployeeListViewModel";




   public EmployeeListViewModel(EmployeeListActivity employeeListActivity, ActivityEmployeeListBinding activityEmployeeListBinding) {

       this.activityEmployeeListBinding = activityEmployeeListBinding;

       this.employeeListActivity = employeeListActivity;

       employeeRepository = new EmployeeRepositoryImpl(employeeListActivity);

   }




   public void setActionBar() {

       Toolbar tb = activityEmployeeListBinding.toolbar;

       employeeListActivity.setSupportActionBar(tb);

       ActionBar actionBar = employeeListActivity.getSupportActionBar();

       if (actionBar != null) {

           actionBar.setDisplayHomeAsUpEnabled(true);

           actionBar.setDisplayShowHomeEnabled(true);

           actionBar.setTitle(R.string.app_name);

       }

       tb.setNavigationOnClickListener(new View.OnClickListener() {

           @Override

           public void onClick(View v) {

               employeeListActivity.onBackPressed();

           }

       });

   }




   public void init() {

       getAllEmployees();

   }




   private void setAdapter(IndexedLinkedHashMap<String, Employee> employeeIndexedLinkedHashMap) {

       if (employeeDetailsAdapter == null) {

           employeeDetailsAdapter = new EmployeeDetailsAdapter(employeeListActivity, employeeIndexedLinkedHashMap);

           activityEmployeeListBinding.rvEmployeeList.setHasFixedSize(true);

           RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(employeeListActivity);

           activityEmployeeListBinding.rvEmployeeList.setLayoutManager(mLayoutManager);

           activityEmployeeListBinding.rvEmployeeList.setItemAnimator(new DefaultItemAnimator());

           activityEmployeeListBinding.rvEmployeeList.setAdapter(employeeDetailsAdapter);

       } else {

           employeeDetailsAdapter.reloadList(employeeIndexedLinkedHashMap);

       }

   }




   private void getAllEmployees() {

       if (listenerRegistration != null)

           listenerRegistration.remove();

       listenerRegistration = employeeRepository.readAllEmployeesByChildEvent(new FirebaseChildCallBack() {

           @Override

           public void onChildAdded(Object object) {

               if (object != null) {

                   Employee employee = (Employee) object;

                   if (employeeDetailsAdapter == null)

                       setAdapter(new IndexedLinkedHashMap<String, Employee>());

                   employeeDetailsAdapter.getEmployeeList().add(employee.getEmpKey(), employee);

                   employeeDetailsAdapter.reloadList(employeeDetailsAdapter.getEmployeeList().size() - 1, ADD);

               }

           }




           @Override

           public void onChildChanged(Object object) {

               if (object != null) {

                   Employee employee = (Employee) object;

                   employeeDetailsAdapter.getEmployeeList().update(employee.getEmpKey(), employee);

                   employeeDetailsAdapter.reloadList(employeeDetailsAdapter.getEmployeeList().getIndexByKey(employee.getEmpKey()), UPDATE);

               }

           }




           @Override

           public void onChildRemoved(Object object) {

               if (object != null) {

                   Employee employee = (Employee) object;

                   employeeDetailsAdapter.getEmployeeList().update(employee.getEmpKey(), employee);

                   employeeDetailsAdapter.reloadList(employeeDetailsAdapter.getEmployeeList().getIndexByKey(employee.getEmpKey()), DELETE);

               }

           }




           @Override

           public void onCancelled(Object object) {

               if (object != null)

                   ExceptionUtil.errorMessage(TAG, "getAllEmployees", new Exception(object.toString()));

           }

       });

   }




   public void removeListener() {

       if (listenerRegistration != null)

           listenerRegistration.remove();

       Glide.get(employeeListActivity).clearMemory();//clear memory

   }




   public void setFabClickListener() {

       activityEmployeeListBinding.fab.setOnClickListener(new View.OnClickListener() {

           @Override

           public void onClick(View view) {

               employeeListActivity.startActivity(new Intent(employeeListActivity, AddEmployeeActivity.class));

           }

       });

   }

}

So this is how we are writing all firebase operation listeners in a separate file and creating a repository for individual collection. Then we are using the repository to manipulate the data operations. And at last, if we are using addSnapshotListener to fetch data, we are then responsible to remove the data change listeners.

In this blog, we had discussed only the read-write best practices of Cloud Firestore listeners. But intentionally I ignored the discussion on Firebase Cloud Firestore querying. The Firestore provides strong querying power on data and we will discuss Firestore querying in the next blog.

Note: In the above example the write operation is not permitted. So to check the write data operation you need to configure your own project from the Firebase console.

Vikram K

Tech Expert

Vikram is a Android developer with over 5+ work of experience with good knowledge in Android, Firebase and UPI payment SDK implementation. He has been worked as technical lead in microlucid technology. He is highly work-o-holic person and loves to teach and share knowledge among team mates.

Keep Reading

Keep Reading

  • Service
  • Career
  • Let's create something together!

  • We’re looking for the best. Are you in?