An approach to MVP/MVI using RxJava 2 & Dagger 2 (Part 1 of 2 — ViewModel is here)

Frederico Sabino
4 min readMay 27, 2017

--

Throughout the years that I’ve been developing and reviewing Android applications, most of them had problems dealing with configuration changes: the application makes a network request/computation task and if the device rotates it’s all gone and we need to restart those tasks.

Pre-ViewModel solutions

While some applications simply don’t allow configuration changes, others try to solve it in different ways:

  • Singleton pattern/Component living on a higher scope which keeps track of these events
  • Fragment.setRetainInstance(boolean)
  • Using Loaders

These were the most common patterns that I’ve seen to solve this problem. One that worked really well for me was using Loaders. They survive configuration changes so any data that they hold also survives. This might include Presenters/Controllers/anything really. To know more how you can implement a component that survives configuration changes I recommend reading this great article by Ian Lake.

So we already have the problem solved why bother? Well there are some keypoints that you need to be aware before starting using Loaders:

  1. They involve quite some boilerplate to set them up. Well maybe not that much but we need to consider the lifecycle of the Loader itself (on top of the Activity/Fragment lifecycle)
class PresenterLoader<T : BasePresenter<*>>(context: Context, private val creator: () -> T) : Loader<T>(context) {
private var presenter: T? = null

override fun onStartLoading() {
presenter?.let { deliverResult(presenter); return }
forceLoad()
}

override fun onForceLoad() {
presenter = creator()
deliverResult(presenter)
}

override fun onReset() {
presenter?.onDestroyed()
presenter = null
}
}

2. They are attached to the Activity/Fragment lifecycle but in a restrictive way. As Ian Lake mentions in the article:

In almost every case, there’s only one method you’ll need to call: initLoader(). This is generally called in onCreate() or onActivityCreated()

Given this, Loaders are a great option to store components that survive configuration changes but they also come with the drawbacks of dealing with an additional lifecycle and the knowledge on how it attaches to the current Activity/Fragment/View lifecycle.

ViewModel — a solution using Dagger 2

Google announced at IO 2017 a new set of libraries: Android Architecture Components. The main goal of these components is to help design a better application.

One of those components is the ViewModel.

The ViewModel class is designed to store and manage UI-related data so that the data survives configuration changes such as screen rotations.

This image from the ViewModel webpage shows that the ViewModel scope survives configuration changes which is what we wanted to achieve with the Loaders.

Moreover, it is really simple to use! You just need to extend ViewModel on the component that you want to persist. Then you can obtain that instance in your Activity/Fragment:

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
presenter = ViewModelProviders.of(this).get(MyPresenter::class.java)
}

Now you might have noticed that we are not directly using the constructor to get MyPresenter . What about Dependency Injection? There are two ways to accomplish this with Dagger 2 and ViewModels:

  1. Instead of extending ViewModel you can extend AndroidViewModel(Application) instead and then you can have an initialization block to inject the dependencies:
class MyPresenter(application: Application) : AndroidViewModel(application){
@Inject lateinit var repository: MyRepository
init {
(application as Application).component.inject(this)
}
}

2. Create a ViewModelProvider.Factory which can be injected:

class MyPresenter(repository: MyRepository) : ViewModel() { ... }@ForView //Dagger 2 Scope
class Factory @Inject constructor(val repository: MyRepository) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>?): T {
@Suppress("UNCHECKED_CAST")
return MyPresenter(repository) as T //ugly I know
}
}

and in the Activity I can inject this Factory and call

@Inject lateinit var factory: Factoryoverride fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
inject()
presenter = ViewModelProviders.of(this,factory).get(MyPresenter::class.java)
}

The first solution is simpler and you don’t have to create a Factory ! The problem here is that we are now requesting the dependencies in the init block instead of having them injected in the constructor. You will also need to mock the Application class if you want to test this component which is not ideal.

That’s why I prefer the second one. Yes… I have to create a Factory for it and you have the ugly cast but it’s easier to see the dependencies which are being injected and, when writing tests, I can mock my own class.

And that’s it. Beware that the ViewModel was just released and at the time of writing it is currently on version 1.0.0-alpha1. In Part 2 I’m going to explain my current approach to MVP/MVI while using ViewModels to survive configuration changes.

--

--