29

Background:

I have a custom CursorLoader that works directly with SQLite Database instead of using a ContentProvider. This loader works with a ListFragment backed by a CursorAdapter. So far so good.

To simplify things, lets assume there is a Delete button on the UI. When user clicks this, I delete a row from the DB, and also call onContentChanged() on my loader. Also, on onLoadFinished() callback, I call notifyDatasetChanged() on my adapter so as to refresh the UI.

Problem:

When the delete commands happen in rapid succession, meaning the onContentChanged() is called in rapid succession, bindView() ends up to be working with stale data. What this means is a row has been deleted, but the ListView is still attempting to display that row. This leads to Cursor exceptions.

What am I doing wrong?

Code:

This is a custom CursorLoader (based on this advice by Ms. Diane Hackborn)

/**
 * An implementation of CursorLoader that works directly with SQLite database
 * cursors, and does not require a ContentProvider.
 * 
 */
public class VideoSqliteCursorLoader extends CursorLoader {

    /*
     * This field is private in the parent class. Hence, redefining it here.
     */
    ForceLoadContentObserver mObserver;

    public VideoSqliteCursorLoader(Context context) {
        super(context);
        mObserver = new ForceLoadContentObserver();

    }

    public VideoSqliteCursorLoader(Context context, Uri uri,
            String[] projection, String selection, String[] selectionArgs,
            String sortOrder) {
        super(context, uri, projection, selection, selectionArgs, sortOrder);
        mObserver = new ForceLoadContentObserver();

    }

    /*
     * Main logic to load data in the background. Parent class uses a
     * ContentProvider to do this. We use DbManager instead.
     * 
     * (non-Javadoc)
     * 
     * @see android.support.v4.content.CursorLoader#loadInBackground()
     */
    @Override
    public Cursor loadInBackground() {
        Cursor cursor = AppGlobals.INSTANCE.getDbManager().getAllCameras();
        if (cursor != null) {
            // Ensure the cursor window is filled
            int count = cursor.getCount();
            registerObserver(cursor, mObserver);
        }

        return cursor;

    }

    /*
     * This mirrors the registerContentObserver method from the parent class. We
     * cannot use that method directly since it is not visible here.
     * 
     * Hence we just copy over the implementation from the parent class and
     * rename the method.
     */
    void registerObserver(Cursor cursor, ContentObserver observer) {
        cursor.registerContentObserver(mObserver);
    }    
}

A snippet from my ListFragment class that shows the LoaderManager callbacks; as well as a refresh() method that I call whenever user adds/deletes a record.

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


    /*
     * Initialize the Loader
     */
    mLoader = getLoaderManager().initLoader(LOADER_ID, null, this);
}

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

@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {

    mAdapter.swapCursor(data);
    mAdapter.notifyDataSetChanged();
}

@Override
public void onLoaderReset(Loader<Cursor> loader) {
    mAdapter.swapCursor(null);
}

public void refresh() {     
    mLoader.onContentChanged();
}

My CursorAdapter is just a regular one with newView() being over-ridden to return newly inflated row layout XML and bindView() using the Cursor to bind columns to Views in the row layout.


EDIT 1

After digging into this a bit, I think the fundamental issue here is the way the CursorAdapter handles the underlying Cursor. I'm trying to understand how that works.

Take the following scenario for better understanding.

  1. Suppose the CursorLoader has finished loading and it returns a Cursor that now has 5 rows.
  2. The Adapter starts displaying these rows. It moves the Cursor to the next position and calls getView()
  3. At this point, even as the list view is in the process of being rendered, a row (say, with _id = 2) is deleted from the database.
  4. This is where the issue is - The CursorAdapter has moved the Cursor to a position which corresponds to a deleted row. The bindView() method still tries to access the columns for this row using this Cursor, which is invalid and we get exceptions.

Question:

  • Is this understanding correct? I am particularly interested in point 4 above where I am making the assumption that when a row gets deleted, the Cursor doesn't get refreshed unless I ask for it to be.
  • Assuming this is right, how do I ask my CursorAdapter to discard/abort its rendering of the ListView even as it is in progress and ask it to use the fresh Cursor (returned through Loader#onContentChanged() and Adapter#notifyDatasetChanged()) instead?

P.S. Question to moderators: Should this edit be moved to a separate question?


EDIT 2

Based on suggestion from various answers, it looks like there was a fundamental mistake in my understanding of how Loaders work. It turns out that:

  1. The Fragment or Adapter should not be directly operating on the Loader at all.
  2. The Loader should monitor for all changes in data and should just give the Adapter the new Cursor in onLoadFinished() whenever data changes.

Armed with this understanding, I attempted the following changes. - No operation on the Loader whatsoever. The refresh method does nothing now.

Also, to debug what's going on inside the Loader and the ContentObserver, I came up with this:

public class VideoSqliteCursorLoader extends CursorLoader {

    private static final String LOG_TAG = "CursorLoader";
    //protected Cursor mCursor;

    public final class CustomForceLoadContentObserver extends ContentObserver {
        private final String LOG_TAG = "ContentObserver";
        public CustomForceLoadContentObserver() {
            super(new Handler());
        }

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

        @Override
        public void onChange(boolean selfChange) {
            Utils.logDebug(LOG_TAG, "onChange called; selfChange = "+selfChange);
            onContentChanged();
        }
    }

    /*
     * This field is private in the parent class. Hence, redefining it here.
     */
    CustomForceLoadContentObserver mObserver;

    public VideoSqliteCursorLoader(Context context) {
        super(context);
        mObserver = new CustomForceLoadContentObserver();

    }

    /*
     * Main logic to load data in the background. Parent class uses a
     * ContentProvider to do this. We use DbManager instead.
     * 
     * (non-Javadoc)
     * 
     * @see android.support.v4.content.CursorLoader#loadInBackground()
     */
    @Override
    public Cursor loadInBackground() {
        Utils.logDebug(LOG_TAG, "loadInBackground called");
        Cursor cursor = AppGlobals.INSTANCE.getDbManager().getAllCameras();
        //mCursor = AppGlobals.INSTANCE.getDbManager().getAllCameras();
        if (cursor != null) {
            // Ensure the cursor window is filled
            int count = cursor.getCount();
            Utils.logDebug(LOG_TAG, "Count = " + count);
            registerObserver(cursor, mObserver);
        }

        return cursor;

    }

    /*
     * This mirrors the registerContentObserver method from the parent class. We
     * cannot use that method directly since it is not visible here.
     * 
     * Hence we just copy over the implementation from the parent class and
     * rename the method.
     */
    void registerObserver(Cursor cursor, ContentObserver observer) {
        cursor.registerContentObserver(mObserver);
    }

    /*
     * A bunch of methods being overridden just for debugging purpose.
     * We simply include a logging statement and call through to super implementation
     * 
     */

    @Override
    public void forceLoad() {
        Utils.logDebug(LOG_TAG, "forceLoad called");
        super.forceLoad();
    }

    @Override
    protected void onForceLoad() {
        Utils.logDebug(LOG_TAG, "onForceLoad called");
        super.onForceLoad();
    }

    @Override
    public void onContentChanged() {
        Utils.logDebug(LOG_TAG, "onContentChanged called");
        super.onContentChanged();
    }
}

And here are snippets of my Fragment and LoaderCallback

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


    /*
     * Initialize the Loader
     */
    getLoaderManager().initLoader(LOADER_ID, null, this);
}

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

@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
    Utils.logDebug(LOG_TAG, "onLoadFinished()");
    mAdapter.swapCursor(data);
}

@Override
public void onLoaderReset(Loader<Cursor> loader) {
    mAdapter.swapCursor(null);
}

public void refresh() {
    Utils.logDebug(LOG_TAG, "CamerasListFragment.refresh() called");
    //mLoader.onContentChanged();
}

Now, whenever there is a change in the DB (row added/deleted), the onChange() method of the ContentObserver should be called - correct? I don't see this happening. My ListView never shows any change. The only time I see any change is if I explicitly call onContentChanged() on the Loader.

What's going wrong here?


EDIT 3

Ok, so I re-wrote my Loader to extend directly from AsyncTaskLoader. I still don't see my DB changes being refreshed, nor the onContentChanged() method of my Loader being called when I insert/delete a row in the DB :-(

Just to clarify a few things:

  1. I used the code for CursorLoader and just modified one single line that returns the Cursor. Here, I replaced the call to ContentProvider with my DbManager code (which in turn uses DatabaseHelper to perform a query and return the Cursor).

    Cursor cursor = AppGlobals.INSTANCE.getDbManager().getAllCameras();

  2. My inserts/updates/deletes on the database happen from elsewhere and not through the Loader. In most cases the DB operations are happening in a background Service, and in a couple of cases, from an Activity. I directly use my DbManager class to perform these operations.

What I still don't get is - who tells my Loader that a row has been added/deleted/modified? In other words, where is ForceLoadContentObserver#onChange() called? In my Loader, I register my observer on the Cursor:

void registerContentObserver(Cursor cursor, ContentObserver observer) {
    cursor.registerContentObserver(mObserver);
}

This would imply that the onus is on the Cursor to notify mObserver when it has changed. But, then AFAIK, a 'Cursor' is not a "live" object that updates the data it is pointing to as and when data is modified in the DB.

Here's the latest iteration of my Loader:

import android.content.Context;
import android.database.ContentObserver;
import android.database.Cursor;
import android.support.v4.content.AsyncTaskLoader;

public class VideoSqliteCursorLoader extends AsyncTaskLoader<Cursor> {
    private static final String LOG_TAG = "CursorLoader";
    final ForceLoadContentObserver mObserver;

    Cursor mCursor;

    /* Runs on a worker thread */
    @Override
    public Cursor loadInBackground() {
        Utils.logDebug(LOG_TAG , "loadInBackground()");
        Cursor cursor = AppGlobals.INSTANCE.getDbManager().getAllCameras();
        if (cursor != null) {
            // Ensure the cursor window is filled
            int count = cursor.getCount();
            Utils.logDebug(LOG_TAG , "Cursor count = "+count);
            registerContentObserver(cursor, mObserver);
        }
        return cursor;
    }

    void registerContentObserver(Cursor cursor, ContentObserver observer) {
        cursor.registerContentObserver(mObserver);
    }

    /* Runs on the UI thread */
    @Override
    public void deliverResult(Cursor cursor) {
        Utils.logDebug(LOG_TAG, "deliverResult()");
        if (isReset()) {
            // An async query came in while the loader is stopped
            if (cursor != null) {
                cursor.close();
            }
            return;
        }
        Cursor oldCursor = mCursor;
        mCursor = cursor;

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

        if (oldCursor != null && oldCursor != cursor && !oldCursor.isClosed()) {
            oldCursor.close();
        }
    }

    /**
     * Creates an empty CursorLoader.
     */
    public VideoSqliteCursorLoader(Context context) {
        super(context);
        mObserver = new ForceLoadContentObserver();
    }

    @Override
    protected void onStartLoading() {
        Utils.logDebug(LOG_TAG, "onStartLoading()");
        if (mCursor != null) {
            deliverResult(mCursor);
        }
        if (takeContentChanged() || mCursor == null) {
            forceLoad();
        }
    }

    /**
     * Must be called from the UI thread
     */
    @Override
    protected void onStopLoading() {
        Utils.logDebug(LOG_TAG, "onStopLoading()");
        // Attempt to cancel the current load task if possible.
        cancelLoad();
    }

    @Override
    public void onCanceled(Cursor cursor) {
        Utils.logDebug(LOG_TAG, "onCanceled()");
        if (cursor != null && !cursor.isClosed()) {
            cursor.close();
        }
    }

    @Override
    protected void onReset() {
        Utils.logDebug(LOG_TAG, "onReset()");
        super.onReset();

        // Ensure the loader is stopped
        onStopLoading();

        if (mCursor != null && !mCursor.isClosed()) {
            mCursor.close();
        }
        mCursor = null;
    }

    @Override
    public void onContentChanged() {
        Utils.logDebug(LOG_TAG, "onContentChanged()");
        super.onContentChanged();
    }

}
curioustechizen
  • 10,572
  • 10
  • 61
  • 110
  • Why don't you keep it simple: delete the UI element and db row manually instead of loading the cursor on every delete.. – Ron Jul 20 '12 at 14:16
  • Actually the scenario I presented here is a very simplified form of what my app actually does. Having said that, how can I selectively delete a single row in a `ListView` without going through the Adapter? – curioustechizen Jul 20 '12 at 17:04
  • I think synchronization is a better solution. try my answer.. – Ron Jul 20 '12 at 17:38
  • Just to be clear... you are being consistent in your usage of the support/non-support versions of the `LoaderManager`, correct? It looks like you are using the `support.v4.content.CursorLoader` with the `android.content.LoaderManager`... should you be calling `getSupportLoaderManager()` instead? Not saying that is the problem... but still. – Alex Lockwood Jul 23 '12 at 15:29
  • Also, you are passing `0` as the `int flag` argument to your `CursorAdapter`, correct? (And the edit you've added to the question is totally fine, IMO). – Alex Lockwood Jul 23 '12 at 15:33
  • It does look like I'm using `getLoaderManager`, but that's probably because I extend `SherlockListFragment` which I doesn't seem to have a `getSupportLoaderManager()`. The int flag to CursorAdapter is 0. – curioustechizen Jul 24 '12 at 05:44

5 Answers5

13

I'm not 100% sure based on the code you've provided, but a couple things stick out:

  1. The first thing that sticks out is that you've included this method in your ListFragment:

     public void refresh() {     
         mLoader.onContentChanged();
     }
    

    When using the LoaderManager, it's rarely necessary (and is often dangerous) to manipulate your Loader directly. After the first call to initLoader, the LoaderManager has total control over the Loader and will "manage" it by calling its methods in the background. You have to be very careful when calling the Loaders methods directly in this case, as it could interfere with the underlying management of the Loader. I can't say for sure that your calls to onContentChanged() are incorrect, since you don't mention it in your post, but it should not be necessary in your situation (and neither should holding a reference to mLoader). Your ListFragment does not care how changes are detected... nor does it care how data is loaded. All it knows is that new data will magically be provided in onLoadFinished when it is available.

  2. You also shouldn't call mAdapter.notifyDataSetChanged() in onLoadFinished. swapCursor will do this for you.

For the most part, the Loader framework should do all of the complicated things involving loading data and managing the Cursors. Your ListFragment code should be simple in comparison.


##Edit #1:

From what I can tell, the CursorLoader relies on the ForceLoadContentObserver (a nested inner class provided in the Loader<D> implementation)... so it would seem that the problem here is that you are implementing your on custom ContentObserver, but nothing is set up to recognize it. A lot of the "self-notification" stuff is done in the Loader<D> and AsyncTaskLoader<D> implementation and is thus hidden away from the concrete Loaders (such as CursorLoader) that do the actual work (i.e. Loader<D> has no idea about CustomForceLoadContentObserver, so why should it ever receive any notifications?).

You mentioned in your updated post that you can't access final ForceLoadContentObserver mObserver; directly, since it is a hidden field. Your fix was to implement your own custom ContentObserver and call registerObserver() in your overriden loadInBackground method (which will cause registerContentObserver to be called on your Cursor). This is why you aren't getting notifications... because you have used a custom ContentObserver that is never recognized by the Loader framework.

To fix the issue, you should have your class directly extend AsyncTaskLoader<Cursor> instead of CursorLoader (i.e. just copy and paste the parts that you are inheriting from CursorLoader into your class). This way you won't run into any issues with thie hidden ForceLoadContentObserver field.

Edit #2:

According to Commonsware, there isn't an easy way to set up global notifications coming from an SQLiteDatabase, which is why the SQLiteCursorLoader in his Loaderex library relies on the Loader calling onContentChanged() on itself each time a transaction is made. The easiest way to broadcast notifications straight from the data source is to implement a ContentProvider and use a CursorLoader. This way you can trust that notifications will be broadcasted to your CursorLoader each time your Service updates the underlying data source.

I don't doubt that there are other solutions (i.e. perhaps by setting up a global ContentObserver... or maybe even by using the ContentResolver#notifyChange method without a ContentProvider), but the cleanest and simplest solution seems to be to just implement a private ContentProvider.

(p.s. make sure you set android:export="false" in the provider tag in your manifest so your ContentProvider can't be seen by other apps! :p)

General Grievance
  • 4,555
  • 31
  • 31
  • 45
Alex Lockwood
  • 83,063
  • 39
  • 206
  • 250
  • I agree with not holding the reference to your Loader. All I've ever done is just rebind with is. – Frank Sposaro Jul 24 '12 at 04:32
  • I don't get it. You mean if the underlying data changes, I need to do _nothing_ for the `ListView` to be refreshed? The `refresh()` method is not even needed? Also, I haven't over-ridden the `onContentChanged()` in my `Loader` implementation. What I have pasted above is the complete implementation of my Loader really. – curioustechizen Jul 24 '12 at 06:03
  • I edited the question with even more details and results of my experiment with the `CursorLoader` and the `LoaderCallbacks`. – curioustechizen Jul 24 '12 at 07:40
  • @curioustechizen (1) When the underlying data changes, the `Loader` should recognize the change and should fire up a new asynchronous query. (2) When the query is completed, the fresh, updated data will eventually find its way to [`onLoadComplete`](http://bit.ly/MFm6Ex). (3) It is in `onLoadComplete` where `onLoadFinished` is finally called. `onLoadFinished` is the place where you *know* for sure that it is safe to use the new data, so this is where you will usually update your UI with the new `Cursor` (i.e. by swapping the new `Cursor` into the adapter). – Alex Lockwood Jul 24 '12 at 13:31
  • (4) You swap the new cursor into the adapter with `swapCursor(cursor)`. This method will also call `notifyDataSetChanged()` on your adapter (as stated in my initial post), so you don't have to worry about notifying the adapter that changes have been made. – Alex Lockwood Jul 24 '12 at 13:33
  • So to answer your question, no... you shouldn't need to ever refresh the `ListView`'s data. Instead, you (as the client) should assume that data will magically be returned to `onLoadFinished` and update your UI from there. This reduces code complexity by a significant amount... none of the "refreshing your UI" code should be in your `Activity` and/or `Fragment`. That being said, this is based 100% on the assumption that your `Loader` is implemented correctly. If your `Loader` does not know how to respond to content changes correctly, then of course it will not work. – Alex Lockwood Jul 24 '12 at 13:37
  • @curioustechizen yeah, I see the problem now. See my updated post. – Alex Lockwood Jul 24 '12 at 14:05
  • Thanks for the detailed explanation and thanks for pointing out the `ForceLoadContentObserver` problem. In my original question, you'd see that I don't define a custom ContentObserver; I just re-define the `mObserver` field. This is also a mistake as I now realize. I'll try out your suggestion of inheriting `AsyncTaskLoader` instead of `CursorLoader`. – curioustechizen Jul 24 '12 at 14:08
  • The "custom" class I was referring to was `CustomForceLoadContentObserver`... it is "custom" in the sense that it doesn't have type `Loader.ForceLoadContentObserver`, which is required if you want your `Loader`'s subclass to be able to register content observers on the cursor and receive notifications when the content is changed. – Alex Lockwood Jul 24 '12 at 14:44
  • @AlexLockwood If you see the _original_ question (not the Edit 2), you'll see that I did not use a `CustomForceLoadContentObserver`. I just _re-defined the field_ there since it was not visible :-) . Anyway, I've implemented my loader as an extension of `AsyncTaskLoader` and I'm playing with it. Will report back my findings. – curioustechizen Jul 25 '12 at 05:10
  • @AlexLockwood Updated the question with Edit3 - more findings, more questions, still no luck :-(. BTW really appreciate your help here. – curioustechizen Jul 25 '12 at 08:51
  • @curioustechizen arg, sorry for making you change that w/o any luck. Upon further investigation, it looks like Mark Murphy actually does call `onContentChanged()` directly on the Loader. See his [`SQLiteCursorLoader`](http://bit.ly/OhZ1pp) implementation... the `ExecSQLTask` extends `ContentChangingTask`, which calls `onContentChanged()` each time an SQL transaction is performed, and this seems like it gets rid of the idea of `ContentObserver`s all together. With a `ContentProvider`, this would be easy... you'd just have to make sure you call `notifyChange()` on the `ContentResolver` and – Alex Lockwood Jul 25 '12 at 13:46
  • @curioustechizen the `ContentObserver`s are magically notified... I'm running to work right now and didn't have enough time to tie the two together by glancing at the source code, but I would still think that the best solution would be to encapsulate the whole idea of content changes *within the SQLite database itself*... that way, the `Loader` doesnt have to do extra work by calling `onContentChanged()` on itself each time a transaction occurs. It also means that your `Loader` will continue to get notified when you perform raw inserts/updates/deletes from elsewhere in your application. – Alex Lockwood Jul 25 '12 at 13:55
  • @curioustechizen Basically, we just need to find the equivalent `notifyChange()` method for an `SQLiteDatabase`... I'll continue to look into it and report back. At least we are both learning something :) – Alex Lockwood Jul 25 '12 at 14:00
  • ContentProvider, setNotificationUri() and notifyChanges() are usable for a limited nuimber of cases. It is easy to find a simple use case where it fails miserably. See: http://stackoverflow.com/questions/32741634/how-to-make-notifychange-work-between-two-activities – f470071 Sep 23 '15 at 20:40
3

This isn't really a solution for your problem, but it might still be of some use for you:

There is a method CursorLoader.setUpdateThrottle(long delayMS), which enforces a minimum time between loadInBackground completing, and the next load being scheduled.

Marcus Forsell Stahre
  • 3,766
  • 1
  • 17
  • 14
  • +1 for pointing this out. It still doesn't solve my problem - because as I have found out (and as I shall soon describe in an edit to the original question), this doesn't guarantee that the `loadInBackground()` will not be called even as a `listView` has not completed rendering all its rows. – curioustechizen Jul 23 '12 at 08:34
  • Alex: That's why I said it isn't really a solution. Maybe I should have posted it as a comment instead? – Marcus Forsell Stahre Jul 24 '12 at 10:52
2

Alternative:

I feel using a CursoLoader is too heavy for this task. What needs to be in sync is the database add/delete, and it can be done in a synchronized method. As I said in a earlier comment, when a mDNS service stops, remove from it db(in synced manner), send remove broadcast, in receiver: remove from data-holder list and notify. That should be sufficient. Just to avoid using an extra arraylist(for backing the adapter), using a CursorLoader is extra-work.


You should do some synchronization on the ListFragment object.

The call to notifyDatasetChanged() should be synchronized.

synchronized(this) {  // this is ListFragment or ListView.
     notifyDatasetChanged();
}
Community
  • 1
  • 1
Ron
  • 24,175
  • 8
  • 56
  • 97
  • When I saw this answer, I thought this is it! However, for some reason I just cannot fathom, in spite of trying synchronization on variety of code blocks, this issue still persists. I tried synchronizing the `onContentChanged()`, the `notifyDataSetChanged()`, and I tried synchronizing on various objects - the `ListView`, the Loader, the `ListFragment`, the `Adapter`. Still no use. When I simulate five simultaneous deletions, I invariably run into a case where the `Cursor` in `bindView()` is working with a row that was deleted after the listView started rendering its rows. – curioustechizen Jul 23 '12 at 08:32
  • Is there any way you can stop current loading? – Ron Jul 23 '12 at 10:05
  • I edited the question with a more details. In fact, I think the ideal approach in such cases would be to batch delete and call `notifyDatasetChanged()` once rather than several times. But I still don't see a way of achieving that. I'm looking at `cancelLoad()` and how it can be used to stop loading. – curioustechizen Jul 23 '12 at 10:13
  • Batch processing is one solution. But when do you decide to run the batch? – Ron Jul 23 '12 at 10:58
  • How about making `loadinBackground()` method as `synchronised`.? – Ron Jul 23 '12 at 10:59
  • "But when do you decide to run the batch?" - Exactly why I didn't attempt it :). Reg synchronizing `loadInBackground` - isn't that equivalent to synchronizing `onContentChanged()`? I think what does need to be synchronized is the `Cursor` that is being displayed by the `CursorAdapter` so as to prevent any changes to the underlying data **while the `ListView` is being rendered**, i.e., for the duration the iteration over the `Cursor` is happening. I just don't know how to do that. – curioustechizen Jul 23 '12 at 11:05
  • I don't understand... how and where is `notifyDatasetChanged()` being called from separate threads? AFAIK `onLoadFinished` is always called on the UI thread (with a `CursorLoader` at least). Either way, this wouldn't be a good solution since it could block the main UI thread. – Alex Lockwood Jul 23 '12 at 15:42
  • If you are open to UI changes to get rid of this problem..then have a single delete button and multiple choice list.. so select multiple items and tap delete ... show a progress dialog. This will be like batch delete, as u had thought earlier.. – Ron Jul 24 '12 at 04:08
  • @userSeven7s how does that "solve" the problem... that sounds more like "hiding" the problem... – Alex Lockwood Jul 24 '12 at 04:38
  • @userSeven7s Like i mentioned earlier, the "delete button" was a simplification. In reality, I populate the list based on services discovered via mDNS. As and when these services disappear, I need to update the list. – curioustechizen Jul 24 '12 at 05:46
  • @curioustechizen Ok.. There should have been some mention of this in your question. In such case, you should keep it simple. When a service goes down, remove those items from UI(from Adapter and call notify), then in background thread remove from DB. This background thread should be synchronised. CursorLoader is simply making it too complex. – Ron Jul 24 '12 at 06:56
  • @userSeven7s Sorry about leaving out the mDNS part- was trying to keep the question focused. Will keep this in mind. How do I remove those items from the `Adapter`? Also, if I don't use `CursorLoader`, won't I have to worry about orientation changes? Finally - my current design is the other way round: I have a `Service` which registers for an ordered broadcast for mDNS services. Whenever there is a change (new service discovered/existing one disappears) the `Service` deletes the row from the DB. The broadcast then reaches the `Activity` where I attempt to refresh the UI. – curioustechizen Jul 24 '12 at 07:19
  • what exactly you do in your broadcast receiver? – Ron Jul 24 '12 at 08:03
  • @userSeven7s the problem is that `onContentChanged()` isn't being called (see the updated post). The `Service` and `BroadcastReceiver` shouldn't have to do with it. – Alex Lockwood Jul 24 '12 at 14:46
  • @AlexLockwood Yes, too many things have changed in this thread in 3-4 days. I feel using a `CursoLoader` is too heavy for this task. What needs to be in sync is the database add/delete, and it can be done in a `synchronized` method. As I said in a earlier comment, when a mDNS service stops, remove from it db(in synced manner), send remove broadcast, in receiver: remove from data-holder list and notify. That should be sufficient. Just to avoid using an extra arraylist(for backing the adapter), using a `CursorLoader` is extra-work. Rest left to the original poster. – Ron Jul 24 '12 at 15:19
  • 2
    @userSeven7s The `CursorLoader` only cares about the SQLite database ... it doesn't know how the data is being inserted/deleted, nor does it care. Using `Loader`s is the easiest and most convenient way to load data from an SQLite database. IIRC, to prevent "database locked" exceptions all you really need to do is use [a singleton `SQLiteDatabase` instance to perform your database accesses](http://stackoverflow.com/a/3689883/844882), which you could easily retrieve and use in the `Service`. – Alex Lockwood Jul 24 '12 at 15:46
  • 1
    I'm using `SQLiteOpenHelper` which has synchronized `getReadableDatabase()` and `getWriteableDatabase()` methods. I call the appropriate `get*Database()` method before each and every SQLite operation. – curioustechizen Jul 25 '12 at 08:57
2

I read your entire thread as I was having the same issue, the following statement is what resolved this issue for me:

getLoaderManager().restartLoader(0, null, this);

Dilip Manek
  • 9,095
  • 5
  • 44
  • 56
marty331
  • 423
  • 5
  • 12
  • Where do you call `restartLoader()`? I'm pretty sure I had tried this as well. I'd appreciate if you could post some code of how you resolved the issue. Its been almost a year since this thread was opened; and as you can see there have been several edits and I'm lost! In particular I'm interested in knowing if you were able to get it to work without Custom `ForceLoadObservers` etc .. – curioustechizen Jun 06 '13 at 05:54
  • I am calling restartLoader() in my onResume(). – marty331 Aug 30 '13 at 19:02
0

A had the same problem. I solved it by:

@Override
public void onResume() {
    super.onResume();  // Always call the superclass method first
    if (some_condition) {
        getSupportLoaderManager().getLoader(LOADER_ID).onContentChanged();
    }
}
Ruslan Mansurov
  • 1,281
  • 16
  • 23