Disclaimer: This post is quite old and better API's are out there now. Please read up on Android architecture view models.

This post was originally written for Mindorks publication on Medium. In this post, we will focus on the Loader API. If you do not know about MVP pattern, I’ll suggest you to read the Part 1 before this.

Example project for this post is at this link. The master branch explores both MVP and Loader API while the other only-mvp branch only explores MVP as explained in Part 1.

Loader API and the rotation problem

The rotation problem is that we fetched some data by a costly operation like a network call or a complex query with transformation from a database. Then the user rotated the phone and we don’t want to do the costly query again. Here comes in the Loader API. Loader API was introduced in API level 11 and is used to load data from a data source to display in an activity or fragment. It has many advantages and solves many problems but the most useful one to us regarding rotation is that Loaders are managed by the system and they persist during configuration changes. According to the documentation of Loaders:

Loaders persist and cache results across configuration changes to prevent duplicate queries.

So if we make the loader load presenter in the first creation of our activity, and then rotate the phone which will recreate the whole activity, we will not make a duplicate query and get back the same presenter as pre-rotation. And then using the cached data in the presenter we can persist our state or instance of data across rotation. Now lets dive into the code.

Loader API has three major classes and interfaces:

  1. LoaderManager:
    Responsible for managing loaders for an activity or fragment. The system automatically determines whether a loader with the same integer ID already exists and will either create a new loader or reuse an existing loader. So in case of rotation, it reuses the same loader giving us our pre-rotation presenter object back.

  2. LoaderCallbacks:
    Interface required to be implemented by the activity or fragment to get the callback from when loader events occur. Although there are three functions, we mostly need to focus on two for our rotation case.

    onCreateLoader(int, Bundle): Callback for when the system needs a new Loader and thus is asking for us to create one.

    onLoadFinished(Loader, D): Callback for when the load has been finished by the loader and we get back the data as ‘D’.

    onLoaderReset: Callback for when a previously created loader is being reset. We won’t need to focus on this though for our rotation use case.

  3. Loader:
    This is the abstract class which serves as the basis for all loader classes.

Implementation of loader API

Lets start with defining our custom loader class. So we need to extend the abstract Loader class. We will have a private member, our presenter we want to load, and will override one function: onStartLoading(). It is normally called as soon as our activity or fragment gets started. As our result doesn’t take time to load and is created when the loader was being created, we will deliver it as soon as this function is called.

class MainLoader extends Loader<MainPresenter> {
    private MainPresenter presenter;

    /*  When the loader is being created, we instantiate the
        presenter also and keep it's object as private */
    MainLoader(Context context, MainPresenter presenter) {
        super(context);
        this.presenter = presenter;
    }

    /* This function is called when activity is being started.
       Hence we deliver the result, our presenter, here. If the
       activity is destroyed in rotation, the loader isn't created
       again as system made sure it survived the rotation, and   
       thus when starting of activity calls this function again 
       automatically and we deliver the pre-rotation presenter as 
       our result to the activity in onLoadFinished callback*/
    @Override
    protected void onStartLoading() {
        deliverResult(presenter);
    }
}

In our activity, we will start the initialization of the Loader in onCreate(). If this is the first time activity is being created a new loader will be created by the system by calling onCreateLoader() callback.

public class MainActivity extends AppCompatActivity implements MainViewInterface,
  LoaderManager.LoaderCallbacks<MainPresenter> {

  @Override
  protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);
      // start the loader to load the presenter

      /* 1001 is a unique loader id through which system manages
         a single instance of loader for an activity, the null
         parameter is the bundle we may wish to pass, and this 
         indicated that the loader callback are implemented by this 
         activity. */
         getSupportLoaderManager().initLoader(1001, null, this);
}

And the loader callbacks are implemented in an activity or fragment as shown below:

/*Activity.java
 - onCreate and other lifecycle stuff */

/*
* Loader API callbacks
* */

/* Here we are creating a new instance of our loader and sending it 
the presenter which it will store for future. */
@Override
public Loader<MainPresenter> onCreateLoader(int id, Bundle args) {
    return new MainLoader(this, new MainPresenter(new DataManager(this)));
}

/*  The deliverResult function from Loader delivers the result 
    back to us here, as you can see we set our local variable in 
    activity equal to the presenter that the loaders loads for 
    us. As initLoader() was called in onCreate(), this load 
    finishes before onStart() of activity, and thus we can start 
    using the presenter from onStart(). Also if rotation happens 
    onCreateLoader() is never called and the load from the 
    previously created loader is given back to us, i.e. the 
    pre-rotation presenter. */
@Override
public void onLoadFinished(Loader<MainPresenter> loader, MainPresenter presenter) {
    this.presenter = presenter;
}

@Override
public void onLoaderReset(Loader<MainPresenter> loader) {

}

In this way we can persist our presenter during rotation, in turn persisting our data and any kind of database or network request we were making.

Please note that there are many alternatives to Loader API to persist presenters and many other uses of Loader API. The documentation on Loader API is pretty good.

Source code

  1. Part 2: Loader API with MVP

Further reading

  1. Making loading data lifecycle aware by Ian Lake
  2. GrokkingAndroid - How to use Loaders in Android