Practical Android MVVM: Yes-No Gif Demo App

Alfonso García Santiago
8 min readApr 29, 2020

--

Photo by Kelly Sikkema on Unsplash

In this article, we’re going through a simple example of Android MVVM and LiveData with Retrofit2 and RxJava2.

Some concepts, components, and libraries that appear in this reading:

  • Android Jetpack: ViewModel and LiveData
  • MVVM
  • Repository
  • RxJava2 (RxAndroid)
  • Retrofit2
  • API REST / JSON
  • Gson
  • Glide

Example API

For this example, I’ve decided to use a cool and crazy API:

https://yesno.wtf

This API provides us with random GIFs labeled by Yes or No. You are supposed to think of a yes-no question and then pressed the button to request and load a yes-no GIF to the previous API.

At the end of this document, you can find a link to the repository in Github.

Project Structure

We can see in this capture the project structure in packages. This is a very small project, so we’ve distributed classes and functionality across packages and not modules.

We can see three main packages: model, view, and viewmodel (MVVM architecture pattern):

model

  • data: data sources (only API in this example)
  • repository: repository and data mappers
  • domain or business logic

viewmodel

  • ViewModel classes

view

  • ui: Activities and Fragments

Architecture (MVVM Pattern)

I would like to highlight that this is a non-complex project so MVVM by itself is good enough to design a smart architecture. But if we were working on a very complex project, we would also need to use CLEAN Architecture to complement the MVVM pattern.

In a CLEAN Architecture among other things we would introduce:

  • Use Cases to manage business rules and uncouple ViewModel from Repository (avoiding that a change in Repository interface might propagate to ViewModel). Here it would introduce unnecessary complexity. In a non-complex project, this logic can be managed perfectly by ViewModels.

So MVVM applied to this example would be:

Later we’ll explain more in detail ViewModel and LiveData. Also RxJava.

Here, talking about the architecture’s big picture, we can say that we’re using MVVM architecture pattern and Repository design pattern:

MVVM Architecture Pattern:

  • ViewModel permits us to uncouple Fragment (UI) from Repository and it is done in a lifecycle-aware way (due to LiveData) and resisting configuration changes of the App

Repository Design Pattern:

  • It is a clean interface to obtain data and to uncouple data sources (API, Database, etc) from the rest of the App.
  • It is recommended to use model classes for each data source. In this example, the only data source is Api or Network and it uses its own answer version called AnswerApiDTO which will be mapped to Answer domain class in Repository.

API call and JSON Response

For this example, we are using the Yes-No API which returns a random answer : “Yes” or “No” or “Maybe”. And also it includes a link to a comical GIF in which we can see famous people giving their approval or disapproval… or none of them.

The endpoint that we are calling is:

The JSON Response we obtain is something like that:

{
"answer": "yes",
"forced": false,
"image": "https://yesno.wtf/assets/yes/......gif"
}

JSON is a standard format normally used to represent and interchange data between a client and a server. It is a human-readable format based on a set of attribute-value pairs and array data types.

In this JSON response, we have got three attributes. In the next sections, we will see how to model this response with a data class and how to use GSON as an adapter for Retrofit to convert from a JSON response to a data object.

Fetching data with Retrofit2

Retrofit is a type-safe HTTP client for Android and Java. With Retrofit, we can send data and fetch data easily to and from an HTTP server.

Thanks to Retrofit we can see HTTP API as a Kotlin interface.

Step 1: Create the interface service class

This interface contains methods that correspond to HTTP methods like: GET, POST, PUT, PATCH, DELETE, and others.

  • In our example, we only need to do a GET request so we use annotation @GET followed by the relative URL of the endpoint (this relative URL “/API” is concatenated to the base URL specified later in the RetrofitClient builder).

Also, we could pass parameters using annotations like @Path or @Query depending on the parameter type.

  • Here, we don’t need to pass parameters.

We call our interface AnswerApiService because this API endpoint returns a random answer (yes, no or maybe) and a GIF URL. All this is encapsulated by data class AnswerApiDTO (as we can see later). We also talk later about the Rx Observable object returned by this method. For now, this is the Retrofit service which contains the desired API endpoint.

Step 2: Retrofit client configuration

Now we are going to configure Retrofit. This way Retrofit can convert our AnswerService interface into a callable object. We set it with:

  • base URL: this base URL is the common base URL for all the endpoints relative URLs written in the service interfaces
  • OkHttpClient object: we create only one instance of OkHttpClient and reuse it for all the HTTP calls (this is the way OkHttpClient recommends)
  • GsonConverterFactor object as ConvertedFactory to create an object of our AnswerService interface which uses Gson to deserialize JSON to Kotlin when receiving data from server (and to serialize Kotlin to JSON when sending data to the server)
  • RxJava2CallAdapterFactoryobject as a CallAdapterFactory to create an Rx Observable to wrap response data

Gson converter (JSON <-> Kotlin data class) and Answer model

Gson is a Java library that can be used to convert Java Objects into their JSON representation. It can also be used to convert a JSON string to an equivalent Java object. Gson can work with arbitrary Java objects including pre-existing objects that you do not have source-code of.

We model JSON data response with the following Kotlin data class using annotation @SerializedName from Gson library:

Do you remember JSON response?

{
"answer": "yes",
"forced": false,
"image": "https://yesno.wtf/assets/yes/.......gif"
}

In the object RetrofitClientwe configured Retrofit passing a GsonConverterFactory.

Repository returns Observable of a domain class

Repository returns Observable of a domain class (not a data source class).

As we can see above, repository implementation receives an Observable<AnswerApiDTO> from AnswerApiService and maps it to Observable<Answer> before returning it.

The class to maps data is DataMappers:

Normally, Repository implementation fetches data from different data sources in different ways (order-based strategies) but in this example, we only have Api as the unique data source. So Repository only calls AnswerApiService.

Subscribing to the Rx Observable returned by our Repository

As we already know, the getAnswer() method of AnswerRepository returns an Observable<Answer> object.

As we can see below in the code, we subscribe to this Observable<Answer> following the Observable Contract:

  • We provide implementations for callback functions: onNext(), onError() and onCompleted().

There are 3 important aspects of this subscription:

  1. We tell Retrofit, who creates and returns the Observable<Answer>, that we want to fetch data operation from an IO thread by subscribeOn(Schedulers.io())
  2. With the fetched data we are going to update the viewmodel’s livedata called answer. There are two methods to update livedata’s value:setValue()andpostValue().First one,setvalue(),updates the value immediately and it must be called from UI thread. Second one, postValue(), posts a task to the main thread to update the value but it isn’t done by itself right now. In this example, we will use setValue() method, so we will tell Retrofit to observe with observeOn(AndroidSchedulers.mainThread())
  3. subscribe() method receives three functions as parameters. These callbacks are: onNext(), onError() and onCompleted() and they are executed from the main thread as we’ve indicated in the previous point.

About this last, we are programming in Kotlin and the subscribe method invoked is:

subscribe (
onNext: (Answer) -> Unit,
onError: (Throwable) -> Unit,
onCompleted: () -> Uniy
)

and to pass to subscribe() the three anonymous functions implementations we use kotlin lambdas resulting this:

subscribe ( 
{ value -> answer.setvalue(Result.Success(value))},
{ error -> answer.setValue(Rsult.Failure(error))},
{ println(“Getting answer completed”)}
)

ViewModel and LiveData

ViewModel and LiveData are both Android Jetpack Architecture Components.

ViewModels

  • manage and store UI-related data
  • usually, keep this data inside LiveData which contains data observed by UI controllers like Activities and Fragments
  • obtain this data, for example, requesting it to the repository
  • maintain this data during configuration changes
  • also may receive requests from view in response to user interactions
  • must never reference a view or lifecycle or a context (if they need to access system services then they should extend AndroidViewModel)
  • its scope match lifecycle passed when it is created through a ViewModelProvider. So if we pass an activity as lifecycle owner then ViewModel will live until the activity is finished and if we pass a fragment as lifecycle owner then until the fragment is detached and gone away permanently
  • for example during a screen orientation change the activity is re-created so it receives the same ViewModel instance that was created by the first activity. This is also true for a fragment because although during a configuration change the fragment is destroyed, after that it is re-created (I mean a new instance of the same fragment) (we don’t navigate to another fragment)

LiveData

  • holds data which are observed by activities and fragments
  • is lifecycle-aware, when wrapped data changes then it only notifies its observers if they are in an active state considering their lifecycle

Our AnswerViewModel contains theLiveData answer:

val answer by lazy {
MutableLiveData<Result<Answer>>()
}

Remember Answer domain class is:

Result wraps Answer to provide status information about the networking request:

And remember that inside subscribe Rx block we update LiveData answer:

subscribe ( 
{ value -> answer.setvalue(Result.Success(value))},
{ error -> answer.setValue(Result.Failure(error))},
{ println(“Getting answer completed”)}
)

You can get the code of the rest of the code in the project on Github (there is a link at the end of this document).

Fragment observes LiveData

AnswerFragment observes Viewmodel’s LiveData answer:

Once we have the image URL (answer.image in the previous snippet), then we call loadGirFromURL() method to use Glide for loading and rendering the GIF.

Glide

An image loading and caching library for Android focused on smooth scrolling.

Glide is a fast and efficient open source media management and image loading framework for Android that wraps media decoding, memory and disk caching, and resource pooling into a simple and easy to use interface.

We invoke Glide passing it:

  • an image URL, so Glide will download the GIF from the web
  • an imageview where painting this GIF
  • a context because Glide is lifecycle-aware and it pauses, resumes or cancels current resource request based on lifecycle’s state of the fragment or activity passed as a parameter

As I said before in the Architecture diagram, Glide covers UI-related and fetching-data-related functionality, so it breaks the architecture aspects separation principle in some way. It has responsibilities from UI and Data.

Finally, you can take a look at AnswerFragment class on the repository.

I hope you’ve found this article enjoyable and useful!Any doubt, question or observation? Please, leave a comment!

Get the project’s source code on my Github!

--

--