开发者

LoaderCallbacks.onLoadFinished is not called when loader is resused and contains data

开发者 https://www.devze.com 2023-03-28 00:07 出处:网络
I have 1 activity and 2 fragments. Both fragments use a custom AsyncTaskLoader to get some data from a webservice and as i\'m using a Loader it should keep the data across activity and fragment re-cre

I have 1 activity and 2 fragments. Both fragments use a custom AsyncTaskLoader to get some data from a webservice and as i'm using a Loader it should keep the data across activity and fragment re-creations. Both fragments override the onActivityCreated method and calls getLoaderManager().initLoader(0, null, this) which either creates a new or reuses an existing loader.

When the activity is first created, it adds Fragment #1 in a FrameLayout by default, loads the data, internally calls LoaderCallbacks.onLoadFinished() method and displays the result. I have a button which replaces Fragment #1 with Fragment #2 on click and Fragment #1 is pushed to the fragment-backstack. When the user hits the BACK key, it switches back to Fragment #1.

onActivityCreated gets called again on Fragment #1 and then obviously calls iniLoader() again. This time the data already exists in the loader and i expect it to automatically call the LoaderCallbacks.onLoadFinished method again, because it already has data available, as described here: http://goo.gl/EGFJk

Ensures a loader is initialized and active. If the loader doesn't already exist, one is created and (if the activity/fragment is currently started) starts the loader. Otherwise the last created loader is re-used. In either case, the given callback is associated with the loader, and will be called as the loader state changes. 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) will be called immediately (inside of this function), so you must be prepared for this to happen.

But the method is never called even if the loader exists and has generated data ready to deliver.


Edit #1 The problem from a users perspective:

  • User starts activity and sees fragment1 with some data
  • User clicks something which changes the first fragment to another, with different data
  • User hits the BACK key
  • User is now looking at fragment1 again, but there's no data. (which means i need to get it from the webservice again - and i'd like to avoid that if possible)

Here is my activity:

public class FragmentTestsActivity extends Activity implements OnClickListener {

    private Button btn1;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        btn1 = (Button) findViewById(R.id.btn1);
        btn1.setOnClickListener(this);

        Fragment newFragment = new Fragment1();
        FragmentTransaction ft = getFragmentManager().beginTransaction();
        ft.add(R.id.fragmentContainer, newFragment).commit();
    }

    @Override
    public void onClick(View view) {
        int id = view.getId();
        if (id == R.id.btn1) {
            showNewFragment();
        }
    }

    public void showNewFragment() {
        // Instantiate a new fragment.
        Fragment2 newFragment = new Fragment2();

        // Add the fragment to the activity, pushing this transaction
        // on to the back stack.
        FragmentTransaction ft = getFragmentManager().beginTransaction();
        ft.replace(R.id.fragmentContainer, newFragment);
        ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
        ft.addToBackStack(null);
        ft.commit();
    }
}

My Fragment #1:

public class Fragment1 extends Fragment implements LoaderCallbacks<String> {

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment1, container, false);
    }

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

        LoaderManager.enableDebugLogging(true);        
        getLoaderManager().initLoader(0, null, this);
    }

    private static class TestLoader extends AsyncTaskLoader<String> {

        String result;

        public TestLoader(Context context) {
            super(context);
        }

        @Override
        public String loadInBackground() {
            // Some long-running call to a webservice - replaced with a simple string to test with            
            return "FirstTimeData";
        }

        @Override
        public void deliverResult(String data) {
            result = data;

            if (isStarted()) {
                super.deliverResult(data);
            }
        }

        @Override
        protected void onStartLoading() {
            if (result != null) {
                deliverResult(result);
            }
            if (takeContentChanged() || result == null) {
                forceLoad();
            }
        }

        @Override
        protected void onStopLoading() {
            cancelLoad();
        开发者_如何学Python}     
    }

    @Override
    public Loader<String> onCreateLoader(int id, Bundle args) {
        return new TestLoader(getActivity());
    }

    @Override
    public void onLoadFinished(Loader<String> loader, String result) {
        Log.d("Fragment1", "onLoadFinished: " + result);
    }

    @Override
    public void onLoaderReset(Loader<String> loader) {
        // TODO Auto-generated method stub
    }
}

Anyone know a solution to this or what i'm doing wrong here? Any help is greatly appreciated.


The correct answer, for me at least, was to move the entire Loader initialisation from onViewCreated or onActivityCreated to onStart.

After that it works fine!


From my point of view the onLoadFinished will only be called the first time cause the load has already finished, it finishes just once for both fragments. What you could do is to store the result in a property in the activity and check for it in the second fragment creation.


Update: After further investigation I found my original answer to be wrong. restartLoader will also destroy the loader if it already exists.

Nevertheless I solved my own problem. I create a standard CursorLoader in onCreateLoader and swap the cursor of my CursorAdapter in onLoadFinished. I needed to call initLoader after initializing the CursorAdapter. Now the data is still there when the fragment is returned from the backstack.

Original answer: I found two possible ways to solve this issue.

Apparently when initLoader(int id, Bundle args, LoaderCallbacks<D> callback) is called for the second time in onActivityCreated(Bundle savedInstanceState) after the Fragment is returned from the backstack, the method onLoadFinished(Loader<String> loader, String result) is never called (as you described).

However, a call to restartLoader(int id, Bundle args, LoaderCallbacks<D> callback) after or instead of initLoader will finally cause onLoadFinished to be called. To improve performance, I use a boolean argument to determine whether the Fragment is a new instance or not. Then I call restartLoader only if the Fragment is returned from the backstack.

As far as I can tell, old data persists in the loader and is not reloaded, but I'm not sure. The second possibility (especially when not using the backstack but instead creating a new Fragment instance in a transaction) is to call destroyLoader(int id) before the Fragment goes away (e.g in onPause).


I already had that issue, I can't really explain why that bug but I know that line :

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

don't work in my code, So you can changed it for that :

LoaderManager lm = getLoaderManager();
lm.initLoader(LOADER_ID, null, this);

The code start the methods :

onCreateLoader

you can try...

0

精彩评论

暂无评论...
验证码 换一张
取 消