Categories
Android Apps Architecture

Android App Architecture, Part 1

Not being familiar with modern Android App architecture and coming from the ViewController world, it is confusing jumping into the recommended MVVM architecture. There are also some parts of the official guide that are left for the reader to figure out, so I will write about my experience with implementing it in my repo browser project.

Choosing dependencies

Besides native Architecture dependencies, there are some parts of the setup that are not defined in the guide. Two of the main ones are the networking and dependency injection libraries.

Networking

The guide uses Retrofit for networking. For me, it made sense to use Volley. I like that I’m in control of the requests that I write, and am not dependent on the Retrofit abstraction of mapping requests to data objects. I can be sure that if there will be customisation required for my requests, Volley can handle it.

Dependency injection

There are many DI libraries, with the most popular one being Dagger. For me, it seemed complicated to get started with and with a lot of boilerplate. I didn’t see any drawbacks from using Koin, so I went for that one instead.

Here are the final dependencies for my project.

Koin setup

After planning and creating the required Activity, ViewModel, Networking, Database and Repository objects, it was time to set up the Koin modules. For my use case, I created singletons and viewModels:

// repository Database
single { get<AppDatabase>().repoDao() }
// repository networking
single { RepoClient(context, get()) }
// repository repository to merge data from database and network
single { RepoRepository(get(), get(), get()) }
// the viewModel
viewModel { (handle: SavedStateHandle) -> 
    RepoListViewModel(handle, get(), get()) } 

Notice the viewModel with SavedStateHandle argument. This allows access to the saved state and arguments of the associated Activity.

Other singletons required were: Thread executors, Room database, SharedPreferences

Here is the Koin setup.

Repository setup

For fetching the repositories from Github and storing/reading them from the database, 3 components were required:

  • Network client
  • Database object
  • MediatorLiveData to merge network/db data

After that, accessing the data in LiveData format is straightforward:

val repoResource = object : NetworkBoundResource<List<Repo>, List<Repo>>(executor) {
    override fun saveCallResult(item: List<Repo>) = repoDao.save(item)

    override fun shouldFetch(data: List<Repo>?): Boolean {
        return data == null || data.isEmpty() || rateLimit.shouldFetch("repos")
    }

    override fun loadFromDb() = repoDao.getRepos()

    override fun createCall() = repoClient.getRepos()

    override fun onFetchFailed() = rateLimit.reset("repos")

}.asLiveData()

Check out the repository source code.

Populating the view

For the last part, the view is updated according to the LiveData<List<Repo>> result. If there is data, the ListView is populated with a DataBinding adapter:

viewModel.repos.observe(viewLifecycleOwner) {
    // update UI
    it.status.showWhenLoading(listProgressBar)

    when (it.status) {
        Status.SUCCESS -> {
            adapter.submitList(it.data)
        }
        Status.ERROR -> {
         Toast.makeText(this@RepoListFragment.activity,
             it.message, 
             Toast.LENGTH_LONG).show()
        }
    }
}

Conclusion

These are the main steps of creating an Android MVVM skeleton app. Of course there are more details(networking, database), which can be discovered in the GitHub repository.

Stay tuned for Part 2, where I will write about testing the app with Mockk.

Reference

Project code in GitHub.