Firebase Firestore and Android Architecture components seem to fit together perfectly. Yet there is little information on how to tie these tools together.
This was brought to my attention by Ashton Jones and in response, I’ve created some articles explaining how to integrate them. In this article, I will explain how to integrate an Android App developed with Android Architecture components with a Firebase Firestore database.
As mentioned this is a follow-up tutorial, but it can be followed without the need to read the previous ones. But if you’re curious, here they are: Create an App That Uses LiveData and ViewModel in JavaUsing Android Architecture Componentsmedium.comHow to Set Up a Database With Firebase Firestore to Use With Android Architecture ComponentsAdd database functionality to your Android appmedium.com
Let’s Start
If you want to follow along from this point, download the Android project on my site.
This project is written in:
- Android Studio 3.6.2
- Java
- Compact library: AndroidX
You will need to configure the Firebase Firestore database by yourself because sadly it can’t be provided as an attachment — here it is if you need to know how to do it. Once you have met these pre-requisites you can start this tutorial.
Run the app and you will see a simple shopping list, which is a very common sample app with a fundamental architecture:
Separating Live Data from the View Model
When developing Android projects it’s always important to refactor constantly. As Uncle Bob Martin once mentioned:
Always leave the campground cleaner than you found it.
You will start this project with a refactor so that it fits better in MVVM architecture. Currently, the structure of the starter project looks like this:
In this refactor, you’ll be adding three new packages to the app:
LiveData
Model
Repository
Separating LiveData from the ViewModel
Ideally, each of the components in your architecture should have its own package, so that other developers know where to find your project’s information.
The choice of package naming varies widely by project and structure. In this case, we’ll have a package for each of the architecture parts. Start this by creating a LiveData package, by right-clicking on the project’s main folder as shown in Image 3:
Name the package LiveData, as in the image below.
Inside that new package, create a Java class called ShoppingListLiveData
.
Just add the name and leave all the default options. The folder structure should look like Image 6 after this change.
Renaming MainActivity and ViewModel
Another important part of refactoring is giving classes a descriptive name. Usually, Android provides us with a default activity, which is always named MainActivity
. Let’s refactor the name of MainActivity
and ViewModel
, from this generic title to one that is more clear. Right-click on the file and you will see a Refactor option, then click on the first one “Rename…” (Image 7), be careful not to click the one called “Rename File…”, because it will change the file but not all the references to it.
After clicking this option, you’ll be prompted with a dialog asking you for the new name and giving you various options (Image 8). Leave everything on the default choices and just change the name.
Change the name of the ViewModel
to ShoppingListViewModel
, and MainActivity
to ShoppingListMainActivity
.
After those changes, the project will look like this:
Great! The project now looks cleaner and if another developer wanted to modify your app they would know what each file was for.
Adding a Model for the Shopping List
Another important part when using MVVM, and almost any other architecture, is creating model classes for your objects. Create a model package and a ShoppingItem
model, following the same steps you did for the LiveData ones. The result should look like Image 10:
This model will only have one property — the name of the shopping list item. Add the following code inside of the class: private String name;
.
We will take advantage of the Android Studio code generator to create the getter and setter for this class. Select the newly added line of code and right-click it. Click the menu option “Generate” (Image 11) and then choose the option “Getter and Setter” (Image 12).
You’ll be prompted with a dialog to select fields. If you had more than one variable you may not want to generate getters and setters for everything. In this case, you only have one item so make sure it is highlighted as in Image 13 and click “OK.”
After the change you should have two new methods, and setName(String name)
. In this case, the methods were generated above the property (Image 14). Personally, to keep code cleaner, I prefer to have the methods at the bottom, so rearrange them if you want to.
Creating a Repository Package and Class
Finally, we create a repository. This is not as common in MVVM architecture. You will see different naming for this section of the code — some refer to DAOs and DTOs, others to network layers. In this case, we’ll use the repository for handling the connection with the backend, AKA the Firebase Firestore.
With the same steps you took with the LiveData and Model, create a Repository package and class. Your result will look similar to Image 15:
If you run the app at this point it should be working and still show you the same screen that you saw at the beginning in Image 1. If you have problems, go back and make sure you followed each step correctly.
Connecting Firestore with Android Architecture Components
Finally, we get to the main point of this tutorial. With the structure, we just created we will be able to connect to Firestore and have a really clean architecture in the project.
Connecting the repository with Firestore
In the ShoppingListRepository
you just created, add the following code:
// 1 private FirebaseFirestore firebaseFirestore = FirebaseFirestore.getInstance(); // 2 public ShoppingListLiveData getFirestoreLiveData() { // 3 DocumentReference documentReference = firebaseFirestore .collection("MyGroceries") .document("March26-2020"); // 4 return new ShoppingListLiveData(); }
Let’s break this down:
// 1
: We will be creating a new instance of Firebase Firestore, which should be available if you integrated the library correctly into the project. If you have trouble review the steps in this post.
// 2
: You will create a getFirestoreLiveData()
the method that will return, as its name states, the LiveData coming from Firestore. This method will substitute the loadShoppingList()
method that we are currently hard coding in the ViewModel
.
// 3
: Firestore and LiveData magic happen here. You just need to provide your collection and document name and that will keep track of any changes in the document.
// 4
: Finally, we create a new instance of ShoppingListLiveData, which will later receive this document reference. However, I didn’t add this just yet as it would cause an error at the moment.
Most of the time when you add new code, Android Studio will prompt you with the imports when you copy and paste (Image 17). If you get it just accept the suggestions.
In case you’re typing the code, you may not get suggestions, so here are the inputs you would need:
import com.evanamargain.android.myshoppinglist.LiveData.ShoppingListLiveData;
import com.google.firebase.firestore.DocumentReference;
import com.google.firebase.firestore.FirebaseFirestore;
If you run the app again it should still be working. Make sure it’s not broken and still looks like Image 1.
Connecting LiveData with the Repository that contains the Firestore reference
LiveData allows us to extend classes in order to configure the received data. This example is very simple, so you will just be adding the logic for feeding the grocery list. Still, take a close look at the code structure as it may be useful in bigger, more complex projects.
The first thing you will do will be to extend and implement EventListener<DocumentSnapshot>
. Your class declaration should look like this:
public class ShoppingListLiveData extends LiveData<List<ShoppingItem>> implements EventListener<DocumentSnapshot> {
...
}
You may be wondering why we need these two classes, so let’s see the answer:
LiveData<List<ShoppingItem>>
, refers to the kind of item that will be observed in the app, we have a list of shopping items, but we want this list to come from FireStore, so by extending it, you’re able to modify the default implementation to one of your own.- The
EventListener<DocumentSnapshot>
allows the app to observe firebase and see if a particular document changes. A snapshot is a fixed image of the data — when the snapshot changes anonEvent
will be triggered.
Speaking of the onEvent
method above — as it’s a required method of EventListener<DocumentSnapshot>
, Android Studio will give you an error asking you to add it.
First, add two instance variables:
private List<ShoppingItem> shoppingListTemp = new ArrayList<>();
public MutableLiveData<List<ShoppingItem>> shoppingList = new MutableLiveData<>();
The first line is a temporary list that you will use to add items. After you get all the items in this temporary list you will be adding them all to the MutableLiveData
variable to notify the View and make the necessary updates.
The onEvent Method
With those two variables added you can now create the onEvent method that the error is requiring, add it with the following code:
// 1
@Override public void onEvent(@Nullable DocumentSnapshot documentSnapshot, @Nullable FirebaseFirestoreException e) {
// 2
if(documentSnapshot != null && documentSnapshot.exists()) { // 3
Map<String, Object> shoppingListItems = documentSnapshot.getData(); // 4
shoppingListTemp.clear(); // 5
for (Map.Entry<String, Object> entry : shoppingListItems.entrySet()) {
ShoppingItem itemToAdd = new ShoppingItem();
itemToAdd.setName(entry.getValue().toString());
shoppingListTemp.add(itemToAdd);
} // 6
shoppingList.setValue(shoppingListTemp); } else {
// 7
Log.d("TAG", "error");
}
}
That’s a long method so let’s break it down.
// 1
: You’re overriding the requested method, which will either contain an error or the snapshot of the document you specified, (which by the way is the one in the Repository).
// 2
: You are checking if you actually got the snapshot.
// 3
: Document snapshots a map where keys are the field names, which in your database are Item1
, Item2
, etc. Take a look at Image 19 for clarification. The value is an object because your code doesn’t know which kind of value it will be receiving, although in this case, all values should be strings.
// 4
: You empty the temporary list to avoid duplicates. There are ways to just add to LiveData the new data in the FireStore database, but this is not covered in this tutorial.
// 5
: You loop through all the items in the map of items and create an instance of a ShoppingItem
for each of them. You set the name to the object and add it to the TemporaryList
.
// 6
: This step is key for triggering LiveData. For the app to know that LiveData changed, you need to use the method setValue()
— you’re setting the temporary list there. If you chose to setValue
every time you added an item, you would be refreshing the screen a lot and that’s not necessary. That’s why this method is being called outside of the loop.
// 7
: Of course, in a production app you would manage the errors and maybe retry or let the user know. But that’s a separate topic — here we’ll just log the error.
After these changes your imports should look like this:
import android.util.Log; import com.evanamargain.android.myshoppinglist.Model.ShoppingItem;
import com.google.firebase.firestore.DocumentSnapshot;
import com.google.firebase.firestore.EventListener;
import com.google.firebase.firestore.FirebaseFirestoreException; import java.util.ArrayList;
import java.util.List;
import java.util.Map; import androidx.annotation.Nullable;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
If you run the app at this point, it should still be working as at the beginning (Image 1) because your current code is still not connected to the View
or ViewModel
. Verify it’s still working.
Connecting LiveData and the Repository
Now that we have both parts, the ShoppingListLiveData
and the ShoppingListRepository
, we have to connect them. Remember that you needed to send the document reference in the repository to LiveData.
At the top of the LiveData
class add a variable to store that document reference:
private DocumentReference documentReference;
This will need the following import:
import com.google.firebase.firestore.DocumentReference;
Then add a constructor to this class where you will initialize it.
public ShoppingListLiveData(DocumentReference documentReference) {
this.documentReference = documentReference;
}
Now that you have this variable and it’s in the constructor of ShoppingListLiveData
, the last line of code in the repository will be complaining. Just add your document reference by changing the return line to this:
return new ShoppingListLiveData(documentReference);
The ViewModel
will also be requesting a parameter for that, just assign null to it as we won’t be initializing it in there. Change the line as follows:
ShoppingListLiveData liveData = null;
Testing
Now run the app — it’s finally working! You will see the exact same info in the app on your phone as in the one you see in your Firestore Database. Now play a bit with the data in Firestore:
- Add new items
- Change values
- Delete items
Notice how the app is updating as you do all those changes, below you can see an image on how this worked for me.
Done! We now have an app that uses Firestore with Android Architecture Components. If you want to see how the project should work in the end you can download the finished project on my site.
See you next time!