Tell Android AsyncTaskLoader to retrieve more data
Asked Answered
C

2

6

Let me start off by saying this is NOT a question about scrolling ListViews. I do not want to know how to tell when a user scrolls to the bottom of a list, so please do not give me answers for that question, or mark this as a duplicate.

I am using a class that extends AsyncTaskLoader to populate a ListView with data from a web service.

Initially, I load 50 items and everything is working great. I need to know how to tell the Loader to load the next 50 items incrementally. I understand WHERE to do this in the ListView code, but I can't figure out the best way to tell the Loader that I want to load more data without resetting it and loading everything again.

Again, to clarify, the issue I'm trying to solve here is just notifying the loader that more data needs to be loaded. It already knows how to load more data when loadInBackground() is called a second time, and the ListView already knows where/when to notify the Loader, the question is just how.

Some of the relevant code:

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

    m_adapter = new SearchAdapter(getActivity());
    setListAdapter(m_adapter);

    // if the loader doesn't already exist, one will be created
    // otherwise the existing loader is reused so we don't have
    // to worry about orientation and other configuration changes
    getLoaderManager().initLoader(SEARCH_LOADER_ID, null, this);
}

@Override
public Loader<List<Result>> onCreateLoader(int id, Bundle args)
{
    String query = args != null ? args.getString(QUERY_KEY) : "";
    return new SearchLoader(getActivity(), query);
}

private class SearchAdapter extends ArrayAdapter<Result>
{
    // ...

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

        // ...

        if (position == getCount() - 2)
            // TODO: Need to notify Loader here

        // ...
    }
}

private static class SearchLoader extends OurAsyncTaskLoader<List<Result>>
{
    public SearchLoader(Context context, String query)
    {
        super(context);

        m_query = query;
        m_data = Lists.newArrayList();
        m_loadedAllResults = false;
    }

    @Override
    public List<Result> loadInBackground()
    {
        if (m_loadedAllResults)
            return m_data;

        // the Loader implementation does a == check rather than a .equals() check
        // on the data, so we need this to be a new List so that it will know we have
        // new data
        m_data = Lists.newArrayList(m_data);

        MyWebService service = new MyWebService();
        List<Result> results = service.getResults(m_query, m_data.size(), COUNT);
        service.close();

        if (results == null)
            return null;

        if (results.size() < COUNT)
            m_loadedAllResults = true;

        for (Result result : results)
            m_data.add(result)

        return m_data;
    }

    private static final int COUNT = 50;

    private final String m_query;

    private boolean m_loadedAllResults;
    private List<Result> m_data;
}
Chilopod answered 3/6, 2013 at 14:35 Comment(5)
@GeorgeStocker Please read this question. You will see that it is NOT a duplicate of the question you listed.Chilopod
More data needs to be loaded for the same query string? Anyway I would drop the Loader and use an AsyncTask or a normal thread to load additional data.Rory
But isn't a Loader the recommended way to go for providing data to back a ListView? An AsyncTask doesn't handle configuration changes, and Loaders do.Chilopod
And how would the loader know which batch to restore after a configuration change?Rory
It doesn't need to. The Loader will just return everything it has loaded so far (all the "batches"). When I notify the Loader, it will load 50 more items and add it to the data it has already loaded. That data only gets cleared out if the query is changed.Chilopod
C
3

I figured out a way that works. In my SearchAdapter#getView() method, I have the following code:

private class SearchAdapter extends ArrayAdapter<Result>
{
    // ...

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

        // ...

        if (position == getCount() - 2)
            getLoaderManager().getLoader(SEARCH_LOADER_ID).onContentChanged();
        // ...
    }
}

I still would like to know if this is the "best practice" way of doing it, but it seems to solve my problem for now.

Chilopod answered 6/6, 2013 at 18:52 Comment(3)
how do you getLoaderManager() from your Adapter?Impostume
My Adapter is an inner class inside my FragmentChilopod
The best solutions are the simplestCosenza
T
0

In your scenario I will recommend you to use ForceLoadContentObserver which you can bind with a URI to the ContentResolver. Like this:

class SearchLoader ....
     ForceLoadContentObserver contentObserver = new ForceLoadContentObserver();
     ....

     @Override
     public void onStartLoading() {
        if (cacheResult == null || takeContentChanged()) { // This will see if there's a change notification to observer and take it.
            onForceLoad();
        } else {
            deliverResult(cacheResult);
        }
     }

     @Override
     public Result loadInBackground() {
        Result result = loadResult();
        // notification uri built upon Result.BASE_URI so it receives all notifications to BASE_URI.
       getContext().getContentResolver().registerContentObserver(result.getNotificationUri(), true, contentObserver);
     }

     @Override
     public void onReset() {
        // ... Do your clean stuff...
        getContext().getContentResolver().unregisterContentObserver(contentObserver);
     }

     ...
}

So you can notify your change everywhere by using:

context.getContentResolver().notifyChanged(Result.BASE_URI, null);

Even when the activity holding the loader is in the background or not possible to deliverResult. And you don't need to get loaders' instances.

All the notification is around Uri of object. Uri is a powerful representation of data in Android.

But I do have my confusion as well. In this scenario, both your approach and my approach assumes a content changes means loading more data. But what if you do need to reload all the data? What kind of notification you will use?

Tortola answered 10/9, 2014 at 9:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.