Presenter surviving orientation changes with Loaders

MVP diagram

The issue with Presenters and configuration changes

Most of the developers agree on the core concepts and how to apply them on Android, but there are still some open questions. The one that we are going to talk today is what to do with the Presenter object when a configuration changes occurs. There are a few approaches:

Let the Presenter be destroyed.

In this approach the Presenter will die when the Activity/Fragment dies. If we wanna restore some state we would need to make use of onSaveInstanceState, via being the View the one that saves this data and then provide it to the Presenter upon creation, or giving the Presenter a chance to store some data by himself passing the save instance Bundle. While this approach can work with some simple data, it will get more complicated if what we want to keep is the reference to some ongoing background operation.

Keep the Presenter somewhere and restore once the component is recreated.

This idea is the one that I have seen the most. However, implementations can differ quite a bit between each other.

A new approach

As we can see, all the approaches have some caveats or corner cases that make them tricky to get right. After give some more thought and being inspired by this nice blog post about Loaders: Making loading data lifecycle aware, I would like to introduce a new approach: use Loaders to preserve Presenters during rotation.

But first… what are Loaders and what they give to us?

Thats great, but… what does it have to do with Presenters?

Well, as we already mention, the problem with Presenters is where to store them and when to decide that their time is over. On the other hand, we just saw a few capabilities about Loaders: they are a tool provided by the framework that is lifecycle aware, the system will take care of cleaning them up when their time has come to an end and last but not least, they don’t necessary need to run in background.

use a synchronous Loader as a cache to store our Presenter”

The catch here is that by using the Loader synchronously we can be sure at which moment during the lifecycle* the Presenter will be initialized and ready to interact. All before the Activity/Fragment is visible to the user.

Ok ok, that sounds nice, so how we translate that to code?

First we should create our Loader implementation extending directly the Loader class. Take into account that we wont subclass CursorLoader or AsyncTaskLoader since we do not want the background capabilities that they provide. The implementation could looks as follows:

public class PresenterLoader<T extends Presenter> extends Loader<T>{

private final PresenterFactory<T> factory;
private T presenter;

// Constructor...

@Override
protected void onStartLoading() {

// If we already own an instance, simply deliver it.
if (presenter != null) {
deliverResult(presenter);
return;
}

// Otherwise, force a load
forceLoad();
}

@Override
protected void onForceLoad() {
// Create the Presenter using the Factory
presenter = factory.create();

// Deliver the result
deliverResult(presenter);
}

@Override
protected void onReset() {
presenter.onDestroyed();
presenter = null;
}
}
public interface Presenter<V>{
void onViewAttached(V view);
void onViewDetached();
void onDestroyed();
}
  • onStartLoading(): Will be called by the Framework for a new or already created Loader once Activity onStart() is reached. In here we check whether we hold a Presenter instance (— in which situation it will be delivered immediately) or the Presenter needs to be created.
  • onForceLoad(): Called when forceLoad() is invoked. Here we are calling the Factory to create our Presenter and delivering the result.
  • deliverResult(): will deliver our Presenter to the Activity/Fragment.
  • onReset(): will be call before the Loader gets destroyed, giving us the chance to communicate this to the Presenter in case some ongoing operation could be cancelled or additional clean ups would be required.
  • PresenterFactory: this interface will hide the details about how to create a Presenter when is required. We use this approach to favour composition over inheritance so we avoid the need of subclassing this PresenterLoader class to provide different Presenters. This interface could look as simple as:
public interface PresenterFactory<T extends Presenter> {
T create();
}

So how would it look from the Activity/Fragment point of view?

Ok, so now that we have our Loader implementation in place, we need to connects our activities and fragments. The connection point will be the LoaderManager. We will call FragmentActivity’s getSupportLoaderManager() or Fragment’s getLoaderManager() to get our instance and then call initLoader(). Google recommends calling this method in Activity#onCreate() or Fragment#onActivityCreated().

But how do I get my Presenter? LoaderCallbacks

LoaderCallbacks is the communication point between Activity/Fragment and the Loader. There are three callbacks:

  • onCreateLoader() — where you construct the actual Loader instance.
  • onLoadFinished() — where Loader will deliver its work, the Presenter in our case.
  • onLoaderReset() — your chance to clean up any references to the data.

Show me the code

public class SampleActivity extends AppCompatActivity implements SomeView, LoaderManager.LoaderCallbacks<Presenter> {

private static final int LOADER_ID = 101;
private Presenter presenter;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

// initialization code...

getSupportLoaderManager().initLoader(LOADER_ID, null, this);
}

@Override
protected void onStart() {
super.onStart();

// Ready to use presenter
presenter.onViewAttached(this);
}

@Override
protected void onStop() {
presenter.onViewDetached();
super.onStop();
}

// Some other activity code and view interface implementation...


@Override
public Loader<Presenter> onCreateLoader(int id, Bundle arg){
return new PresenterLoader<>(this, new SomeFactoryImpl());
}

@Override
public void onLoadFinished(Loader<Presenter> loader, Presenter presenter) {
this.presenter = presenter;
}

@Override
public void onLoaderReset(Loader<Presenter> loader) {
presenter = null;
}
}
  • Init Loader with a unique ID within this instance of Activity/Fragment.
getSupportLoaderManager().initLoader(LOADER_ID, null, this);
  • Calling super.onStart will create or reconnect to an existing Loader and onStartLoading will be called. In our case this will create or deliver the Presenter instance in onLoadFinished. All of this will happen on the main UI thread, so by the time super.onStart is finished our Presenter will be ready to be used.
  • If the Loader needs to be created, the system will call onCreateLoader, where we will create our instance passing the proper PresenterFactory implementation.
    @Override
public Loader<Presenter> onCreateLoader(int id, Bundle arg){
return new PresenterLoader<>(this, new SomeFactoryImpl());
}

Et voila!

So summarizing a bit, we have seen that Loaders are a tool provided by the Android framework that offers the following:

  • They will be kept during configuration changes.
  • They are cleaned up by the system when the Activity/Fragment is no longer needed.
  • They are tied to the Activity/Fragment lifecycle, so this events could be propagated.
  • Each Activity/Fragment will have attached their own Loader instances, so several combination of “Presenter-Activity/Fragment” can be living at the same time, as in a ViewPager.
  • Loaders can run synchronously, making deterministic when its content will be deliver and ready to use.

--

--

Building things @Facebook

Love podcasts or audiobooks? Learn on the go with our new app.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store