Android Architecture: MVVM with Coroutines + Retrofit + Hilt + Kotlin Flow + Room

Narendrasinh Dodiya
5 min readNov 2, 2020

--

Photo by Pixabay

When you are starting a new Android App the first thing you want is to have the best architecture for your App. Ideally, you would want your architecture to be built with the best development trends in Android.

The quest for the perfect architecture starts by researching and learning top architecture trends. As the MVVM being the officially recommended architecture, it is an ideal choice to make.

There are many articles on building MVVM architecture using LiveData, ViewModel, Room, Retrofit, Coroutines, Dagger, RxJava, repository pattern, and the single source of truth strategy, etc. But, when it comes to putting all pieces together, things get tricky.

The official Android Architecture blueprint provides excellent MVVM architecture samples. But, the samples don’t use any networking library(e.g. Retrofit) library and instead mocks the Remote Data sources.

Also, I came across this article on why you should not be using LivData in repositories ‘No more LiveData in your repository’. And the architecture blueprint samples use the LiveData in repositories.

Motivation

Ideally, you would want to build something very scalable yet very concise, easy to understand, and simple to implement. Thanks to the introduction of the new official members, Kotlin Coroutines, Kotlin Flow, and Android Hilt.

We will build an MVVM architecture using all these concepts. Let’s get started.

The best way to learn is to implement it. So, we will be building a sample app that uses the TMDB API to show a list of trending movies. The detail screen shows details about the selected movie.

You can find the complete source code of this sample project on GitHub.

Let’s get started!

Model

View

We don’t need to create the Factory for our ListingViewModel. Hilt handles all the pain. Refer.

The listing screen observes the viewModel.movieList LiveData exposed by the ViewModel and feeds the data to the MovieAdapter to display.

Every response is wrapped inside the Result class, which holds the 3 different statuses of the response(SUCCESS, ERROR, LOADING).
The data property holds the success value in case of successful response and error and message properties in case of failure.

Listing screen

ViewModel

The Hilt will inject movieRepository dependency in the constructor.

Kotlin Coroutines makes it super easy to do the IO work.fetchMovies() launches a Coroutine in the viewModelScope provided by the ViewModel KTX library.
You don’t need to worry about canceling the operations when ViewModel goes away. The viewModelScope will manage everything for you. You can read more about Coroutines scopes and Structured concurrency here.

movieRepository.fetchTrendingMovies() returns the Flow object. The collect will be invoked when any value is emitted to the Flow. We will learn more about Flow in the next section.

ViewModel for Listing screen

Repository
The repository will be responsible to provide the data either from the Remote or Local data sources.

flow{} builder constructs the Flow object. The Flow exposes the data as a stream like RxJava. The flowOn(Dispatchers.IO) specifies the Coroutine context for the execution. The emit() will invoke the collect() in our ViewModel. Here we will emit 3 different values.

emit(fetchTrendingMoviesCached()) emits the cached values from the Room database data.
emit(Result.loading()) emits the loading state.
emit(result) emits the result from the API response.

We will cache the data when the response is successful.

Note: The approach that we have used in this sample app is less complex and gives you control over the Loading status. But, If you really want a more clean Single Source of the Truth strategy for your data, you can easily do that by returning Flow from your Room Dao. The Room library already has nice support for this.

Repository

Remote Data Source
The remote data source will be responsible for fetching the data from the REST APIs.

The MovieRemoteDataSource uses the Retrofit library to fetch the data from the TMDB REST APIs. Hilt injects the required dependency in the constructor.

getResponse is the Higher-Order Kotlin function which accepts the Retrofit Service function as the method parameter and returns the Retrofit response wrapped inside Result class. This makes our code concise.

Remote Data Source

Retrofit already has Coroutine suspend function support. We just need to add the suspend keyword in our service interface.

Local Data Source: Room Dao
The local data source will be responsible for storing and fetching the data from the Room Database.

Local Data Source

Hilt: Dependency Injection
The Hilt is built on top of the Dagger and it is very less boilerplate and it is part of the Jetpack libraries and easily integrates with other Jetpack libraries.

The only dependencies we need to construct is the Retrofit and the Room database. Which is pretty straight forward.

Retrofit Dependency
Database Dependency

You can read more about using Hilt in your App from here.

That’s it. We are done!
You can find the complete source code of the sample project on GitHub.

I haven’t implemented pagination in the listing screen to keep the sample app very small and simple to understand.

You might not need Kotlin Flow at all.

We returned everything as Flow from our Repository. But, if you just need one shot operation, then the same can be achieved by simply using the suspend function. You can learn more from here.
In our case, Flow helps us to emit the statuses along with the success and error response.

Disclaimer
We must first understand the core concept of any new approach or library before randomly putting everything together.

Summary

  • We learned how to put all new Android concepts together and make it work seamlessly.
  • We learned how we can use Kotlin Flow/suspend function to make your app concise and readable.
  • We learned how Hilt Dependency Injection makes your code less boilerplate and testable.
  • We learned how to use Retrofit with Coroutines and wrap the response with success, failure, and loading states.

Please let me know your valuable inputs in the comments.

I would appreciate Claps if you find this article useful. Your Claps will keep me motivated to continue doing the good work.

--

--