Getting started with your post-COVID-19 Android App with Coroutines, LiveData, ViewModel and Koin!

Abhriya Roy
11 min readJun 21, 2020

So you’ve survived the COVID-19 pandemic and are gearing up to build your own Android app for this post-apocalyptic world? Let’s familiarize ourselves with the right guns to get our job done.

In this futuristic world, we are part of an organization that operates with intergalactic spaceships and our mission is to build an app that will show a list of trending movies to our travelers during their long intergalactic journey.

Let’s code name our mission as MovieD! 😏

Before getting any further, let's make it clear, that this mission is only for the beginners and for those wanting to brush up the basics!
You can go through our entire source code here:-

But let’s go through it together to ensure mission success and remove all confusions that might come across our path.

Planning is important, so lets first list down the weapon we will be using:-

  • Navigation component (this will help us navigate to different screens we will also see how to implement shared element transition using these)
  • Koin (it's essential to enforce the D in SOLID principles and also the easiest way to do so. Unlike something known as Dagger!)
  • ViewModel (hence we are obviously going to implement the MVVM architecture pattern)
  • LiveData (to let our data survive state changes like a rotation of the screen)
  • Kotlin Coroutines (to get to the background thread easily)
  • Retrofit (the easiest way to make network calls)

Before launch…

Let's familiarize ourselves with the server endpoint and how we will communicate with it.

We will be using The Movie Database to fetch our movies from. You can sign up for a free account and obtain an API key from your accounts settings like below :

Let's get started!

Alright, now that we know what weapons we need to possess, lets put them in our bag so that we have them at our disposal during our mission. Our app/build.gradle will look something like this.

Well, something’s, not right! Ah, we have defined a bag full of guns named app_module_dependencies() located at ./app-dependencies.gradle . But where are they?

So, let’s go ahead and create a new file ./app-dependencies.gradle at the app level, which should look like:

Well, something’s still not right! What are deps and where do I find them?

So deps is a global map of dependencies that can be used by any module in our project. Declaring all modules inside a map such as deps helps us to share them in between modules and thereby maintaining just one single instance of declaring all our dependencies and making our life much easier.

So, let's create a file named dependencies.gradle at the project level like:

We would need a custom recycler view which is not in the scope of this tutorial but you can find it here. While I would encourage any beginner to dive into any code she writes, it should be fine if you just copy-paste the entire module for now.

Wait. Something's missing still! Yes, you’ve got it right, as the last step, we need to define the version of the libraries we will be using. We can create a versions.gradle file at the app level containing :

Phew! Now we have all our battle guns ready. 💪🏾

Defining our app architecture

For now, we will only create an application class and empty packages. And our project structure will look something like this

  • data will our single source of truth for all our data requirements
  • di will contain all of our dependency injection files
  • ui will contain the activities/fragments
  • util will contain all our utility classes
  • viewmodel will contain all our view models
  • MoviedApplication is the application class for all our repositories.

Now that we have an understanding of our project map, let's define the DI components first.

For that, we will define a DiModules class which will hold our Koin modules and look something like this

Next, we go to our Application class and setup Koin!

And for Android to know where your application class actually is, you need to add it to your AndroidManifest.xml like :

We have two intent-filters associated with MainActivity -

  • android.intent.action.MAIN tells Android that this is the starting activity for the app very similar to the main function in Java
  • android.intent.category.LAUNCHER tells that this app should show an Icon in any launcher app.

It’s very important to maintain contact with our command center so let's go ahead and set up the classes inside our data package.

Our data package consists of datasources that help us talk to our command center and also entities which are data classes that format the jsondata in a usable object.

The MoviesResultEntity looks like :

which, in turn, depends on the MovieEntity :

Get the data

Now that we have our data classes ready, let's make the actual network call to fetch the data. To do that we use Retrofit. It also supports Kotlin Coroutines out of the box and thus makes out life way easier!

If you want to get a deeper insight into Coroutines in Kotlin some great articles are available at :

Let’s make a service class that can serve us with the data we require. So we create a class under /data/datasource/remote named MovieService.kt :

This class defines a simpleGET method and obtains the popular movies from our command center. The method getPopularMovies returns a Response<MoviesResultEntity> which is basically our data class wrapped in Retrofit’s Response type from which we can easily determine if the network call was successful or not. The suspended keyword is a part of Kotlin Coroutines that tells Kotlin that this is function can be blocking and should be called from outside the main thread!

Next up, we place the MoviesRemoteDataSource file inside /data/datasource/remote :

So what have we done here?

We have created a class namely MoviesRemoteDataSourceImpl which implements our MoviesRemoteDataSource interface and thus defines the getPopularMovies() method.

But wait a sec! Where did the movieService object come from? Well, movieServiceis the Retrofit object which has been passed as a constructor argument so that it can be injected into the class using Dependency Injection (discussed later on in this article).

Oh! We almost forgot to add the INTERNET permission to our manifest file.

<uses-permission android:name="android.permission.INTERNET"/>

Ah! Now, we have a line of communication with our command center setup, but let’s go ahead and make our local information center (Repository) familiar to our command center.

So, we’ve defined a MovieDataRepository interface which has a suspended function named getPopularMovies() and we’ve implemented the same in our MovieDataRepositoryImpl class. Inside getPopularMovies() we make a call to our moviesRemoteDataSource to get the list of popular movies. It will either return a success Response or will return a Response with error in it. Either way, we wrap it in a ResourceResult class with appropriate success/error status and set it as the mutableLiveData result and return it from the function.

At this point, you might be wondering why the interfaces?! Because they help us, mock classes, easily and make our life a lot easier while writing tests and defining interfaces also help us keep our code clean and segregated.

You’d probably also be asking why the ResourceResult class and how does it look?

The ResourceResult class holds three states:

  • Success
  • Error
  • Loading

These states help us render our ui accordingly. And the class looks like :

Thus when we start a blocking operation, we can set the status to loading. On receiving successful data, we can set it to success else if we had encountered any error, we can set the status to error. This will help our ui react to the changes appropriately.

Our mission’s backbone: The ViewModel

So now that we have all setup required to obtain mission-critical information from the command center, let's go ahead and write the ViewModel class:

We defined our view model here but the things to look out for are :

private var moviesLiveData = MutableLiveData<ResourceResult<MoviesResultEntity>>()
val moviesResultData: LiveData<ResourceResult<MoviesResultEntity>>
get() = moviesLiveData

We have kept the mutable livedata with a private access modifier so that the data can be changed from inside the MoviesLiveData class only but the moviedData is an immutable livedata which in turn returns the moviesLiveData and can be accessed from outside but can only be observed. Makes sense?

As soon as the getPopularMovies method is called, we set the status to loading so that our ui can show loaders/wait messages accordingly

moviesLiveData.value = ResourceResult.loading()

and then,

viewModelScope.launch {
movieDataRepository.getPopularMovies().let {
moviesLiveData.value = it.value
}
}

Here we have used the viewModelScope so that our coroutine call can be canceled automatically and disposed of, when and if the scope gets out of the ViewModel's lifecycle.

Also please note that moviesDataRepository.getPopularMovies() is a suspended function, but still, we are able to call it without Android studio shouting at us for doing something wrong, is because of the launch keyword which enables us to call a coroutine from the main thread and shifts control to a separate thread, as stated in the documentation:

Launches a new coroutine without blocking the current thread and returns a reference to the coroutine as a [Job].
The coroutine is cancelled when the resulting job is [cancelled][Job.cancel].

Configure the DI

Previously, we had left the modules empty, but, now that we have our repository and our viewmodel ready, we can go ahead and configure it:

We have separated all the dependencies we need into various modules according to their functions. Here you will notice how the networkModule contains the Retrofit and the MovieService instances and they are injected into the MoviesRemoteDataSource and MovieDataRepository respectively!

The single keyword instructs Koin to maintain a Singleton copy of this object and the viewModel keyword instructs Koin that the supplied class is a ViewModel!

Let’s give MovieD a Face!

Now that we have everything other than the UI in place, let’s go ahead and configure the UI.

First off, we will have a splash screen that will be used to load the data while showing the user a cool animation!

Inside main/assets we have added a Lottie animation json file.

We have a MainActivity which inflates the SplashFragment and then moves on to the MovieShowcaseFragment once the movie list has completed loading. In MovieShowcaseFragment on clicking on a movie cards, the MovieDetailsFragment is displayed.

Inside our src/main/res folder we have the following XML files which provide us with an UI to our app:

We will not delve into the specifics of every ui related code in this article, however, we will discuss the key points one should look out for:-

First, the XML files are placed under a layout folder which corresponds to the UI required when the app is in PORTRAIT mode and the layout-land folder contains UI for the LANDSCAPE mode. Android, itself decides which orientation based layout to call at what time and we don’t need to worry about it.

Second, inside the SplashFragment we get an instance of our ViewModel by injecting it using:

private val movieViewModel: MovieViewModel by sharedViewModel()

We use sharedViewModel() so that Koin supplies us with the same instance across all our fragment

Third, we observe the ViewModel and react to its status. Let’s take the example of the SplashFragment :

// Setup observer
movieViewModel.moviesResultData.observe(viewLifecycleOwner, Observer {
when (it.status) {
Status.LOADING -> lottieSplash.playAnimation()
Status.SUCCESS -> showMovieShowcaseScreen(view)
Status.ERROR -> showMovieLoadingErrorMessageWithRetryButton()
}
})
// Make the method call
movieViewModel.getPopularMovies()

Here we observe the status of the livedata value and react to it appropriately by showing a loading animation when the status is LOADING or by showing the showMovieShowcaseScreen or if we had

Once the result is obtained, every time you call moviesViewModel.moviesData.observe we will get the data automatically instantly as it is preloaded and we are using a shared instance of the ViewModel.

The Nav Graph!

To do this, we need to create a navigation resource package and place a nav_graph.xml file inside it. Then we can use the graphical editor to setup routes between our screen.

To switch to the code editor, we need to click on Code button at the top right-hand corner:

To pass arguments, we can use an <argument> tag inside our destination fragment and pass in values along with default values well:

To add Enter/Exit Animations we can provide the anim values inside our <action> tag:

Final touches

Our MainActivity enables fullscreen mode and inflates the activity_main layout:

This is where the navigation component kicks in and calls our starting point which is the SplashFragment .

The SplashFragment inflates the fragment_splash layout and starts observing the moviesLiveData and reacts to its various states by making appropriate changes to the ui:

Initially the status is LOADING and hence the lottie animation starts playing. Once loading is complete, the livedata’s value changes to on Satus.SUCCESS and it redirects to the MovieShowcaseFragment .

The MovieShowcaseFragment :

The movies are preloaded on the splash screen and as we are using a shared instance of the viewmodel, we directly get the movies instead of waiting for any network call to happen and thus can render them directly by handling only the SUCCESS state. On clicking any Item on the MovieShowcaseFragment, the MovieDetailFragment opens which looks like :

Let’s see what our effort looks like!

Alas, Mission Accomplished!

We did not dive into every nitty-gritty detail but, you can find the entire source code at:

I hope you enjoyed this little journey of ours, so please leave some claps. 😄

This is the first part of a series. In the upcoming article, we will write tests together. So I am looking forward to it just as you are!

P.S.: This was my first article EVER! Your Claps are precious to me and will encourage me to write more articles like this one!

I feel this is the right place to acknowledge and thank Aritra Roy for inspiring me to take up Android Development and for the constant guidance. 😇

--

--

Abhriya Roy

Android developer keen on knowing things in detail and giving back to the community.