How to work synchronously with Firebase + Coroutines + LiveData + MVVM + Clean Architecture

Gastón Saillén
Firebase Tips & Tricks
7 min readDec 21, 2019

After moving from MVP to MVVM, there was a lot of going on in my head on how to structure my code in a way that is understandable and scalable

Architecture is the first thing I think about when starting a new project and is this the main entry point that will define quality, testability, and maintainability of the project through its lifetime

I always wanted to work with asynchronous APIs in a synchronous way, because is more flexible and lets our code cleaner than nesting callbacks.

I was working with LiveData in my whole app, doing all my app reactive is not what I really wanted, I had to play with the LiveData states onActive() and onInactive() to structure some jobs, but this is not what I really wanted since it added lots of boilerplate code and also the code was not really clean.

So, I decided to move to coroutines to do my onetime operations and notify my ViewModel about any data I want to retrieve from Firebase.

For example, take this code

EventRepository

This code retrieves a list of events from Firebase, and as you can see, we return a LiveData since we don't know exactly when this operation will finish, and after this operation finish it populates the MutableLiveData with myList and it propagates the result to the ViewModel and then the ViewModel propagates it to the view with another LiveData

EventViewModel

Here we can see that we are also observing to the LiveData inside the Repository and populating another MutableLiveData

And then in my view, I had to also observe the changes emitted by my ViewModel

MainActivity

But there is a problem… Is not a good idea to use observeForever() in the viewModel to observe changes at the repository since this observer like it names says will observe forever until the lifecycle of the view that is attached is destroyed, and it that point when the onCleared() method is trigger will kill this observer, meanwhile it will be listening. And it also will be listening if our activity dies if we do not stop observing in onCleared()

Let's remember ViewModel Lifecycle

As I said, if we do not get rid of the observer before the activity dies it will keep listening for changes and maybe propagates more than one result to the UI when we don't want it.

So, here is where Coroutines come handy since we do not observe in our ViewModel, we just launch a suspend function that will finish later in some time, and then we can decide in which thread execute it.

Take the code from above, the code from the Repository class, imagine now that we need to nest another call inside the onSuccess callback to fetch for example the id’s of those events and go to another collection, we should do something like this

This leads to a callback hell as well as nesting requests that makes our code not really clean.

Now, this post will transform that nested callbacks and callbacks to this

Note how I use the .await() method here, this returns a QuerySnapshot object that we can use to fetch the results inside that Task

Isn't this awesome ? with just one line, we removed the callbacks and our code now is synchronous !!! (sort of, thanks to the libraries will be introducing next 😊)

After a brief introduction of what this post will cover, let's start with a little example on how to fetch events from Firebase and return our results to the UI

Introduction

First of all, we will be using the following libraries inside our build.gradle

Now I will explain a little bit about these libraries, the first one kotlinx-coroutines-play-services will give us the .await() method to work with Task objects, and since Firebase returns Tasks as objects in any call, this will help us

Let's see the method in depth

Here we can see that we need to wrap a Task to use the .await() method and also is a suspend function, which will continue with the result we want or with an Exception

The other dependencies are for Firebase, LiveData, and LifeCycle, basically, the androidx.lifecycle.liveData class will let us work with the liveData{} extension function, this will replace the viewModelScope.launch{} extension function and also will handle all the coroutine jobs to the attached lifecycle of the viewModel, so we don't need to worry to cancel jobs in the ViewModel or worry for any leaks that could happen, also it will detach the livedata if is not active.

Demo App

Let's work with an app that fetches a list of events from Firebase and displays them in our view.

First of all, let's add the value object class Resource

This class will handle our loading, success, and failure from an asynchronous operation that will be seeing later.

Let's start from our UseCase, we need to fetch data from Firebase, so we will have a UseCase in between of the ViewModel and the Repo that will handle the operation from the Repository to the ViewModel

First, we will create an interface that will be implemented in the UseCase

Note that this method is a suspend function because we will launch this request from the ViewModel in another Thread, to prevent blocking the UI, later we will talk about the different Threads

Now, lets Implement this code inside our UseCase

Here we communicate to the Repository through the EventsRepo interface that is implemented at the repository

Now, lets implement this interface in our Repo and do the asynchronous work here

Here as we can see, we use the .await() method, this method as I told before will return the data we want or an Exception, since this is a coroutine suspend function we can handle the exception here or just in our ViewModel.

If this method in the .await() throws an exception, the suspend function will propagate this Exception to the last caller of this suspend function, and the last caller here will be our ViewModel, so, there is where we can handle the exception thrown by this method.

Lets code our ViewModel

See how clean is our ViewModel, here we use the extension function liveData{} this extension function needs a Dispatcher to put our work in a Thread we define after Disparchers.

The different Dispatchers are

From Android Coroutines Docs

To specify where the coroutines should run, Kotlin provides three dispatchers that you can use:

  • Dispatchers.Main — Use this dispatcher to run a coroutine on the main Android thread. This should be used only for interacting with the UI and performing quick work. Examples include calling suspend functions, running Android UI framework operations, and updating LiveData objects.
  • Dispatchers.IO — This dispatcher is optimized to perform disk or network I/O outside of the main thread. Examples include using the Room component, reading from or writing to files, and running any network operations.
  • Dispatchers.Default — This dispatcher is optimized to perform CPU-intensive work outside of the main thread. Example use cases include sorting a list and parsing JSON.

Then we use a try catch block to handle the result or the exception, let's start with the result

val eventList = useCase.getEvents()

This will call the suspend method getEvents() and wait until it returns a MutableList<Event>

emit(eventList)

After the Event list comes from the Repository we emit that value to the liveData{} extension function, this works as a .value or .postValue we used with MutableLiveData, there is also emitSource() which will cancel (dispose) the other emits we did before, this can be used to do a catching strategy.

After we do this emit our val fetchEventList is now populated with our List or an exception if someone is thrown

Exceptions are caught and propagated from our repo to our ViewModel that is launching the coroutine, so there is nothing to do in our repo to handle any exception, this will be passed to the catch block and since all Exceptions extends from Exception, this (e:Exception) will handle all the exception for us that could be thrown inside our repo

For the last step we need two things, the first one is to make a Factory for your ViewModel since we cant instantiate our ViewModel with parameters

And for last in our UI we wait for the Result and handle it

MainActivity

That's all, we have now a cleaner code to work with coroutines, livedata and clean architecture, now if this post helped you, there is a clap button, you can clap this post how many times you like :)

PD: if you want to keep listening for changes you should use flow, in this post we discussed one time operations

Repository

--

--