Application Design Patterns

Using the Android MVVM Pattern with Firebase

Yashovardhan Dhanania
Firebase Developers
7 min readAug 11, 2020

--

In this post, I will describe how I use the MVVM pattern with Firebase. This post assumes you are familiar with the MVVM architecture and have some experience with LiveData and coroutines. All code is written in Kotlin.

If you have read about MVVM with Android, the below diagram is what you are most likely familiar with.

source: https://developer.android.com/jetpack/guide

However, with Cloud Firestore, we can remove the last two parts of it. This is because Firestore provides its own local cache. An (additional) local cache is not needed or recommended. This means we can remove the model and remote data source and combine them in a single repository class as shown in the below diagram.

Source: Modification of the above image with my very limited editing skills

For any given part of the app, I end up using four different classes:

  1. Activity/Fragment
  2. ViewModel
  3. A single firestore service object
  4. Data class (Object representation of the required data)

Data Class

Let’s start bottom-up, my data class is a standard Kotlin data class with a companion function to convert document snapshots to the profile object. I know the standard .toObject(class) function can help me with this, but I prefer writing my own function for this. This helps better handle errors and provide default values. In some cases, it even helps shift from standard data types to enums for better representing certain elements.

A standard user profile data class can look something like:-

You will notice the added Crashlytics reporting. This is something I found useful after working on a production database where data types were often mixed up. Further, by returning null, I can tell my Firebase service function that something was wrong with this particular document and we can ignore it if needed.

Firestore Service object

For hosting our database code, we use a Kotlin object. This makes our Firebase Service a singleton, making only a single instance of this service available. This means we can easily access the functions defined here from anywhere in our code.

Side-note: As Rodrigo pointed out in the comments, be sure to include this dependency in your app level build.gradle file. This is required to support coroutine calls like await() for firebase objects.

implementation ‘org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.4.1’ # Update to the latest version

A simple Firebase service object with a get function could be something like:

I am using suspend functions as they can easily be launched from any coroutine. They provide the flexibility of synchronous programming with async code. We will be calling this function from our view model which already has a coroutine scope viewModelScope.

Notice that I am again using try-catch blocks. This time, it helps determine errors like missing documents/invalid collection names. Further, it makes use of the toUser() extension function we wrote earlier. This makes the code both easier to read and more reusable.

Now, let’s say we need to get a collection of documents - for example a list of friends. You can add another function to the same object to return a list of users.

Notice the mapNotNull function? It is really useful in these situations. You want to avoid showing malformed documents and our original toUser function returns a null for every document not confirming to our structure. We simply return an empty list in case we face some errors. The user doesn’t need to know what exactly went wrong. Only you need that information.

Flows

Another topic I would like to touch on is Kotlin flows. Flows are kind of like LiveData but don’t run until someone is collecting the flow. Then they emit values to the collector. Flow also adheres to Coroutine cancellation principles and can be extended with operators for complex tasks.

In Firestore terms, a flow can be used with a snapshot listener. We can attach a listener when the flow is first collected and then detach it when the flow is cancelled. This can be really useful for things like live feeds, chat applications, and apps where some data is being continuously updated.

Note that callback flow, which we are using below, is a part of the experimental coroutines API. So you will need to use the annotation @ExperimentalCoroutinesApi whenever you use this.

A very simplified example of a flow is:

Please note that the above example is very basic. An actual use case can be much more complex.

Anyways, the main element to note here is the CallbackFlow. A callback flow is used (as the name suggests) to attach a flow to a callback like the Firebase snapshot listener. The basic steps being done here are:

  1. Creating a listener registration inside a callback flow
  2. Cancelling the registration in case of any error
  3. Emitting the results via the offer() method
  4. Calling awaitClose

Now, awaitClose needs a special mention here. This single statement keeps this flow active and ensures it waits till it’s closed or cancelled. When it’s closed, we can safely detach the Firebase listener we attached earlier.

ViewModel

Now comes one of the most important elements of the architecture - The ViewModel. The view model can be thought of as the heart of your app. It connects the data to the UI while handling the logical part of your app.

You need to calculate and update a score based on some formula? The view model handles it; You want to generate a random number? Leave it to the view model. This is also the layer where we convert the data from Firebase into our beloved LiveData streams and let the UI handle the rest.

Let’s create a view model for using the above repository we created:-

Many interesting points to note here. Firstly, notice how I am using a private _userProfile and a userProfile differently. The major reason for this is that you don’t want to expose your mutable properties to your activity/fragment. Only the viewModel has access to the mutable live data while only the immutable live data is exposed.

Now, in this example, I have two liveDatas - userProfile and posts. userProfile is an example of a single object while posts is a list of objects. These represent the two types of calls you will mostly be making to Firebase. A single document and a collection.

Notice the viewModelScope, it is a special CoroutineScope provided by Android for use in your view models. Since our Firebase functions are defined as suspend functions, you can only call them from within a coroutine scope or another suspend function. However, using viewModelScope makes it easy to manage your data. All pending coroutines are automatically cancelled when the view model is destroyed. This helps prevent memory leaks and unnecessary network calls. You can learn more about cancellation in coroutines here.

If you need your list to be updated, there are two ways. The first one is using a flow with a listener (as shown above) and you can use the .asLiveData() method to convert your flow to a liveData. The other is manually querying the database again and adding the new documents to your list. For this, you can take help from the paging library. Firebase allows you to set conditions such as startafter and limit for your queries where you can pass the last document you received and receive the next N documents.

Normally, I would store a private mutable list in the viewModel along with the live data abstractions for this particular case. When I get the new documents, I can add them to my mutable list and then update my live data with the new list.

Activities & Fragments

Now we come to our final layer in this architecture - our UI layer.

You are already familiar with this layer. I will only be highlighting how to use our view model with this.

Our first step with our fragment or activity is to initialize the views and then get an instance of our view model using ViewModelProvider. Note that although I am using data binding in the above example, it is not necessary to use data binding in your project. Using it, however, will make your life easier.

So coming to our view model, we have two LiveDatas we want to observe from our Fragment. We attach a simple Observer while specifying the owner as viewLifecycleOwner. This allows our LiveData to be automatically managed by the fragment’s lifecycle. When our lifecycle is destroyed, we will automatically stop observing the liveDatas.

Now, in our observers, we only get the current copy of the data. So, userProfile will have the updated user profile as fetched from Firebase (or null if nothing is fetched yet). posts will have the latest copy of our posts list. We can then send this data to our adapters or use it directly in our views. Just like you normally do. When the data is updated, the observer is called again so you can easily update the UI almost instantaneously. Make sure your observer is suitable for running multiple times even in succession.

Now our UI layer doesn’t need to worry about any implementation detail. This means if your backend logic changes, you can easily make changes to your app without worrying about modifying the UI.

Conclusion

Thank you so much for reading — I hope you learnt something.

Architecture components with Kotlin make it easy to manage your app. I have personally used most of the concepts demonstrated here in actual production apps. You can learn more about Jetpack Architecture components on the Guide to app architecture. If you are just getting started with view models, this Codelab might be a good starting point.

I must admit this article was highly opinionated and based on my experience dealing with Firebase and MVVM. You might have a different or better approach to handling some of the things I mentioned here. If so, please do leave a comment, I would love to hear more about it!

Resources

--

--