mvp_with_dagger
mvp_with_dagger copied to clipboard
How presenters survive Activity recreations on configuration changes with Dagger2
Presenters that survive Activity
configuration changes using Dagger2
Recently I was looking for an example how to make my Presenters
survive an Activity
recreation when a configuration change occurs. All examples that I found were using Fragments
, looked way too complicated or didn't use Dagger
. So I decided come up with my own solution and setup this project, so it may help other people.
The problem
My project structure was built in a way, that a Presenter
was coupled to its view lifecycle. I did directly use Activities
, so the Presenter
was destroyed once the Activity
was destroyed. That was fine for my project. Then I got to a point where I needed to run a long network operation.
On a button click the Presenter
subscribed to an Observable
that would perform the network operation. The Presenter
updated the View
with the progress. So far it worked. But on a screen rotation the Activity
was recreated and therefore the Presenter
, too. The new instance knew nothing about the progress.
So I wanted to change my Presenters
, so they survive the Activity
recreation, keep subscribed to the Observable
, that performs the network task, and update the View
once it is back.
The idea
Google officially suggests to use Fragment.setRetainInstance(true)
, but I felt like adding a Fragment
only for this purpose was too much. I also read about keeping Presenters
in a static Map
, but couldn't find an example, also nothing that showcases this with Dagger2
. My Presenters
currently were injected anew every time an Activity
was created, with this Map
this should only happen when an Activity
is really created for the first time, not on a recreation.
Note: The following section requires you to be familar with
Dagger2
andMVP
The solution
First I needed to create some cache, that can be injected everywhere I needed it. I created a new class for that:
public final class PresenterCache {
private final Map<String, BasePresenter> cachedPresenters = new ArrayMap<>();
(...)
}
As a key I decided to simply use class names, you can use whatever you want.
I added this to my ApplicationModule
, so I could get the singleton PresenterCache
everywhere where it's needed
@Provides
@Singleton
PresenterCache providePresenterCache() {
return new PresenterCache();
}
Now I could inject the PresenterCache
in my BaseActivity
class. I added the following method, which is called in onCreate()
and checks if there is a cached presenter. If not, it creates a new one using Dagger
.
private void restoreOrCreatePresenter() {
// try to get a cached presenter
presenter = presenterCache.getPresenter(getClass().getName());
if (presenter == null) {
// no cached one found, create a new one
presenter = presenterComponent.getPresenter();
presenterCache.putPresenter(getClass().getName(), presenter);
}
presenter.bindView(this);
}
So what's that presenterComponent
that creates a new presenter for me? It's simply an Interface
that every Dagger Component
extends, that needs to provide a Presenter
:
public interface PresenterComponent<T extends BasePresenter> {
T getPresenter();
}
When Dagger
builds the Components
it will include a method getPresenter()
now which returns you a new instance of type T
.
So far, if an
Activity
gets recreated, it will use the cachedPresenter
and simply bind itself to this one, allowing thePresenter
to update the view accordingly and, more important, continue e.g. network operations.
Bad side: Currently the Presenter
will also be cached if the Activity
is closed normally, e.g. by a back press.
So let's modify BaseActivity.onStop()
:
@Override
protected void onStop() {
if (!isChangingConfigurations()) {
// activity is stopped normally, remove the cached presenter so it's not cached
// even if activity gets killed
presenterCache.removePresenter(presenter);
}
// onStop will clear view reference
presenter.onStop(isChangingConfigurations());
}
From the documentation of isChangingConfigurations()
Check to see whether this activity is in the process of being destroyed in order to be recreated with a new configuration.
Awesome, so if we know our Activity
is being stopped normally we can remove it from PresenterCache
. Additionally we clear the view reference in the Presenter
. We do this in onStop()
as this is the last method Android guarantees us to be called. Afterwards the Activity
may be killed at any given time.
There is one last modification needed. If the activity is simply stopped, e.g. because we launched a new Activity
that made our Activity
invisible, and now restarts, because that Activity
has been closed, we want to put the presenter back to the cache and bind ourself to it:
@Override
protected void onRestart() {
super.onRestart();
// put presenter back to cache and re-bind view
presenterCache.putPresenter(getClass().getName(), presenter);
presenter.bindView(this);
presenter.onRestart();
}
Check out the sample for a full example.