0

I have an application which is using Loaders to get at a database which is also being edited by an IntentService. I receive data from the Loader through a LoaderCallbacks implementation, which is working fine.

I am also using ContentResolver#notifyChange(Uri, ContentObserver) to trigger reload. However, this only works when I call Cursor#setNotificationUri(Uri) beforehand.

I can find no reference to the latter method in any documentation and it seems in fact this may be causing crashes: see also

IllegalStateException "attempt to re-open an already-closed object" in SimpleCursorAdapter from ContentProvider

However, without this call on the Cursor the LoaderCallbacks#onLoadFinished(Loader<Cursor>, Cursor) is only hit after the initial load, and not after the notification. Do I also need to implement an OnLoadCompleteListener to do, well, exactly the same thing?

ContentProvider query method:

class MyContentProvider extends ContentProvider {
//...

    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
            String[] selectionArgs, String sortOrder) {
        SQLiteDatabase db = mOpenHelper.getReadableDatabase();
        Cursor query = db.query(getTableName(uri), projection, selection, selectionArgs, null, null, sortOrder);
        query.setNotificationUri(getContext().getContentResolver(), uri);
        return query;
    }

//...
}

Typical LoaderCallbacks:

LoaderCallbacks<Cursor> mCallbacks = new LoaderCallbacks<Cursor>() {

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

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
        if(cursor.isClosed()) {
            Log.d(TAG, "CURSOR RETURNED CLOSED");
            Activity activity = getActivity();
            if(activity!=null) {
                activity.getLoaderManager().restartLoader(mFragmentId, null, mCallbacks);
            }
            return;
        }
        mArticleAdapter.swapCursor(cursor);
    }

    @Override
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
        triggerArticleFeed();
        CursorLoader cursorLoader = null;

        if(id == mFragmentId) {
            cursorLoader = new CursorLoader(getActivity(),
                                            MyContentProvider.ARTICLES_URI,
                                            null,
                                            ArticlesContentHelper.ARTICLES_WHERE,
                                            ArticlesContentHelper.ARTICLES_WHEREARGS,
                                            null);
        }
        return(cursorLoader);
    }
};
Community
  • 1
  • 1
Andrew Wyld
  • 7,133
  • 7
  • 54
  • 96
  • can you post onLoadFinished method ? – Moh Sakkijha Feb 19 '13 at 17:20
  • Is it your own content provider or some other content provider? – ebarrenechea Feb 19 '13 at 17:21
  • @Saksak I've put those in. – Andrew Wyld Feb 19 '13 at 17:22
  • @ebarrenchea it's our `ContentProvider`. – Andrew Wyld Feb 19 '13 at 17:22
  • Do you know if it's necessary to implement both callbacks? – Andrew Wyld Feb 19 '13 at 17:24
  • It's not. calling setNotificationUri along with the normal Loader callbacks is enough. – dymmeh Feb 19 '13 at 17:25
  • this exception occur when you explicitly close the cursor , can you post your Adapter check if you are doing anything to the cursor in it ? – Moh Sakkijha Feb 19 '13 at 17:26
  • @dymmeh OK ... in that case have you any idea what's causing this? http://stackoverflow.com/questions/14956608/illegalstateexception-attempt-to-re-open-an-already-closed-object-in-simplecur – Andrew Wyld Feb 19 '13 at 17:26
  • @Saksak I don't close the cursor *anywhere*. It's the first thing I checked. – Andrew Wyld Feb 19 '13 at 17:27
  • @AndrewWyld - you are destroying the loader manually.. why? This is pointless since it's automatically managed. – dymmeh Feb 19 '13 at 17:28
  • @dymmeh that was something I tried in order to fix the crash; this app is single-orientation only except in one `Activity` and we're implementing `onConfigurationChanged` in that. I'll get rid of that though and see if it makes a difference. – Andrew Wyld Feb 19 '13 at 17:30
  • @AndrewWyld - what does triggerArticleFeed(); do? – dymmeh Feb 19 '13 at 17:31
  • @AndrewWyld can you try and remove the check cursor.isClosed() ? comment it and just call the swap – Moh Sakkijha Feb 19 '13 at 17:33
  • @Saksak again, this is something I added to try and fix the problem based on http://stackoverflow.com/questions/11999098/illegalstateexception-support-loadermanager-with-autocompletetextview the bug was present before. – Andrew Wyld Feb 19 '13 at 17:34
  • @dymmeh it calls the `IntentService` that repopulates the database. `private void triggerArticleFeed() { Context context = getActivity(); if(context == null) return; Intent intent = new Intent(context, FeedIntentService.class); intent.setAction(MyContentProvider.ARTICLES_URI.toString()); context.startService(intent); }` – Andrew Wyld Feb 19 '13 at 17:39
  • I just noticed you are passing `null` for your projection when you create your cursor loader. I'm not sure what would happen in that case, but maybe that is the cause of your problems. – ebarrenechea Feb 19 '13 at 17:43
  • @ebarrenchea the google example is done with a null `Bundle`: http://developer.android.com/guide/components/loaders.html so I doubt it's the problem. – Andrew Wyld Feb 19 '13 at 17:45
  • @ebarrenchea - null projection just means you return all columns available. Not efficient but won't cause any crashes. – dymmeh Feb 19 '13 at 17:47
  • @AndrewWyld - Can you show your adapter creation? If you are using the deprecated SimpleCursoradapter constructor you will run into this problem. – dymmeh Feb 19 '13 at 17:50
  • @dymmeh sure, but shall I do it on the other question? – Andrew Wyld Feb 19 '13 at 17:51
  • @AndrewWyld - sure. that will be better. I'll monitor that one – dymmeh Feb 19 '13 at 17:52
  • @dymmeh done. It's the bottom code snippet. – Andrew Wyld Feb 19 '13 at 17:52

1 Answers1

1

Implementing both listeners is a very bad idea:

02-19 17:46:25.139: E/AndroidRuntime(24886): FATAL EXCEPTION: main
02-19 17:46:25.139: E/AndroidRuntime(24886): java.lang.IllegalStateException: There is already a listener registered
02-19 17:46:25.139: E/AndroidRuntime(24886):    at android.content.Loader.registerListener(Loader.java:152)
02-19 17:46:25.139: E/AndroidRuntime(24886):    at android.app.LoaderManagerImpl$LoaderInfo.start(LoaderManager.java:273)
02-19 17:46:25.139: E/AndroidRuntime(24886):    at android.app.LoaderManagerImpl.installLoader(LoaderManager.java:523)
02-19 17:46:25.139: E/AndroidRuntime(24886):    at android.app.LoaderManagerImpl.createAndInstallLoader(LoaderManager.java:510)
02-19 17:46:25.139: E/AndroidRuntime(24886):    at android.app.LoaderManagerImpl.initLoader(LoaderManager.java:563)

So in fact the answer to this question is:

It is necessary NOT to implement both listeners.

Andrew Wyld
  • 7,133
  • 7
  • 54
  • 96