Cursorloader not refreshing when underlying data changes

I have a content provider, a content resolver, and a cursor loader. The loader is used to indirectly populate a listview (ie not a simple cursor adapter, rather an array adapter, since I need to use the cursor's results to gather other data).

When I change the underlying data, the listview does not re-populate as the onLoadFinished(Loader<Cursor>, Cursor) call back is not called.

As suggested while I'm writing this, there are a lot of questions on this issue. eg CursorLoader not updating after data change

And all the questions point out two things:

  • In your content provider query() method, you need to tell the cursor about notifications a'la
  • c.setNotificationUri(getContext().getContentResolver(), uri);

  • In your content provider insert/update/delete method, you need to notify on the URI:
  • getContext().getContentResolver().notifyChange(uri, null);

    I'm doing those things.

    I'm also NOT closing any of the cursors I get back.

    Note, my content provider lives in a separate app (think of it as a content provider app -- no launcher main activity). A com.example.provider APK, and the com.example.app is calling (in the content resolver) via the content://com.example.provider/table URI etc. The content resolver (dbhelper) lives in a library project (com.example.appdb) that the activity links in. (This way, multiple projects can use the dbhelper via linking, and all content providers are installed via single APK)

    I have turned on debugging in the loader manager, and can see where I force a refresh after the data changes (ie the loader being restarted and previous being marked inactive), but nothing that says anything is happening automatically -- rather, in response to my force refresh.

    Any ideas why my loader isn't being refreshed ?

    -- EDIT --

    The loader create:

    Log.d(TAG, "onCreateLoader ID: " + loaderID);
    // typically a switch on the loader ID, and then 
    return dbHelper.getfoo()
    

    Where getfoo() returns a cursor loader via:

    return new CursorLoader(context, FOO_URI, foo_Fields, foo_query, foo_argArray, foo_sort );

    Loader Finish takes the cursor and populates a table (and does some processing) -- nothing fancy there.

    public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
        Log.d(TAG, "onLoadFinished id: " + loader.getId());
        // switch on loader ID .. 
        FillTable(cursor, (FooAdapter) foo_info.listview.getAdapter();
    

    Loader Reset clears the table.

    public void onLoaderReset(Loader<Cursor> loader) {
        Log.d(TAG, "onLoaderReset id: " + loader.getId());
        // normally a switch on loader ID
        ((FooAdapter)foo_info.listview.getAdapter()).clear();
    

    The content provider does:

    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { Cursor cursor; SQLiteQueryBuilder qb = new SQLiteQueryBuilder();

        SQLiteDatabase db = dbHelper.getReadableDatabase();
    
        switch (sUriMatcher.match(uri)) {
        case FOO:
            qb.setTables(FOO);
            qb.setProjectionMap(FooProjectionMap);
            break; 
        default:
            throw new IllegalArgumentException("Unknown URI " + uri);
        }
    
        Cursor c = qb.query(db, projection, selection, selectionArgs, null, null, sortOrder);
    
        c.setNotificationUri(getContext().getContentResolver(), uri);
        return c;
    

    }

    Then insert/update is similar:

    public Uri insert(Uri uri, ContentValues initialValues) {
        ContentValues values;
        if (initialValues != null) {
            values = new ContentValues(initialValues);
        } else {
            values = new ContentValues();
        }
    
        String tableName;
        Uri contentUri;
    
        switch (sUriMatcher.match(uri)) {
        case FOO:
            tableName = FOOTABLE;
            contentUri = FOO_URI;
            break;
    
        default:
            throw new IllegalArgumentException("Unknown URI " + uri);
        }
    
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        long rowId = db.insert(tableName, null, values);
        if (rowId > 0) {
            Uri objUri = ContentUris.withAppendedId(contentUri, rowId);
            getContext().getContentResolver().notifyChange(objUri, null);
            return objUri;
        }
    
        throw new SQLException("Failed to insert row into " + uri);
    }
    

    I see that you aren't calling swapCursor or changeCursor in your onLoadFinished and onLoaderReset. You need to do that for your adapter to access the data loaded into the new cursor.

    in onLoadFinished, call something like:

    mAdapter.swapCursor(cursor)
    

    In onLoaderReset, call this to remove references to the old cursor:

    mAdapter.swapCursor(null)
    

    Where mAdapter is your listView's adapter.

    More info: http://developer.android.com/guide/components/loaders.html#onLoadFinished


    So, for anyone who looks this up and has this problem:

    Make certain that the URI you register the cursor on, and the URI you notify the cursor on are the same.

    In my case, I was using a different URI to query than was used in other code that modified the underlying table. There wasn't an easy fix to make notification work, so I kept resetting the loader in OnResume() as the solution.


    This is not a direct answer to this specific problem, but may help others in a similar situation. In my case, I had a join and even though I used the full tablename.columnname in the projection and query. It ended up using the wrong ID values anyway. So be sure to check your ContentProvider or SQL syntax.

    链接地址: http://www.djcxy.com/p/31994.html

    上一篇: urimatcher无法使用预填充的数据库

    下一篇: 当底层数据发生变化时,Cursorloader不会刷新