Will LoaderManager.restartLoader() always result in a call to onCreateLoader()?
Asked Answered
E

1

15

LoaderManager has this method restartLoader():

public abstract Loader<D> restartLoader (int id, Bundle args, LoaderCallbacks<D> callback)

Starts a new or restarts an existing Loader in this manager, registers the callbacks to it, and (if the activity/fragment is currently started) starts loading it. If a loader with the same id has previously been started it will automatically be destroyed when the new loader completes its work. The callback will be delivered before the old loader is destroyed.

Based on the dev guide, I get the idea that indeed, a call to onCreateLoader will always result from restartLoader():

Restarting a Loader

...

To discard your old data, you use restartLoader(). For example, this implementation of SearchView.OnQueryTextListener restarts the loader when the user's query changes. The loader needs to be restarted so that it can use the revised search filter to do a new query:

public boolean onQueryTextChanged(String newText) {
    // Called when the action bar search text has changed.  Update
    // the search filter, and restart the loader to do a new query
    // with this filter.
    mCurFilter = !TextUtils.isEmpty(newText) ? newText : null;
    getLoaderManager().restartLoader(0, null, this);
    return true;
}

public Loader<Cursor> onCreateLoader(int id, Bundle args) {
    // NOTE: The Loader is instantiated with the user's query

    Uri baseUri;       
    if (mCurFilter != null) {
        baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,
                  Uri.encode(mCurFilter));
    } else {
        baseUri = Contacts.CONTENT_URI;
    }

    // Now create and return a CursorLoader that will take care of
    // creating a Cursor for the data being displayed.
    String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND ("
            + Contacts.HAS_PHONE_NUMBER + "=1) AND ("
            + Contacts.DISPLAY_NAME + " != '' ))";
    return new CursorLoader(getActivity(), baseUri,
            CONTACTS_SUMMARY_PROJECTION, select, null,
            Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC");
}

In the example, onCreateLoader is the only place where information about the user's query is passed to the Loader (on instantiation). However the docs throw me off, by saying 'Starts a new or restarts an existing Loader'.

Elmiraelmo answered 21/1, 2014 at 8:55 Comment(0)
U
30

The simple answer to your question is yes, a call to restartLoader() will call onCreateLoader() again.

You can start more than one loader in parallel (say you have two SimpleCursorAdapters to fill), e.g.:

getLoaderManager().initLoader(0, null, this);  //id = 0
getLoaderManager().initLoader(1, null, this);  //id = 1

onCreateLoader is then called by the Loader Manager for each id (the loader that is returned is then built asynchronously by the Loader Manager):

public Loader<Cursor> onCreateLoader(int id, Bundle args)
{
    if (id == 0)
        //return a Loader<Cursor> for id 0
    else if (id == 1)
        //return a Loader<Cursor> for id 1
}

The Loader Manager passes the resulting loader to onLoadFinished:

public void onLoadFinished(Loader<Cursor> loader, Cursor cursor)
{
    if (loader.getId() == 0)
        //cursor was returned from onCreateLoader for id 0
        //perhaps do swapCursor(cursor) on an adapter using this loader
    else if (loader.getId() == 1)
        //cursor was returned from onCreateLoader for id 1
        //perhaps do swapCursor(cursor) on an adapter using this loader
}

When you subsequently call restart loader:

getLoaderManager().restartLoader(0, null, this);  //id = 0

...onLoaderReset is first called:

public void onLoaderReset(Loader<Cursor> loader)
{
    if (loader.getId() == 0)
        //perhaps do swapCursor(null) on an adapter using this loader
    else if (loader.getId() == 1)
        //perhaps do swapCursor(null) on an adapter using this loader
}

...followed by a new call to onCreateLoader. So in that respect, onCreateLoader is used both to start a brand new loader and when an existing loader is reset.

Unchurch answered 21/1, 2014 at 9:47 Comment(5)
I'm interested as to why you think that is the case? This is the whole point of loaders, to interact with a ContentProvider/Datasource asynchronously. The long running task, that might otherwise block the UI, is run in onCreateLoader - the LoaderManager runs that part in the background.Unchurch
Yes, yes, the loadInBackBackground part of an AsyncTaskLoader surely runs on a background thread, but I think onCreateLoader is on the UI thread: Clients of loaders should as a rule perform any calls on to a Loader from the main thread of their process (that is, the thread the Activity callbacks and other things occur on). -- Comparable to instantiating an AsyncTask, that was my reasoning.Elmiraelmo
The call to the Loader you make from the client/main thread is the .initLoader() call. The Loader then runs onCreateLoader in the background.Unchurch
using this: android.os.Process.getThreadPriority(android.os.Process.myTid()); seems like onCreateLoader runs on UI thread. I've also added some UI updates in onCreateLoader and it works without any problem.Smukler
@Smukler Thank you; having looked at this again you are correct. onCreateLoader does run on the UI thread - it is the loader that it returns that is built asynchronously by the Loader Manager. I have edited my answer to remove the inaccuracy.Unchurch

© 2022 - 2024 — McMap. All rights reserved.