Android app with MVVM + Room + Paging + Koin + Coroutine + Live Data + Retrofit
Tags: android, mvvm, room, retrofit
Published: 2020-11-05 13:35:20

---
title: Android app with MVVM + Room + Paging + Koin + Coroutine + Live Data + Retrofit
published: true
description: Develop Android app with ViewModel + Room + Paging + Koin + Coroutine in Kotlin
tags: android, mvvm, room, retrofit
cover_image: https://www.mindinventory.com/blog/wp-content/uploads/2018/05/android-jetpack.jpg
---

Earlier, we didn't have architecture components officially from Android. A few years ago, Android introduced a set of components to develop Android applications with proper architecture under "Android Jetpack".

**Jetpack** is a suite of libraries to help developers follow best practices, reduce boilerplate code, and write code that works consistently across Android versions and devices so that developers can focus on the code they care about.

## Components Used
In this article, we will develop a small Android application to demonstrate jetpack as well as a few more components which are as follows:
- **ViewModel**- A jetpack component to architect app with MVVM. Here we will use data binding as well.
- **Room Database**- It's a wrapper over the SQLite database to give developers an ORM-like feel.
- **Paging Library** - The Paging Library helps you load and display a small amount of data at a time. Loading partial data on-demand reduces the usage of network bandwidth and system resources.
- **Coroutine**- Coroutine is a framework in Kotlin to make asynchronous calls in a more readable fashion.
- **Koin**- It's a dependency injection library that is very easy to use as compare to dagger or hilt.
- **Live Data**- It's a life cycle aware observable data holder.
- **Retrofit**- It's the most famous web service calling library which we will use in the app to fetch data from web API.

## Repositories
I have developed the following apps to demonstrate these components-
- [Photo Search](https://github.com/mohitrajput987/photo-search)
- [GitHub Issues](https://github.com/mohitrajput987/github-issues) 

Feel free to fork these repositories, add new features, and raise pull requests.

## Coding Starts
Without too much description, I will add code snippets from [GitHub Issues](https://github.com/mohitrajput987/github-issues) project here which are self-explanatory. For any query, you can use the comment section.

Here is the contract for github pull request list feature:
```kotlin
interface IssuesContract {
    interface ViewModel {
        fun getIssues(): LiveData<PagedList<IssuesModels.Issue>>
        fun getProgressStatus(): LiveData<ProgressStatus>
        fun searchIssues(githubOwner: String, repoName: String, state: String)
    }

    interface Repository {
        suspend fun searchIssues(githubOwner: String, repoName: String, state: String, pageNumber: Int): DataResult<List<IssuesModels.Issue>>
    }
}
```

Here is the implementation of our data layer:
```kotlin
class IssuesRepository(private val gitHubApiService: GitHubApiService, private val issuesDao: IssuesDao) : IssuesContract.Repository {
    companion object {
        const val PAGE_SIZE = 20
    }

    override suspend fun searchIssues(githubOwner: String, repoName: String, state: String, pageNumber: Int): DataResult<List<IssuesModels.Issue>> {
        val storedIssues = issuesDao.getIssues(githubOwner, repoName, state, ((pageNumber - 1) * PAGE_SIZE), PAGE_SIZE)
        if (storedIssues.isNotEmpty()) {
            val refreshInterval = 30 * 60 * 1000
            val oldestIssue = storedIssues.minBy { it.storedAt }
            if (System.currentTimeMillis() - oldestIssue!!.storedAt < refreshInterval) {
                return DataResult.DataSuccess(IssuesResponseToIssuesMapper().mapFromDatabase(storedIssues))
            } else {
                issuesDao.deleteIssues(githubOwner, repoName, state)
            }
        }

        val response = gitHubApiService.fetchIssues(githubOwner, repoName, state, pageNumber, PAGE_SIZE)
        return if (response.isSuccessful) {
            val issuesResponse = response.body()!!
            val issues = IssuesResponseToIssuesMapper().map(issuesResponse)
            issuesDao.insertAll(issues.map { IssuesModels.IssueEntity(it.id, it.patchUrl, it.title, it.number, it.userName, it.state, githubOwner, repoName, System.currentTimeMillis()) })
            DataResult.DataSuccess(issues)
        } else {
            DataResult.DataError(ErrorHandler.getError(response))
        }
    }
}
```

Here is the implementation of ViewModel:

```kotlin
class IssuesViewModel(private val repository: IssuesContract.Repository) : ViewModel(), IssuesContract.ViewModel {
    private val issuesDataSourceFactory: IssuesDataSourceFactory = IssuesDataSourceFactory(repository, viewModelScope)
    private val progressLoadStatus: LiveData<ProgressStatus>
    private val issues: LiveData<PagedList<IssuesModels.Issue>>

    init {
        val pagedListConfig = PagedList.Config.Builder()
                .setEnablePlaceholders(true)
                .setInitialLoadSizeHint(20)
                .setPageSize(20)
                .build()

        issues = LivePagedListBuilder<Int, IssuesModels.Issue>(issuesDataSourceFactory, pagedListConfig)
                .build()

        progressLoadStatus = Transformations.switchMap(issuesDataSourceFactory.liveData, IssuesDataSource::getProgressLiveStatus)
    }

    override fun getIssues() = issues

    override fun getProgressStatus() = progressLoadStatus

    override fun searchIssues(githubOwner: String, repoName: String, state: String) {
        issuesDataSourceFactory.githubOwner = githubOwner
        issuesDataSourceFactory.repoName = repoName
        issuesDataSourceFactory.state = state
        issues.value?.dataSource?.invalidate()
    }
}
```
Here you can see, we have used live data of `PagedList`. We have initialized paging related instanced i.e. paging config, data source etc. Now we will create data source factory:

```kotlin
class IssuesDataSourceFactory(private val repository: IssuesContract.Repository, private val scope: CoroutineScope) : DataSource.Factory<Int, IssuesModels.Issue>() {
    var githubOwner: String = ""
    var repoName: String = ""
    var state: String = ""

    val liveData = MutableLiveData<IssuesDataSource>()
    lateinit var issuesDataSource: IssuesDataSource

    override fun create(): DataSource<Int, IssuesModels.Issue> {
        issuesDataSource = IssuesDataSource(repository, scope, githubOwner, repoName, state)
        liveData.postValue(issuesDataSource)
        return issuesDataSource
    }
}
```

and here is the implementation of data source:
```kotlin
class IssuesDataSource(private val repository: IssuesContract.Repository, private val scope: CoroutineScope, private val githubOwner: String, private val repoName: String, private val state: String) : PageKeyedDataSource<Int, IssuesModels.Issue>() {
    private val progressLiveStatus = MutableLiveData<ProgressStatus>()

    fun getProgressLiveStatus() = progressLiveStatus

    override fun loadInitial(params: LoadInitialParams<Int>, callback: LoadInitialCallback<Int, IssuesModels.Issue>) {
        scope.launch {
            progressLiveStatus.postValue(ProgressStatus.Loading)
            when (val dataResult = repository.searchIssues(githubOwner, repoName, state, 1)) {
                is DataResult.DataSuccess -> {
                    progressLiveStatus.postValue(ProgressStatus.Success)
                    callback.onResult(dataResult.data, null, 2)
                }
                is DataResult.DataError -> progressLiveStatus.postValue(ProgressStatus.Error(dataResult.errorMessage))
            }
        }
    }

    override fun loadAfter(params: LoadParams<Int>, callback: LoadCallback<Int, IssuesModels.Issue>) {
        scope.launch {
            progressLiveStatus.postValue(ProgressStatus.Loading)
            when (val dataResult = repository.searchIssues(githubOwner, repoName, state, params.key)) {
                is DataResult.DataSuccess -> {
                    progressLiveStatus.postValue(ProgressStatus.Success)
                    callback.onResult(dataResult.data, params.key + 1)
                }
                is DataResult.DataError -> progressLiveStatus.postValue(ProgressStatus.Error(dataResult.errorMessage))
            }
        }
    }

    override fun loadBefore(params: LoadParams<Int>, callback: LoadCallback<Int, IssuesModels.Issue>) {

    }
}
```

Now it's time to create adapter for `RecyclerView` according to paging library:
```kotlin
class IssueAdapter : PagedListAdapter<IssuesModels.Issue, IssueAdapter.IssueViewHolder>(DiffUtilCallBack()) {

    inner class IssueViewHolder(private val binding: ItemIssueBinding) : RecyclerView.ViewHolder(binding.root) {

        fun bind(issue: IssuesModels.Issue) {
            binding.issue = issue
            binding.executePendingBindings()
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): IssueViewHolder {
        val itemBinding: ItemIssueBinding = ItemIssueBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        return IssueViewHolder(itemBinding)
    }

    override fun onBindViewHolder(holder: IssueViewHolder, position: Int) {
        holder.bind(getItem(position)!!)
    }
}

class DiffUtilCallBack : DiffUtil.ItemCallback<IssuesModels.Issue>() {
    override fun areItemsTheSame(oldItem: IssuesModels.Issue, newItem: IssuesModels.Issue) = oldItem.id == newItem.id

    override fun areContentsTheSame(oldItem: IssuesModels.Issue, newItem: IssuesModels.Issue): Boolean {
        return oldItem.id == newItem.id
                && oldItem.title == newItem.title
                && oldItem.number == newItem.number
    }
}
```

In the source code of GitHub Issues, `IssuesFragment` has complete code for initializing the `ViewModel`. Here is a snippet how can we observe data and set it to adapter:

```kotlin
private fun setupViewModel() {
        issuesViewModel.getIssues().observe(this, Observer {
            (rvIssues.adapter as IssueAdapter).submitList(it)
        })

        issuesViewModel.getProgressStatus().observe(this, Observer {
            when (it) {
                is ProgressStatus.Loading -> displayLoading()
                is ProgressStatus.Success -> hideLoading()
                is ProgressStatus.Error -> displayError(it.errorMessage)
            }
        })
        issuesViewModel.searchIssues(githubOwner, repoName, issueState)
    }
```

In the `GitHubApp` application class, you can see the setup of Koin and its global modules
```kotlin
class GitHubApp : Application() {
    override fun onCreate() {
        super.onCreate()
        initKoin()
    }

    private fun initKoin() {
        startKoin {
            androidLogger(Level.DEBUG)
            androidContext(this@GitHubApp)
            androidFileProperties()
            modules(provideModules())
        }
    }

    private fun provideModules() = listOf(retrofitModule, apiModule, databaseModule)
}
```

To see the complete working source code, fork
- [GitHub Issues](https://github.com/mohitrajput987/github-issues) or
- [Photo Search](https://github.com/mohitrajput987/photo-search)
repository and run on the latest Android Studio. 

If you have any doubt or query or need a detailed explanation of any component, drop in the comment box.