Question

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;
}
Was it helpful?

Solution

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.

OTHER TIPS

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?

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top