LoaderCallbacks.onLoadFinished not called if orientation change happens during AsyncTaskLoader run
Asked Answered
H

2

20

Using android-support-v4.jar and FragmentActivity (no fragments at this point)

I have an AsyncTaskLoader which I start loading and then change the orientation while the background thread is still running. In my logs I see the responses come through to the background requests. The responses complete and I expect onLoadFinished() to be called, but it never is.

As a means of troubleshooting, in the Manifest, if I set android:configChanges="orientation" onLoadFinished() gets called as expected.

My Activity implements the loader callbacks. In the source for LoaderManager.initLoader() I see that if the loader already exists, the new callback is set to the LoaderInfo inner object class but I don't see where Loader.registerListener() is called again. registerListener only seems to be called when LoaderManagerImpl.createAndInstallLoader() is called.

I suspect that since the activity is destroyed and recreated on orientation change and since it is the listener for callbacks, the new activity is not registered to be notified.

Can anyone confirm my understanding and what the solution so that onLoadFinished is called after orientation change?

Horseweed answered 23/8, 2011 at 19:20 Comment(2)
As another troubleshooting step I added a UI-less worker Fragment and setRetainInstance to true. The Fragment implements the LoaderCallbacks. The Fragment is retained between orientation changes but its onLoadFinished() is never called after orientation change.Horseweed
Where are you calling initLoader()? Make sure it's in onCreate(). BTW, you can use LoaderManager.enableDebugLogging(true) to get some debugging info about the loader lifecycle (in logcat).Luminal
H
34

Nikolay identified the issue - Thank you.

I was calling initLoader fron onResume(). The Android documentation states:

"You typically initialize a Loader within the activity's onCreate() method, or within the fragment's onActivityCreated() method."

Read "typically" as a bit more emphatic than I did when it comes to dealing with configuration change life cycle.

I moved my initLoader call to onCreate() and that solved my problem.

I think the reason is that in FragmentActivity.onCreate() a collection of LoaderManagers is pulled from LastNonConfigurationInstance and in FragmentActivity.onStart() there is some start up work regarding Loaders and LoaderManagers. Things are already in process by the time onResume() is called. When the Loader needs instantiated for the first time, calling initLoader from outside onCreate() still works.

Horseweed answered 24/8, 2011 at 13:53 Comment(3)
Thanks for summarizing this here, I used your answer for the fragments implementation. It helped a lot ! Also, thanks Nikolay for the info. Amazingly helpful guys !Cipher
I had the same problem. But moving initLoader to onActivityCreated() did not work for me. But moving it to onCreate() did! Not sure why that is... Thanks for a good answer that helped a lot!Afc
I also faced same problem.This scenario is solved. But when loader is in progress if user locks the phone and comes to activity, onLoadFinished is not called.Herbalist
H
3

It's actually not the call to initLoader() in onCreate() that's fixing it. It's the call to getLoaderManager(). In summary, what happens is that when an activity is restarted, it already knows about the loaders. It tries to restart them when your activity hits onStart(), but then it hits this code in FragmentHostCallback.doLoaderStart()*:

void doLoaderStart() {
    if (mLoadersStarted) {
        return;
    }
    mLoadersStarted = true;

    if (mLoaderManager != null) {
        mLoaderManager.doStart();
    } else if (!mCheckedForLoaderManager) {
        mLoaderManager = getLoaderManager("(root)", mLoadersStarted, false);
        // WTF: Why aren't we calling doStart() here?!
    }
    mCheckedForLoaderManager = true;
}

Since getLoaderManager() wasn't called yet, mLoaderManager is null. It therefore skips the first condition and the call to mLoaderManager.doStart().

You can test this by simply putting a call to getLoaderManager() in onCreate(). You don't need to call init / restart loaders there.

This really seems like a bug to me.

* This is the code path even if you aren't using fragments, so don't get confused by that.

Heartily answered 15/11, 2017 at 0:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.