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.
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.
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.
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 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.
How to Effectively Hire and Manage a Remote Team of Developers.
Download NowThe Mindbowser team's professionalism consistently impressed me. Their commitment to quality shone through in every aspect of the project. They truly went the extra mile, ensuring they understood our needs perfectly and were always willing to invest the time to...
CTO, New Day Therapeutics
I collaborated with Mindbowser for several years on a complex SaaS platform project. They took over a partially completed project and successfully transformed it into a fully functional and robust platform. Throughout the entire process, the quality of their work...
President, E.B. Carlson
Mindbowser and team are professional, talented and very responsive. They got us through a challenging situation with our IOT product successfully. They will be our go to dev team going forward.
Founder, Cascada
Amazing team to work with. Very responsive and very skilled in both front and backend engineering. Looking forward to our next project together.
Co-Founder, Emerge
The team is great to work with. Very professional, on task, and efficient.
Founder, PeriopMD
I can not express enough how pleased we are with the whole team. From the first call and meeting, they took our vision and ran with it. Communication was easy and everyone was flexible to our schedule. I’m excited to...
Founder, Seeke
Mindbowser has truly been foundational in my journey from concept to design and onto that final launch phase.
CEO, KickSnap
We had very close go live timeline and Mindbowser team got us live a month before.
CEO, BuyNow WorldWide
If you want a team of great developers, I recommend them for the next project.
Founder, Teach Reach
Mindbowser built both iOS and Android apps for Mindworks, that have stood the test of time. 5 years later they still function quite beautifully. Their team always met their objectives and I'm very happy with the end result. Thank you!
Founder, Mindworks
Mindbowser has delivered a much better quality product than our previous tech vendors. Our product is stable and passed Well Architected Framework Review from AWS.
CEO, PurpleAnt
I am happy to share that we got USD 10k in cloud credits courtesy of our friends at Mindbowser. Thank you Pravin and Ayush, this means a lot to us.
CTO, Shortlist
Mindbowser is one of the reasons that our app is successful. These guys have been a great team.
Founder & CEO, MangoMirror
Kudos for all your hard work and diligence on the Telehealth platform project. You made it possible.
CEO, ThriveHealth
Mindbowser helped us build an awesome iOS app to bring balance to people’s lives.
CEO, SMILINGMIND
They were a very responsive team! Extremely easy to communicate and work with!
Founder & CEO, TotTech
We’ve had very little-to-no hiccups at all—it’s been a really pleasurable experience.
Co-Founder, TEAM8s
Mindbowser was very helpful with explaining the development process and started quickly on the project.
Executive Director of Product Development, Innovation Lab
The greatest benefit we got from Mindbowser is the expertise. Their team has developed apps in all different industries with all types of social proofs.
Co-Founder, Vesica
Mindbowser is professional, efficient and thorough.
Consultant, XPRIZE
Very committed, they create beautiful apps and are very benevolent. They have brilliant Ideas.
Founder, S.T.A.R.S of Wellness
Mindbowser was great; they listened to us a lot and helped us hone in on the actual idea of the app. They had put together fantastic wireframes for us.
Co-Founder, Flat Earth
Ayush was responsive and paired me with the best team member possible, to complete my complex vision and project. Could not be happier.
Founder, Child Life On Call
The team from Mindbowser stayed on task, asked the right questions, and completed the required tasks in a timely fashion! Strong work team!
CEO, SDOH2Health LLC
Mindbowser was easy to work with and hit the ground running, immediately feeling like part of our team.
CEO, Stealth Startup
Mindbowser was an excellent partner in developing my fitness app. They were patient, attentive, & understood my business needs. The end product exceeded my expectations. Thrilled to share it globally.
Owner, Phalanx
Mindbowser's expertise in tech, process & mobile development made them our choice for our app. The team was dedicated to the process & delivered high-quality features on time. They also gave valuable industry advice. Highly recommend them for app development...
Co-Founder, Fox&Fork