Difference between initLoader and restartLoader in LoaderManager
Asked Answered
R

6

129

I'm completely lost regarding the differences between the initLoader and the restartLoader functions of the LoaderManager:

  • They both have the same signature.
  • restartLoader also creates a loader, if it does not exist ("Starts a new or restarts an existing Loader in this manager").

Is there some relation between the two methods? Does calling restartLoader always call initLoader? Can I call restartLoader without having to call initLoader? Is it safe to call initLoader twice to refresh the data? When should I use one of the two and why?

Rupp answered 21/1, 2013 at 18:36 Comment(0)
Z
201

To answer this question you need to dig into the LoaderManager code. While the documentation for LoaderManager itself isn't clear enough (or there wouldn't be this question), the documentation for LoaderManagerImpl, a subclass of the abstract LoaderManager, is much more enlightening.

initLoader

Call to initialize a particular ID with a Loader. If this ID already has a Loader associated with it, it is left unchanged and any previous callbacks replaced with the newly provided ones. If there is not currently a Loader for the ID, a new one is created and started.

This function should generally be used when a component is initializing, to ensure that a Loader it relies on is created. This allows it to re-use an existing Loader's data if there already is one, so that for example when an Activity is re-created after a configuration change it does not need to re-create its loaders.

restartLoader

Call to re-create the Loader associated with a particular ID. If there is currently a Loader associated with this ID, it will be canceled/stopped/destroyed as appropriate. A new Loader with the given arguments will be created and its data delivered to you once available.

[...] After calling this function, any previous Loaders associated with this ID will be considered invalid, and you will receive no further data updates from them.

There are basically two cases:

  1. The loader with the id doesn't exist: both methods will create a new loader so there's no difference there
  2. The loader with the id already exists: initLoader will only replace the callbacks passed as a parameter but won't cancel or stop the loader. For a CursorLoader that means the cursor stays open and active (if that was the case before the initLoader call). `restartLoader, on the other hand, will cancel, stop and destroy the loader (and close the underlying data source like a cursor) and create a new loader (which would also create a new cursor and re-run the query if the loader is a CursorLoader).

Here's the simplified code for both methods:

initLoader

LoaderInfo info = mLoaders.get(id);
if (info == null) {
    // Loader doesn't already exist -> create new one
    info = createAndInstallLoader(id, args, LoaderManager.LoaderCallbacks<Object>)callback);
} else {
   // Loader exists -> only replace callbacks   
   info.mCallbacks = (LoaderManager.LoaderCallbacks<Object>)callback;
}

restartLoader

LoaderInfo info = mLoaders.get(id);
if (info != null) {
    LoaderInfo inactive = mInactiveLoaders.get(id);
    if (inactive != null) {
        // does a lot of stuff to deal with already inactive loaders
    } else {
        // Keep track of the previous instance of this loader so we can destroy
        // it when the new one completes.
        info.mLoader.abandon();
        mInactiveLoaders.put(id, info);
    }
}
info = createAndInstallLoader(id, args,  (LoaderManager.LoaderCallbacks<Object>)callback);

As we can see in case the loader doesn't exist (info == null) both methods will create a new loader (info = createAndInstallLoader(...)). In case the loader already exists initLoader only replaces the callbacks (info.mCallbacks = ...) while restartLoader inactivates the old loader (it will be destroyed when the new loader completes its work) and then creates a new one.

Thus said it's now clear when to use initLoader and when to use restartLoader and why it makes sense to have the two methods. initLoader is used to ensure there's an initialized loader. If none exists a new one is created, if one already exists it's re-used. We always use this method UNLESS we need a new loader because the query to run has changed (not the underlying data but the actual query like in SQL statement for a CursorLoader), in which case we will call restartLoader.

The Activity/Fragment life cycle has nothing to do with the decision to use one or the other method (and there's no need to keep track of the calls using a one-shot flag as Simon suggested)! This decision is made solely based on the "need" for a new loader. If we want to run the same query we use initLoader, if we want to run a different query we use restartLoader.

We could always use restartLoader but that would be inefficient. After a screen rotation or if the user navigates away from the app and returns later to the same Activity we usually want to show the same query result and so the restartLoader would unnecessarily re-create the loader and dismiss the underlying (potentially expensive) query result.

It's very important to understand the difference between the data that is loaded and the "query" to load that data. Let's assume we use a CursorLoader querying a table for orders. If a new order is added to that table the CursorLoader uses onContentChanged() to inform the UI to update and show the new order (no need to use restartLoader in this case). If we want to display only open orders we need a new query and we would use restartLoaderto return a new CursorLoader reflecting the new query.


Is there some relation between the two methods?

They share the code to create a new Loader but they do different things when a loader already exists.

Does calling restartLoader always call initLoader?

No, it never does.

Can I call restartLoader without having to call initLoader?

Yes.

Is it safe to call initLoader twice to refresh the data?

It's safe to call initLoader twice but no data will be refreshed.

When should I use one of the two and why?


That should (hopefully) be clear after my explanations above.

Configuration changes

A LoaderManager retains its state across configuration changes (including orientation changes) so you would think there's nothing left for us to do. Think again...

First of all, a LoaderManager doesn't retain the callbacks, so if you do nothing you won't receive calls to your callback methods like onLoadFinished() and the like and that will very likely break your app.

Therefore we HAVE to call at least initLoader to restore the callback methods (a restartLoader is, of course, possible too). The documentation states:

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) [...].

That means if we call initLoader after an orientation change, we will get an onLoadFinished call right away because the data is already loaded (assuming that was the case before the change). While that sounds straight forward it can be tricky (don't we all love Android...).

We have to distinguish between two cases:

  1. Handles configuration changes itself: this is the case for Fragments that use setRetainInstance(true) or for an Activity with the according android:configChanges tag in the manifest. These components won't receive an onCreate call after e.g. a screen rotation, so keep in mind to call initLoader/restartLoader in another callback method (e.g. in onActivityCreated(Bundle)). To be able to initialize the Loader(s), the loader ids need to be stored (e.g. in a List). Because the component is retained across configuration changes we can just loop over the existing loader ids and call initLoader(loaderid, ...).
  2. Doesn't handle configuration changes itself: In this case the Loaders can be initialized in onCreate but we need to manually retain the loader ids or we won't be able to make the needed initLoader/restartLoader calls. If the ids are stored in an ArrayList, we would do an
    outState.putIntegerArrayList(loaderIdsKey, loaderIdsArray) in onSaveInstanceState and restore the ids in onCreate: loaderIdsArray = savedInstanceState.getIntegerArrayList(loaderIdsKey) before we make the initLoader call(s).
Ziegfeld answered 4/1, 2014 at 3:33 Comment(16)
:+1: One last point. If you use initLoader (and all callbacks have finished, Loader is idle) after a rotation you won't get an onLoadFinished callback but if you use restartLoader you will?Technics
Incorrect. The initLoader method calls the onLoadFinished() method before it returns (if the loader is started and has data). I added a paragraph about configuration changes to explain this in more detail.Ziegfeld
ah of course, a combination of your answer and @alexlockwood's gives the full picture. I guess the answer for others is, use initLoader if your Query is static and restartLoader if you want to change the queryTechnics
That summons it nicely: "use initLoader if your Query is static and restartLoader if you want to change the query"Ziegfeld
i am using "initLoader" and getting response from Webservice by passing username and password if response is true ...... so its working fine but if response if "false" its giving me toast message(custom message done by me) and after getting first false response its every time giving me "false response" even my id and password is fine .......whats the solution to overcome in this situation ....please let me know solution may i need to restart loader as soon as i got false response from serverJawbone
@EmanuelMoecklin Excellent answer! However, I am confused with your last two points. Why do you say "loader idS" (plural)? Doesn't every loader have one, and only one, ID?Highkeyed
@Tiago Yes but who says an app uses only one Loader at at time? One of my apps uses an arbitrary number of Loaders (combining data from a arbitrary number of databases, one for each account the user has set up). I'm merely covering the general case.Ziegfeld
@EmanuelMoecklin Ok, I am not saying an app always uses only one Loader at a time. I just said the sentence I read was ambiguous in which I thought you were talking about one loader having two IDs or something (and not multiple loaders). Thanks for clarifying though.Highkeyed
@Tiago I'm using Loader(s) / Loaders and loader ids and that seemed clear to me but go ahead and edit my answer if you think you can improve it.Ziegfeld
It would be great if there was a restartLoader() that preserved the existing callbacks. Also it seems like there is no straightforward way currently of changing the loader URL so that next time the loader is used it will refresh and load from the new URL for instance.Amaras
@Jarrod Smith Preserving the callbacks would add a little convenience but would IMO make the code more error prone. How can you be sure that callbacks had been passed into the loader already? Passing them with each initLoader/restartLoader is explicit and easier to understand/maintain.Ziegfeld
@Jarrod Smith: restartLoader is exactly the way to load from a new URL (I guess you're talking about CursorLoaders?). call restartLoader and return the new CursorLoader with the new Uri in onCreateLoader.Ziegfeld
@EmanuelMoecklin Think about the case where your fragments are created but not currently added to the Activity at the moment you learn that their loaders need to be restarted. For starters mFrag.getLoaderManager() throws an exception in this case, forcing you to maintain a separate set of dirty flags for all the loaders in your app and add another layer that wraps LoaderManager. My case is a radio streaming app where the base URL for many of the loaders across multiple app screens need to change when the user changes the station.Amaras
@Jarrod Smith Loaders and especially the LoaderManager are associated with an Activity or Fragment and shouldn't be used outside that context ("LoaderManager: Interface associated with an Activity or Fragment for managing one or more Loader instances associated with it"). What you describe sounds more like it requires a Service (different screens can bind to the service to retrieve the data). Don't use loaders for something they were not designed to be used for.Ziegfeld
@EmanuelMoecklin, Thanks for such a great answer. Why are we changing the call backs in initLoader? what would happen if I would call initLoader with different callBacks (I don't see a use case, I am just asking out of curiosity).Poul
@Mhd. Tahawi you're not changing the callbacks, you only set them to wherever they should go. After a screen rotation they need to be re-set because Android won't keep them around to prevent memory leaks. You're free to set them to whatever you want as long as they do the right thing.Ziegfeld
E
46

Calling initLoader when the Loader has already been created (this typically happens after configuration changes, for example) tells the LoaderManager to deliver the Loader's most recent data to onLoadFinished immediately. If the Loader has not already been created (when the activity/fragment first launches, for example) the call to initLoader tells the LoaderManager to call onCreateLoader to create the new Loader.

Calling restartLoader destroys an already existing Loader (as well as any existing data associated with it) and tells the LoaderManager to call onCreateLoader to create the new Loader and to initiate a new load.


The documentation is pretty clear about this too:

  • initLoader 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.

  • restartLoader 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.

Elative answered 23/5, 2013 at 4:47 Comment(8)
@TomanMoney I explained what it means in my answer. What part are you confused about?Elative
you just rehashed the doc. But the doc gives no indication as to where each method should be used and why it is bad to mess it up. In my experience, just calling restartLoader and never calling initLoader works fine. So this is still confusing.Fusain
@TomanMoney Usually you use initLoader() in onCreate()/onActivityCreated() when the activity/fragment first starts up. This way, when the user first opens an activity, the loader will be created for the first time... but on any subsequent configuration changes where the entire activity/fragment must be destroyed, the following call to initLoader() will simply return the old Loader instead of creating a new one. Usually you use restartLoader() when you need to change the Loader's query (i.e. you want to get filtered/sorted data, etc.).Elative
Yes, but in onRestart, you typically want to refresh the data, so you need this whole business with the one-time flag Simon below had alluded to. Yes, in a perfect world, this is not necessary as the loader would get notified of the data change and this would happen automatically, but the world is not perfect...Fusain
I am still confused about the API decision to have both methods, since they have the same signature. Why couldn't the API be a single startLoader() method which does the "right thing" every time? I think this is the part that confuses a lot of people.Fusain
@TomanMoney The documentation here says: developer.android.com/guide/components/loaders.html. "They automatically reconnect to the last loader's cursor when being recreated after a configuration change. Thus, they don't need to re-query their data."Diley
I have a doubt here. Do we explicitly communicate to the LoaderManager to make the mCallback null, when the Activity/Fragment is undergoing a orientation change? If not then LoaderManager can try to call a callback on a destroyed reference untill initLoader() is called again in onCreate() of Activity/Fragment where it will update the mCallback.Subdued
@AlexLockwood will calling restartLoader() will have any impact on onLoaderReset() of loader.Been
M
16

I recently hit a problem with multiple loader managers and screen orientation changes and would like to say that after a lot of trial-and-error, the following pattern works for me in both Activities and Fragments:

onCreate: call initLoader(s)
          set a one-shot flag
onResume: call restartLoader (or later, as applicable) if the one-shot is not set.
          unset the one-shot in either case.

(in other words, set some flag so that initLoader is always run once & that restartLoader is run on the 2nd & subsequent passes through onResume)

Also, remember to assign different ids for each of your loaders within an Activity (which can be a bit of an issue with fragments within that activity if you're not careful with your numbering)


I tried using initLoader only .... didn't seem to work effectively.

Tried initLoader on onCreate with null args (docs say this is ok) & restartLoader (with valid args) in onResume ....docs are wrong & initLoader throws a nullpointer exception.

Tried restartLoader only ... works for a while but blows on 5th or 6th screen re-orientation.

Tried initLoader in onResume; again works for a while & then blows. (specifically the "Called doRetain when not started:"... error)

Tried the following : (excerpt from a cover class that has the loader id passed into the constructor)

/**
 * start or restart the loader (why bother with 2 separate functions ?) (now I know why)
 * 
 * @param manager
 * @param args
 * @deprecated use {@link #restart(LoaderManager, Bundle)} in onResume (as appropriate) and {@link #initialise(LoaderManager, Bundle)} in onCreate 
 */
@Deprecated 
public void start(LoaderManager manager, Bundle args) {
    if (manager.getLoader(this.id) == null) {
        manager.initLoader(this.id, args, this);
    } else {
        manager.restartLoader(this.id, args, this);
    }
}

(which I found somewhere in Stack-Overflow)

Again, this worked for a while but still threw the occasional glitch.


From what I can figure out while debugging, I think that there is something to do with save/restore instance state that requires that initLoader(/s) are run in the onCreate part of the lifecycle if they are to survive a spin of the cycle. ( I may be wrong.)

in the case of Managers that cannot be started until the results come back from another manager or task (ie. cannot be initialised in onCreate), I only use initLoader. (I may not be correct in this but it seems to work. These secondary loaders are not part of the immediate instance-state so using initLoader may actually be correct in this case)

lifecycle


Looking at the diagrams and docs, I would have thought that initLoader should go in onCreate & restartLoader in onRestart for Activities but that leaves Fragments using some different pattern and I've not had time to investigate if this is actually stable. Can anyone else comment on if they have success with this pattern for activities?

Merilynmeringue answered 22/5, 2013 at 23:51 Comment(5)
/@Merilynmeringue is 100% correct and this should be the accepted answer. I didn't quite believe his answer and spent several hrs trying to find different ways of making this work. As soon as I moved the initLoader call to onCreate things started working. You then need the one-shot flag to account for the times onStart is called but no onCreateRichrichara
"Tried restartLoader only ... works for a while but blows on 5th or 6th screen re-orientation." It does? I just tried it and rotated the screen a hundred times and got no blow up. What kind of exception are you getting?Fusain
-1 I appreciate the research effort behind this answer but most of the results are incorrect.Ziegfeld
@Diley almost everything. If you read and understand my answer you'll understand what initLoader and restartLoader do and when to use which and you'll also understand why almost all of Simon's conclusions are wrong. There's no connection between the life cycle of a fragment/activity and the decision when to use initLoader/restartLoader (with a caveat I explain under configuration changes). Simon concludes from trial & error that the life cycle is the clue to understanding the two methods but it's not.Ziegfeld
@Diley I'm not trying to advertise my own answer. I'm merely trying to help other developers and prevent them from using Simon's results for their own apps. Once you understand what the two methods are meant for, the whole thing becomes fairly obvious and straight forward to implement.Ziegfeld
B
0

Init loader on first start uses loadInBackground() method, on second start it will be omitted. So, my opinion, the better solution is:

Loader<?> loa; 
try {
    loa = getLoaderManager().getLoader(0);
} catch (Exception e) {
    loa = null;
}
if (loa == null) {
    getLoaderManager().initLoader(0, null, this);
} else {
    loa.forceLoad();
}

///////////////////////////////////////////////////////////////////////////

protected SimpleCursorAdapter mAdapter;

private abstract class SimpleCursorAdapterLoader 
    extends AsyncTaskLoader <Cursor> {

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

    @Override
    protected void onStartLoading() {
        if (takeContentChanged() || mAdapter.isEmpty()) {
            forceLoad();
        }
    }

    @Override
    protected void onStopLoading() {
        cancelLoad();
    }

    @Override
    protected void onReset() {
        super.onReset();
        onStopLoading();
    }
}

I spent many time to find this solution -- restartLoader(...) didn't work properly in my case. The only forceLoad() lets to finish previous loading thread without callback (so you will have all db transactions finished properly) and starts again new thread. Yes, it demands some extra time, but is more stable. Only the last started thread will take callback. Thus, if you want to make tests with interrupting your db transactions -- your welcome, try to restartLoader(...), otherwise forceLoad(). The only convenience of restartLoader(...) is to deliver new initial data, I mean parameters. And please don't forget to destroy loader in onDetach() method of the suitable Fragment in this case. Also keep in mind, that some times, when you have an activity and, let them say, 2 fragments with Loader each one inclusive activity -- you will reach only 2 Loader Managers, so Activity shares its LoaderManager with Fragment (s), that's shown on the screen first during loading. Try LoaderManager.enableDebugLogging(true); to see the details in each certain case.

Badminton answered 19/3, 2013 at 14:21 Comment(1)
-1 for wrapping the call to getLoader(0) in a try { ... } catch (Exception e) { ... }.Elative
C
0

initLoader will reuse the the same parameters if the loader already exists. It returns immediately if old data is already loaded, even if you call it with new parameters. The loader should ideally automatically notify the activity of new data. If the screen rotated, initLoader would be called again and the old data would be immediately displayed.

restartLoader is for when you want to force a reload and change the parameters as well. If you were to make a login screen using loaders, you would call only restartLoader each time the button was clicked. (The button may be clicked multiple times due to incorrect credentials etc.). You would only ever call initLoader when restoring the activity's saved instance state in the event the screen was rotated while a login was in progress.

Cloy answered 21/1, 2014 at 14:21 Comment(0)
L
-1

If the loader already exists, restartLoader will stop/cancel/destroy the old one, while initLoader will just initialize it with the given callback. I cannot find out what the old callbacks do in these cases, but I guess they will just be abandoned.

I scanned through http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.0.1_r1/android/app/LoaderManager.java but I cannot find out what the exact difference is, apart from that the methods do different things. So I would say, use initLoader the first time and restart for the following times, though I cannot say with certainty what each of them will do exactly.

Liggins answered 21/1, 2013 at 18:43 Comment(1)
And what will initLoader do in this case?Rupp

© 2022 - 2024 — McMap. All rights reserved.