How can I implement a simple AsyncTaskLoader?
Asked Answered
I

3

9

In one of my recent SO questions, someone suggested that I use Loader for my solution. So here I am trying to understand how to implement a simple AsyncTaskLoader

Here's what I've come up with:

public class Scraper extends AsyncTaskLoader<List<Event>> {

    List<Event> lstEvents;

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

    public List<Event> loadInBackground() {

            //This is where I will do some work and return the data

    }

}

That's all I've figured out. I read the docs for the AyncTaskLoader but I have never come across anything so cryptic and messy like it. There's a million methods, all of which are contrary to each other and looking at them it is imossible to deduce, the order in which they are invoked or even whether they should be overridden and invoked. The life-cycle of this task is a damned nightmare.

What I'm looking for is to simply scrape some data and return it. I'd also like to store it in a class variable so that I return it the next time promptly without having to scrape all the data again.

I have no open cursors, streams or anything like that, just a simple variable called lstEvents (which might be big). I don't want to leak memory and waste resources so I'll be glad if someone could explain what I need to close/nullify where and when to get this task working.

Where should I store the data into the class variable? Shoudl i do it at the end of my loadInBackground method or should I do this in the deliverResult method?

In this simple scenario, are there places that I really need to check whether the task was cancelled or whether it was reset or should I simply not override these methods and let the AsyncTaskLoader handle it.

Some scaffolding code would be helpful if anyone knows. Thanks a ton.

Ihs answered 27/9, 2012 at 19:38 Comment(0)
C
7

In one of my recent SO questions, someone suggested that I use Loader for my solution.

That probably was not a good suggestion.

The life-cycle of this task is a damned nightmare.

Not to mention that the Loader contract suggests that you are supposed to be able to dynamically detect changes in the data and deliver updates automatically to the one using the Loader, which is easy for a ContentProvider, difficult for other on-device stores, and not especially practical for scenarios like yours.

Frankly, I'd just use an AsyncTask and call it good.

That being said, you might take a look at the Loader implementations I made for SQLite databases (sans ContentProvider) and SharedPreferences as examples of custom loaders. The only methods you need to implement are loadInBackground() and onStartLoading(). You may need deliverResult() if the built-in implementation is insufficient -- in your case, I suspect that the built-in one will be just fine. You can add in some of the other methods, as you will see in my AbstractCursorLoader, though once again I suspect that you will not need them.

are there places that I really need to check whether the task was cancelled or whether it was reset or should I simply not override these methods and let the AsyncTaskLoader handle it.

Start with letting AsyncTaskLoader handle it, and worry about them only if you determine that you need them for one reason or another.

Candlepower answered 27/9, 2012 at 19:59 Comment(0)
E
3

In addition to the all the good info from CommonsWare's answer, here's another thing to consider. When implementing AsyncTaskLoader, you'll need to call forceLoad() somewhere on either your Loader or on your subclass of AsyncTaskLoader. Otherwise, your loadInBackground() method won't get called in my experience.

From my understanding of the documentation, a loader can hang on to its results in a member variable like you've stubbed out in your question. Then, you can override onStartLoading() and check for your cached list of results. If the list is null, the loader can call forceLoad() on itself.

Also note that if you do end up implement caching results, be aware that loadInBackground() is called on another thread so you might want to synchronize access to any member variables.

Eupatorium answered 27/9, 2012 at 20:15 Comment(3)
"you'll need to call forceLoad()" -- this is incorrect. See: github.com/commonsguy/cw-omnibus/tree/master/Loaders/… for an example of an app that does not need to do this.Candlepower
Can you clarify? The example posted uses a CursorLoader, which inherits from AsyncTaskLoader which does call forceLoad() in onStartLoading().Eupatorium
Oh, never mind. I misunderstood where you were thinking forceLoad() would be called. My apologies for my confusion.Candlepower
O
1

It's little bit late but may be useful for others who are reading this question. This is a realization of AsyncTaskLoader:

public class EventListLoader extends AsyncTaskLoader<List<Event>> {
    private List<Event> events;

    public EventListLoader(Context context) {
        super(context);
        events = new ArrayList<Event>();
    }

    @Override
    public List<Event> loadInBackground(){
        //"Simply scrape some data and return it" as a list of events here.
    }

    @Override
    public void deliverResult(List<Event> data) {
        if (isReset()) {
            releaseResources(events);
            return;
        }

        List<Event> oldData = events;
        events = data;

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

        releaseResources(oldData);
    }

    @Override
    protected void onStartLoading() {
        if (events != null && !events.isEmpty())
            deliverResult(events);

        if (takeContentChanged() || events == null || events.isEmpty())
            forceLoad();
    }

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

    @Override
    public void onCanceled(List<Event> data) {
        super.onCanceled(data);
        releaseResources(events);
    }

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

    private void releaseResources(List<Event> data) {
        if (data!= null && !data.isEmpty())
            data.clear();
    }
}

Using it inside of ListFragment as:

public class EventsListFragment extends ListFragment implements LoaderManager.LoaderCallbacks<List<Event>> {
    private static final int LOADER_ID = 0;

    ...

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        ...
        setListAdapter(new EventListAdapter());
        if (savedInstanceState == null)
            getLoaderManager().initLoader(LOADER_ID, null, this);
        else
            getLoaderManager().restartLoader(LOADER_ID, null, this);
    }

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

    @Override
    public void onLoadFinished(Loader<List<Event>> loader, List<Event> data) {
        ((EventListAdapter) getListAdapter()).setData(data);
        invalidateActionMode(); //Only if Contextual Action Bar (CAB) is used.
    }

    @Override
    public void onLoaderReset(Loader<List<Event>> loader) {
        ((EventListAdapter) getListAdapter()).clearData();
    }

we can load and show an event list (from local database or internet) as a ListFragment (for example) if we define EventListAdapter as:

public class EventListAdapter extends BaseAdapter {
    protected List<Event> mItems = new ArrayList<>();

    @Override                               
    public boolean isEnabled(int position) {
        return true;
    }

    @Override
    public boolean hasStableIds() {
        return true;
    }

    @Override
    public int getCount() {
        return mItems.size();
    }

    @Override
    public Object getItem(int position) {
        return mItems.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
       ...
    }

    public void setData(List<Event> items){
        mItems.clear();
        mItems.addAll(items);
        notifyDataSetChanged();
    }

    public void clearData(){
        mItems.clear();
        notifyDataSetChanged();
    }
}
Ovation answered 24/1, 2017 at 15:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.