Android Skeleton Repository for the infamous “Initial Commit” using MVVM + Koin + Coroutines + Retrofit in Kotlin

Kunal Chaubal
ProAndroidDev
Published in
5 min readMay 11, 2020

--

Photo by Yancy Min on Unsplash

As developers, you must be aware of the famous “Initial commit” that is pushed as the first commit to your repository. This forms as a base skeleton to your project on which we start building the app. This is the most crucial commit in the repository since this forms a building block of the project.

Changing any kind of architecture design once the app has matured is time-consuming, complex and not recommended. Hence, modularizing the project into various components is important so that the app becomes scalable and your future-proof.

This article talks about an Android repository that can be used as a codebase for your ‘initial commit’ on git, if you are starting a new android project.

A full repository can be found on GitHub. (Adding a link at the beginning, in case you’d like to checkout the codebase while you read through this article)

In a nutshell, we have used, MVVM + Koin + Coroutines + Retrofit + Kotlin. Along with this, we are using LiveData and a single RVAdapter for every RecyclerView in the project (Check this article to understand how/why we have implemented a generic RecyclerViewAdapter). There are a number of Util classes and extension functions included in the repository that we will discuss further in this article.

Getting Started

Data flow diagram

This is how the data flow occurs between different components.

  1. View ←→ ViewModel: DataBinding is used wherever possible for bidirectional communication
  2. ViewModel ←→ Repository: Any function from the repository returns an object wrapped in ‘Resource’ object (Explained further in this article). Co-routines are used to make any network calls to the repository.
  3. Repository ←→ API: The Repository determines whether the data is fetched from an API call or from local storage so that the ViewModel is unaware of the data source.

To summarize, once the View triggers an API call, the VM makes a request using CoroutinesManager to the repository. The repository fetches data and returns to the VM. The VM then updates the View with necessary information

  • The repository contains Util classes, Activity/Fragment extensions, and some helper classes under the ‘extensions’ and ‘utils’ directory.
  • All the UI files are under the package ‘ui’ and it’s respective data classes under ‘data’.
  • The generic RecyclerView adapter and all the files associated with it are under the ‘adapter’ directory.
  • Depending upon the purpose of the app, account managers or booking managers can go under ‘manager’ directory.
  • ‘base’ directory contains base classes like BaseActivity/ BaseFragment.

Under ‘livedata’ directory, you will find SingleLiveEvent and AbsentLiveData classes.

SingleLiveEvent is a lifecycle-aware observable that sends only new updates after subscription, used for events like navigation and Snackbar messages. This avoids a common problem with events: on configuration change (like rotation) an update can be emitted if the observer is active. This LiveData only calls the observable if there’s an explicit call to setValue() or call().

AbsentLiveData is a LiveData class that has a ‘null’ value. This can be used as a fallback mechanism if any function fails to return a valid LiveData object.

Notice that apart from BaseActivity and BaseFragment, we have DataBindingActivity and DataBindingFragment.

In DataBindingActivity, we keep an instance of ViewDataBinding and override the setContentView function by inflating the layout that is returned in layoutId(). layoutId() is an abstract function declared in BaseActivity.

A similar behavior can be seen in DataBindingFragment and BaseFragment

This repository also contains an ‘example’ directory which should give you an example of how,

  • Dependencies are injected in a fragment using Koin.
  • Coroutines manager is used to make asynchronous API calls.
  • SingleLiveEvent is used between ViewModel and Fragment.
  • Databinding is implemented using ObservableField to update UI.

Notice that we have a module.kt file under most of the packages under ‘ui’ and ‘data’. This file is used by Koin for dependency injection

modules.kt for FetchDetailsViewModel

Check out this article if you’d like to know how Koin is implemented.

ViewModel

FetchDetailsViewModel.kt

Here, we have added a few dependencies while initializing FetchDetailsViewModel.
‘updateEvent’ is a SingleLiveEvent that is observed in FetchDetailsFragment and is used for updating the UI when all API calls are made.
‘textObservable’ is an ObservableField that is observed in our XML file

android:text="@{vm.textObservable}"

When fetchDetails() function is called, it returns a Response object wrapped in ‘Resource’. This class helps in identifying the state of the response object so that we can update the UI accordingly.

This is how the Resource class looks like:

The ‘Status’ enum helps in identifying the state of the returned object. In case of error or cancel, an AppException is also returned which specifies the error details.

Repository

The business logic to decide whether to access Data from local storage or make an API call is implemented in the Repository class. We have used an extension function ‘handleException’ to check if any exceptions are thrown. This function helps to limit the try/catch block in one place rather than duplicating it in every repository.

suspend fun <T> handleException(apiCall: suspend () -> T): Resource<T> {
return try {
Resource.success(apiCall.invoke())
} catch (throwable: Throwable) {
Resource.error(AppException(throwable))
}
}

If you are wondering where Retrofit is initialized, it is in data/remote/module.kt file and it looks something like this:

Modularization is done wherever possible in this architecture so that replacing/updating any components without affecting other components should be comparatively convenient.

If you have any suggestions/alternatives regarding this architecture design, I’d be happy to discuss :)

Feel free to fork it/contribute to it. Happy Coding!

--

--