Android: LoaderCallbacks.OnLoadFinished called twice

AndroidLoader

Android Problem Overview


I noticed strange situation using Android Loaders and Fragments. When I invoke LoaderManager.initLoader() after orientation change onLoadFinished is not called (although documentation suggests I should be prepared for this) but it is called twice after this. Here is link to post in google groups which describe the same situation https://groups.google.com/forum/?fromgroups#!topic/android-developers/aA2vHYxSskU . I wrote sample application in which I only init simple Loader in Fragment.onActivityCreated() to check if this happens and it does. Anyone noticed this?

Android Solutions


Solution 1 - Android

You can put the initLoader() method inside your Fragment's onResume() callback; then the Loader's onLoadFinished() will not be called twice anymore.

	@Override
public void onResume()
{
	super.onResume();
	getLoaderManager().initLoader(0, null, this);
}

Solution 2 - Android

This problem manifested itself for me with a CursorLoader returning a Cursor that was already closed:

android.database.StaleDataException: Attempted to access a cursor after it has been closed.

I'd guess this is a bug or an oversight. While moving initLoader() into onResume may work, what I was able to do was remove the Loader when I'm done with it:

To start the loader (in my onCreate):

  getLoaderManager().initLoader(MUSIC_LOADER_ID, null, this);

Then after I'm done with it (basically at the end of onLoadFinished)

  getLoaderManager().destroyLoader(MUSIC_LOADER_ID);

This seems to behave as expected, no extra calls.

Solution 3 - Android

initLoader documentation says,

> If at the point of call the caller is in its started state, and the > requested loader already exists and has generated its data, then > callback onLoadFinished(Loader, D)

I suggest you to implement something like onStartLoading function at this sample

For quick test you can try:

@Override protected void onStartLoading() {
    forceLoad();
}

This launch loadInBackground function and then onLoadFinished in Fragment.

Any way, if you attach some code i'll try to give you more help.

Solution 4 - Android

I solved the problem of onLoadFinished being called twice like this. In your Fragment.onActivityCreated() init your Loader like this

if (getLoaderManager().getLoader(LOADER_ID) == null) {
    getLoaderManager().initLoader(LOADER_ID, bundle, loaderCallbacks);
} else {
    getLoaderManager().restartLoader(LOADER_ID, bundle, loaderCallbacks);

}

here loaderCallbacks implements your usual Loader callbacks

private LoaderManager.LoaderCallbacks<T> loaderCallbacks
        = new LoaderManager.LoaderCallbacks<T>() {
    @Override
    public Loader<T> onCreateLoader(int id, Bundle args) {
    	...
    	...
    }

    @Override
    public void onLoadFinished(Loader<T> loader, T data) {
    	...
    	...
    }

    @Override
    public void onLoaderReset(Loader<T> loader) {
    	...
    	...
    }
};

Solution 5 - Android

The problem is that it called twice:

  1. from Fragment.onStart
  2. from FragmentActivity.onStart

The only difference is that in Fragment.onStart it checks if mLoaderManager != null. What this means is if you call getLoadManager before onStart, like in onActivityCreated, it will get/create load manager and it will be called. To avoid this you need to call it later, like in onResume.

Solution 6 - Android

When calling initLoader from onActivityCreated you can detect rotation :

@Override
public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);

    if (savedInstanceState == null) {
        // fresh new fragment, not orientation/config change
        getLoaderManager().initLoader(YOUR_LOADER_ID, null, mCallbacks);
    }
    ...
}

This way the loader behaves as expected resulting in single onLoadFinished call.
It is not called on rotation anymore, so if you want loader's data, you can keep it in your fragment, for instance by overriding onSaveInstanceState.

Edit:
I just realized that onLoadFinished won't be called if rotation happens during loader's loadInBackground. To fix this you'd still need to call initLoader after rotation if the data from loader not yet available.

Hope that helps.

Solution 7 - Android

You can also compare the data object in onLoadFinished(Loader loader, Object data). If the data object matches one you already have, you can just not do anything when onLoadFinished is called. For example:

public void onLoadFinished(Loader loader, Object data) {
        if(data != null && mData != data){
            //Do something
        }
}

Solution 8 - Android

Since all searching for this subject inevitably ends up here, I just wanted to add my experience. As @jperera said, the culprit was that LoaderManager will call onLoadFinished() if the loaders already exist. In my case, I had fragments in a FragmentPager and scrolling 2 tabs away and then scrolling next to it again would cause my old fragment to begin creating itself.

Since placing initLoader() inside onCreate() also causes double callbacks, I placed the initLoader() inside onResume(). But the sequence of events ends up being onCreate(), LoaderManager calls callbacks since loaders exist, then onResume() is called, triggering another initLoader() and onLoadFinished() sequence. IE, another double callback.

solution

I found a quick solution by "Matt". After all your data is loaded (if you have more than one loader), destroy all of the loaders so their callbacks won't be called an extra time.

Solution 9 - Android

i have face this issue.but i while used to call the destroyloader(YOUR_ID) in loaderfinished methods. then the loader not again call the backgrdound task twice.

Solution 10 - Android

If implementing AppCompatActivity, double check you are using getSupportLoaderManager() in all cases (destroyLoader/initLoader etc). I had mistakenly used getSupportLoaderManager() in conjunction with a getLoaderManager() and suffered the same issue.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionLukaszSView Question on Stackoverflow
Solution 1 - AndroidBogdan ZuracView Answer on Stackoverflow
Solution 2 - AndroidMattView Answer on Stackoverflow
Solution 3 - AndroidjpereraView Answer on Stackoverflow
Solution 4 - AndroidAmrendra KumarView Answer on Stackoverflow
Solution 5 - AndroidvovkabView Answer on Stackoverflow
Solution 6 - AndroidkiruwkaView Answer on Stackoverflow
Solution 7 - AndroidAmer MeerView Answer on Stackoverflow
Solution 8 - Android1mike12View Answer on Stackoverflow
Solution 9 - AndroidprakashView Answer on Stackoverflow
Solution 10 - AndroidCoastalBView Answer on Stackoverflow