63

I am trying to implement a loader example on Android but can't get it to start the loader. I am using the following code. It will hit the "Create Loader" but it will never reach the "Loading started" log message. Am I missing a call that I need?

Activity:

    public class TestingZoneActivity extends ListActivity implements LoaderCallbacks<ArrayList<Content>>{

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

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

        @Override
        public Loader<ArrayList<Content>> onCreateLoader(int id, Bundle args) {
            Log.e("TEST", "Create Loader");
            return new ImageLoader(this);
        }

        @Override
        public void onLoadFinished(Loader<ArrayList<Content>> loader, ArrayList<Content> data) {
            setListAdapter(new ImageAdapter(this, data));
        }

        @Override
        public void onLoaderReset(Loader<ArrayList<Content>> loader) {
            setListAdapter(null);
        }
    }

Loader:

    public class ImageLoader extends AsyncTaskLoader<ArrayList<Content>> {

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

        @Override
        public ArrayList<Content> loadInBackground() {
            Log.e("TEST", "Loading started");
        }

    }
Bobbake4
  • 24,509
  • 9
  • 59
  • 94
  • are you using the compatibility library?If yes take a look at this [issue](http://code.google.com/p/android/issues/detail?id=14944) – Renard May 09 '12 at 23:04
  • Alright that thread solved my problem. Post it as an answer and I will accept. – Bobbake4 May 10 '12 at 14:24
  • The best way for you understand that is checking the CursorLoader's source code. In fact, you have to take care of when loadInBackground() should be called, so that's way your code is not being called. Just override loadInBackground() is not sufficient, and you can check that in documentation. For example, inside your implementation, you have to decide by conditions when forceLoad() should be executed. – Tiago Feb 05 '16 at 23:18

6 Answers6

131

I had the same problem using the compatibility library. I solved it by calling forceLoad

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

Obviously the documentation on AsyncLoader is lacking and this problem also exists on HoneyComb. More information can be found here

The official example of AsyncTaskLoader is also calling forceLoad() so its not a bug, but i still think that that behavior is not very intuitive.

Renard
  • 6,909
  • 2
  • 27
  • 23
  • 1
    Thank you so much! It's really a pity that the official Android documentation has this kind of problems/incongruences. – the_dark_destructor Jul 05 '13 at 10:31
  • 11
    As you know if requested loader already exists and has generated it's data, calling `LoaderManager.initLoader()` triggers firing `LoaderCallbacks.onLoadFinished()` omitting `AsyncTaskLoader.onLoadInBackground()` callback. But in the same time 'AsyncTaskLoader.forceLoad()` forces loading procedure to start over without reporting when finished. This may lead to unexpected behavior. Better look at method `AsyncTaskLoader.onStartLoading()` and implement `AsyncTaskLoader.forceLoad()` there. – Grigori A. Dec 16 '13 at 14:48
  • 1
    Just to be comprehensive, I also needed to call `#forceLoad()` on `getLoaderManager().restartLoader(...)` in addition to `getLoaderManager().initLoader(...)` – Scott Tomaszewski Jan 07 '15 at 22:45
  • 3
    This causes `loadInBackground()` to be called unnecessarily often, i.e. on orientation change, which does not happen when overriding `deliverResult()` and `onStartLoading()` properly. – Torsten Römer Sep 02 '15 at 15:05
  • This didn't work for me, `onContentChanged()` instead of `forceLoad()` neither. Maybe it is because I'm using `getSupportLoaderManager()`. But what worked for me was this: `getSupportLoaderManager().restartLoader(0, null, MyActivity.this);` – Bevor May 08 '16 at 15:35
  • Thanks man, you saved my day. If some one using support libs. they can use "getSupportLoaderManager()" instead of "getLoaderManager()" – Jay Oct 05 '17 at 06:38
26

Overriding loadInBackground() is not enough.

Have a look at the AppListLoader on http://developer.android.com/reference/android/content/AsyncTaskLoader.html .

At least add these two methods to your loader:

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

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

and call onContentChanged() from its constructor.

rrayst
  • 261
  • 3
  • 2
  • It seems that you also have to use `getLoaderManager().initLoader(0, null, this).onContentChanged();` construction – Boris Treukhov Jan 19 '16 at 16:20
  • This is for the case if the loader hangs when frame has started loading, then paused, and then resumed. – Boris Treukhov Jan 19 '16 at 16:30
  • 1
    Just adding these two methods is not enough, you have to add onContentChanged(); in the constructor after calling super(). (Or use the .onContentChanged() where you call it like @boris says – 1mike12 Feb 04 '16 at 20:11
14

rrayst's advice is quite compact. If you write your method like this:

protected void onStartLoading() {
    forceLoad();
}

you ''ll notice that when a child activity comes up and then you return to the parent one, onStartLoading (and so loadInBackground) are called again!

What can you do? Set an internal variable (mContentChanged) to true inside the constructor; then check this variable inside onStartLoading. Only when it's true, start loading for real:

package example.util;

import android.content.Context;
import android.support.v4.content.AsyncTaskLoader;

public abstract class ATLoader<D> extends AsyncTaskLoader<D> {

    public ATLoader(Context context) {
        super(context);
        // run only once
        onContentChanged();
    }

    @Override
    protected void onStartLoading() {
        // That's how we start every AsyncTaskLoader...
        // -  code snippet from  android.content.CursorLoader  (method  onStartLoading)
        if (takeContentChanged()) {
            forceLoad();
        }
    }

    @Override
    protected void onStopLoading() {
        cancelLoad();
    }
}
Community
  • 1
  • 1
KitKat
  • 1,495
  • 14
  • 15
  • I hate this behaviour. In any place of the documentation it says `onStartLoading()` will be called after a child activity finishes and the parent one comes back. Luckily `takeContentChanged()` solves the problem. – AxeEffect Feb 23 '14 at 14:17
  • Is there any way to check if the loader has finished its job? Would setting a boolean variable in deliverResult do the trick? Or should I do more than that? – android developer Mar 30 '16 at 07:16
  • 1
    Should you call "cancelLoad" in "onStopLoading" ? – android developer Mar 30 '16 at 08:36
  • @androiddeveloper Although I had no problems omitting this call, I edited my code. – KitKat Jun 22 '16 at 16:35
3

Since none of the answers here (besides the accepted one) alone helped me to solve this, here is how it worked for me.

I don't think the accepted answer is the correct solution, since it causes loadInBackground() to be called more often than necessary, i.e. on orientation change, which does not happen when properly overriding the following methods in the loader as well:

@Override
public void deliverResult(final List<Participant> data) {
    participants = data;

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

}

@Override
protected void onStartLoading() {
    if (takeContentChanged() || participants == null) {
        forceLoad();
    }
}
Torsten Römer
  • 3,834
  • 4
  • 40
  • 53
0

If you are using a custom loader, you can save the last data reference, and have it available via a getter. when the user rotates his screen, you can get the loader back from getLoaderManager().getLoader method, and then return back the reference. For my testing I noticed that startLoadering goes all the way to CustomLoader.onLoadFinished but the result is never deliver to activity.onLoadFinished.I suspect the activity reference gets lost upon rotation. By the way the great thing about creating loaders is they are persistent through LoaderManager. Think of it as another flavor of headless fragments.. lol.

Loader loader  =  getLoaderManager().getLoader(LOADER_ID);

if( loader == null )
{
    getLoaderManager().initLoader(LOADER_ID, null, MyActivity.this );
}
else
{
    listAdapter.addAll(((CustomLoader) loader).getLastData());
}
Juan Mendez
  • 2,658
  • 1
  • 27
  • 23
0

I've found that each of the above solutions have issues, especially when the app starts while the screen is turned off, and the loading takes a few moments.

Here's my solution (base on this):

https://stackoverflow.com/a/22675607/878126

android developer
  • 114,585
  • 152
  • 739
  • 1,270