Tutorial: Modern Android Development with MVVM, LiveData and Firebase (Part 2)

Elijah Dangerfield
8 min readMar 3, 2020

This article is part of a series on creating modern Android applications with modern tools. All other parts, as well as the finished code, can be found below

Part 1: (Setting up the project)

Part 3: ( LiveData and reactive programming)

Part 4: (Finishing up final product)

Github/Elijah-Dangerfield/TriviaApp

Welcome!

In part one we just finished creating a connection from our front end (Android application) to our backend (Firestore Database).

Up until now our project has mostly been planning and set up. It's definitely not my favorite part of being a developer. But don't worry, in this article, we will get into the meet of creating our trivia game.

In this article, we will be covering the architecture of our application. We will be implementing the MVVM architecture skeleton and explaining the different layers in MVVM as we go.

Why MVVM

There are plenty of different architectures to choose from. There is MVI (Model View Intent), MVP (Model View Presenter), MVVM (Model View View-Model) and more. They all have the common goal of creating a clean, maintainable code base with a separation of concerns.

By separation of concerns I mean to say that we will have specific layers that are assigned a job, and are only concerned with that one job. These jobs will relate to creating a data flow from Firebase down to our view. Separating the code in this way allows us to create non-coupled, maintainable code.

Alternatively, we could fairly easily place all of our code in the main activity. However, the code will soon become much like a house of cards. Every line of code may have some unknown impact on some other line of code and making even the slightest change to our app could become very difficult.

Thus, by using MVVM and separating our code into different layers only concerned with one part of our data flow, we can create clean, maintainable code.

In traditional MVVM the different layers in our data flow are:

  1. Model (Used to model the data)
  2. View (Used to show the data to the user)
  3. View-Model (Used to model what data/state the view should show)

Creating Our Different Data Layers

We will be implementing the MVVM architecture with an added layer: A Repository layer (Used to get the data)

We will go over each layer, its concern and begin implementing them. Given the separation of concerns in MVVM, some layers will have no idea of the existence of others. After all, the View layer is only concerned with showing data, it has no concern with where the data comes from and thus, knows nothing of our Repository layer (the layer concerned with getting the data)

It can get pretty confusing but you can refer to this illustration of our specific architecture as we go.

This image illustrates the different layers in our data flow and the communication between them. For simplicity, we will not have a persistence layer.

So let's start, shall we?

Repository

The repository is the layer in our data flow that is concerned with getting the data. In Android terms, it will be a class that exposes the ability to query Firebase and return the data to the caller.

A common practice is to create an interface for our repository to implement.

This makes testing easier by allowing us to mock our repository layer using the interface. There is also the benefit that if you ever wanted to switch from using Firebase, you can simply implement the interface with your new service and not have to worry about changing any of your other data layers as they only need an implementation of the interface.

To start we can create an empty interface and an empty class that implements that interface to represent our repository layer. which we will implement soon.

Model

The model in MVVM is simply the way in which we represent our data. Considering our data will be questions from Firebase, we will be defining a simple data class to hold each question in the same format they are stored on Firebase. Each document will represent one question so we will need a data class to match our documents.

We can actually complete our entire Model layer now by creating this simple class:

View-Model

The view-model in MVVM is the layer in our data flow that represents what our view will hold (aka models it). That being said any data, or state we plan to have in our view, we should have a representation of it in the view-model. It is also of note that view-models are allowed to have business logic in them as well.

The view model itself gets any data needed from the repository. In Android terms, our view-model will be a class that extends the ViewModel class given to us by Android. We can then give it a reference to our repository layer which creates the connection between the two as seen in the diagram.

The golden part about the view model class is that it is lifecycle aware, meaning that when our view dies because of something simple like an orientation change, our data does not. When the view comes back to life it just asks the view-model how it should look (because of the view-model models the view!)

For now, we can create an empty class that simply extends the ViewModel class and give it a reference to our repository.

View

And finally the last part of our data flow: the view. The concern of the view in our data flow is simple: show the data. In Android terms this will be an Activity or Fragment that has an XML file with the view components to be used. The view simply gets the data/state from the view-model and feeds the view components with the data to show the user and handle user interaction.

For our simple app, we have multiple ways we can implement the view. With our app only having 1 screen it would not be a terrible idea to keep the MainActivity as the view layer. However, if we ever wanted to add multiple screens such as a neat starter screen with category selection then we could use a fragment as our view layer.

I personally love using fragments. I specifically will be using the new navigation component from the Jetpack Libraries. If you want to do the same then there is a bit of set up to complete. The good news is, there's a great article to show you how to do that here:

Better programming: Jetpack Navigation Components in Android

Regardless of whether you are using an activity or fragment, for now we can just keep it minimal and add a reference to the View Model like so

A Note On Organization

Beyond organizing data flow, I personally like to keep my code base organized in packages. If you want to follow the same practices as me, this is how I organized the code for my different data layers.

Note: I keep the main activity out of ui as I use fragments for all ui.

Feeling accomplished?

You should! Although all of our classes are empty we have implemented the skeleton of our architecture. The last part of our goal is to fill in each part and define our data flow.

So Let’s Build That Data Flow

A good place to start for defining our data flow is at the top: the repository.

In the end, we want to be able for our view to have access to a list of questions. So our repository must have the ability to get that data and expose it for the data flow.

The first step to doing this is adding the name of our function in our repository interface. I will simply call it getQuestions and have it return what we want to flow down to the view.

Adding this function declaration to the repository interface will raise a flag in the repository implementation because we must define this function in the implementation.

In part 1 we built a function to pull and log all questions in our “questions” collection on Firestore.

Log.d("Elijah", "Getting questions")
val db = FirebaseFirestore.getInstance()
db.collection("questions").get().addOnSuccessListener {
it
.documents.forEach {question ->
Log.d("Elijah", question.data.toString())
}
}
.addOnFailureListener {
Log
.d("Elijah", "Failed with: ${it.localizedMessage}")
}

In order to get our list of questions for our data flow we only need to tweak this code to read in the data as our model (the question class)and be stored in a list.

Just as before this code uses our Firebase Firestore instance to get all documents. The only difference is that this time it reads them in as a list of type Question.

To prove that this function works we can instantiate a repo in our view and just log the result.

After running the application you can look at the log cat and filter by the string “Elijah” to see the results

OH NO

As you can see the view got an empty list but the repository got the actual data.

Don't worry, this was all orchestrated by yours truly to illustrate a modern development obstacle: Asynchronous programming.

Our “problem” comes from the fact that Firebase’s API for Firestore operations are all asynchronous automatically.

In front end development we often will run non-view related tasks (such as fetching data) asynchronously. In mobile development, this keeps the ui thread light to make sure the user doesn't get caught on a frozen screen while things load. Instead, data is fetched in the background and when the task is done, the view is notified in some way.

So what do we do?

In order to work around this problem, we are going to look into something known as reactive programming in part 3 (link below).

I'll see ya there

Part 3: ( LiveData and reactive programming)

--

--