Reading Time: 10 minutes

Firebase Firestore and Android Architecture components, seem to fit together perfectly, despite that, there is little information on how to tie together these tools. This was brought to my attention by Ashton Jones and in response, I’ve created some posts explaining how to integrate them. In this Firestore with Android Architecture Components Integration, 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 still followed along without the need of reading the previous ones. In case you are curious, here are they:

Let’s start

If you want to follow along starting here, you can download the Android project below:

This project is written in:

  • Android Studio 3.6.2
  • Kotlin 1.3
  • Compat Library: AndroidX

You will need to configure the Firebase Firestore database by yourself because sadly it can’t be provided as an attachment, but here it is if you need to know how to do it. Once you have met these pre-requisites you can start with this tutorial.

Run the app and you will see the following screen:

Image 1. Initial view of the App

A simple shopping list, which is a very common sample app with a very basic architecture.

Separating Live Data from the View Model

When developing Android projects it is always important to refactor constantly. As Uncle Bob Martin once mentioned:

Always leave the campground cleaner than you found it.

Clean Code. Robert C. Martin from the boy scouts

You will start this project with a refactor so that it fits in MVVM architecture better. Currently, the structure of the starter project looks like this:

Image 2. Starting app structure

In this refactor, you will be adding three new packages to the app:

  1. LiveData
  2. Model
  3. 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 projects information. The choice of the package naming varies widely by project and structure, but in this case we will have a package for each of the architecture parts. Start this by creating a LiveData package, by right clicking on the projects main folder as shown in Image 3.

Image 3. Create a new package menu

Name the package LiveData as in the image below.

Image 4. Name of the LiveData Package

Inside of that new package create a Java class called ShoppingListLiveData.

Image 5. Create a new class menu

Just add the name and leave all the default options. The folder structure should look like in Image 6 after this change.

Image 6. Project structure after adding the LiveData package and class

Renaming MainActivity and ViewModel

Another important part when refactoring is giving classes a descriptive name, usually Android provides us with a default activity and it is always named MainActivity. Lets 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 that one will only change the file but not all the references to it.

Image 7. Refactor menu

After clicking this option, you will 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.

Image 8. Renaming the ViewModel

After those changes, the project will look like this.

Image 9. Project structure result after refactoring the ViewModel name

Great! The project now looks cleaner and if another developer wanted to modify your app they would know what each file is 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.

Image 10. Shopping item model and its package

This model will only have one property that will be 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 on it. Click on the menu option called ‘Generate…’ (Image 11) and then choose the option ‘Getter and Setter’ (Image 12).

Image 11. Menu for generating code
Image 12. Option for generating getter and setter

Then you will 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’.

Image 13. Choosing variables that need a getter and a setter

After the change you should have two new methods, ‘getName()’ 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 methods at the bottom, so rearrange them if you want to.

Image 14. Result of the generation of a getter and a setter

Creating a Repository package and class

Finally, we will 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 will use the repository for handling the connection with the backend, AKA Firebase Firestore.

With the same steps as you did with the LiveData and Model, create a Repository package and class. Your result will look similar to Image 15.

Image 15. Project structure after creating the repository package and class

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 trouble go back in the steps and make sure you followed everything correctly.

Connecting Firestore with Android Architecture Components

Finally, we got to the main point of this tutorial. With the structure we just created we will be able to connect to Firestore, plus 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 down the code above:

  1. We will be creating a new instance of Firebase Firestore, which should be available if you integrated the library correctly to the project. If you have trouble review the steps in this post.
  2. You will create a ‘getFirestoreLiveData()’ 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 + LiveData magic happens 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, but I didn’t add this just now as it would cause an error at the moment.
Image 16. Firestore Database with the collection and document name used in the code.

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.

Image 17. Import prompt from Android Studio.

In case you are typing the code, you may not get suggestions, so these 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 in 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, as this example is very simple you will just be adding the logic for feeding the grocery list. Still, take a close look at the code structure ass this may be useful in bigger, more complex projects.

The first thing you will do will be to extend LiveData<List<ShoppingItem>> and implement EventListener<DocumentSnapshot>. Your class declaration should look like this now:

public class ShoppingListLiveData extends LiveData<List<ShoppingItem>> implements EventListener<DocumentSnapshot> {
  ...
}

You may be wondering why we need this two classes, so let’s see the answer:

  1. 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 are able to modify the default implementation to one of your own.
  2. 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, and when the snapshot changes an ‘onEvent’ will be triggered.

Speaking of the ‘onEvent’ method above, as it is a required method of EventListener<DocumentSnapshot>, Android Studio is giving 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 together 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 are 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 are got as a map where keys are the field names, which in your database are ‘Item1’, ‘Item2’… 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 are 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 so here we will just be logging the error.
Image 18. Document Key Value Pairs

After all this changes your imports should look like the following:

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, you have to connect them. Remember you needed to send the document reference in the repository to Live Data.

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 is 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 and it’s finally working! You will see the exact same info in your phone than the one you see in your Firestore Database. Now play a bit with the data in Firestore:

  1. Add new items
  2. Change values
  3. 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.

Image 19. Final result

Done! We now have an app that uses Firestore with Android Architecture Components. If you want to see how the project should work at the end you can download the finished project here:

If you liked the content on this post, please subscribe at the bottom of the page. If you have any doubts don’t hesitate to leave a comment and I will reply as soon as possible. You can also donate a coffee to support me so I can keep on creating things like this!

See you next time!

Evana Margain Puig

(Visited 105 times, 1 visits today)