Google recommends us to call getLoaderManager().initLoader(0, null, this);
within Fragment's onActivityCreated
http://developer.android.com/reference/android/content/AsyncTaskLoader.html
However, that yields the following problem : onLoadFinished will be called twice during configuration changes (Rotation)
We can simulate the problem as follow.
Code
package org.yccheok.gui;
import android.content.Context;
import android.os.Bundle;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.AsyncTaskLoader;
import android.support.v4.content.Loader;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.actionbarsherlock.app.SherlockFragment;
public class HomeMenuFragment extends SherlockFragment implements LoaderManager.LoaderCallbacks<HomeMenuFragment.Infos> {
private static class InfosLoader extends AsyncTaskLoader<Infos> {
private Infos infos = null;
public InfosLoader(Context context) {
super(context);
}
@Override
public Infos loadInBackground() {
Log.i(TAG, "loadInBackground");
this.infos = Infos.newInstance();
return infos;
}
/**
* Handles a request to cancel a load.
*/
@Override
public void onCanceled(Infos infos) {
super.onCanceled(infos);
}
/**
* Handles a request to stop the Loader.
* Automatically called by LoaderManager via stopLoading.
*/
@Override
protected void onStopLoading() {
// Attempt to cancel the current load task if possible.
cancelLoad();
}
/**
* Handles a request to start the Loader.
* Automatically called by LoaderManager via startLoading.
*/
@Override
protected void onStartLoading() {
if (this.infos != null) {
Log.i(TAG, "deliverResult");
deliverResult(this.infos);
}
if (takeContentChanged() || this.infos == null) {
Log.i(TAG, "forceLoad");
forceLoad();
}
}
/**
* Handles a request to completely reset the Loader.
* Automatically called by LoaderManager via reset.
*/
@Override
protected void onReset() {
super.onReset();
// Ensure the loader is stopped
onStopLoading();
// At this point we can release the resources associated with 'apps'
// if needed.
this.infos = null;
}
}
static class Infos {
private Infos() {
}
public static Infos newInstance() {
return new Infos();
}
}
@Override
public void onActivityCreated (Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Log.i(TAG, "onActivityCreated");
// Prepare the loader. Either re-connect with an existing one,
// or start a new one.
getLoaderManager().initLoader(0, null, this);
}
@Override
public Loader<Infos> onCreateLoader(int arg0, Bundle arg1) {
return new InfosLoader(this.getSherlockActivity());
}
@Override
public void onLoadFinished(Loader<Infos> arg0, Infos arg1) {
Log.i(TAG, "onLoadFinished! -> " + arg1);
}
@Override
public void onLoaderReset(Loader<Infos> arg0) {
}
public void reloadAfterOpenFromCloud() {
this.getLoaderManager().getLoader(0).onContentChanged();
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.home_menu, container, false);
return v;
}
private static final String TAG = HomeMenuFragment.class.getSimpleName();
}
Logging
I/HomeMenuFragment(14776): onActivityCreated
I/HomeMenuFragment(14776): forceLoad
I/HomeMenuFragment(14776): loadInBackground
I/HomeMenuFragment(14776): onLoadFinished! -> org.yccheok.gui.HomeMenuFragment$Infos@4195ad58
[Rotation happens right here]
I/HomeMenuFragment(14776): onActivityCreated
I/HomeMenuFragment(14776): onLoadFinished! -> org.yccheok.gui.HomeMenuFragment$Infos@4195ad58
I/HomeMenuFragment(14776): onLoadFinished! -> org.yccheok.gui.HomeMenuFragment$Infos@4195ad58
According to Android: LoaderCallbacks.OnLoadFinished called twice, one of the proposed solution is calling initLoader
in onResume
.
@Override
public void onActivityCreated (Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Log.i(TAG, "onActivityCreated");
//getLoaderManager().initLoader(0, null, this);
}
@Override
public void onResume()
{
super.onResume();
Log.i(TAG, "onResume");
// Prepare the loader. Either re-connect with an existing one,
// or start a new one.
getLoaderManager().initLoader(0, null, this);
}
Here is the logging. It looks OK now after we move initLoader
to onResume
.
Logging
I/HomeMenuFragment(15468): onActivityCreated
I/HomeMenuFragment(15468): onResume
I/HomeMenuFragment(15468): forceLoad
I/HomeMenuFragment(15468): loadInBackground
I/HomeMenuFragment(15468): onLoadFinished! -> org.yccheok.gui.HomeMenuFragment$Infos@4195aed0
I/HomeMenuFragment(15468): onActivityCreated
I/HomeMenuFragment(15468): onResume
I/HomeMenuFragment(15468): onLoadFinished! -> org.yccheok.gui.HomeMenuFragment$Infos@4195aed0
I was wondering
- Why the proposed solution work?
- Is this a bug? Should we file a bug to Google regarding this behavior? Maybe there is a ticket being filed to Google, but I cannot find it.