Search code examples
androidandroid-contentproviderandroid-contentresolverandroid-cursorloaderandroid-loadermanager

Load data from db using content provider


I am using content providers to store and retrieve data from Database. This is what happening in the app when user install the app.

  1. App start a service which will download data from server and store it in db
  2. App is using CursorLoader to load data from db

Problem is first time there wouldn't be any data in db until service download the data and store it. When that happens then CursorLoader doesn't load data from db. It does load data from db if i close and reopen the app.

This is how i am using ContentProvider

@Override
public int bulkInsert(Uri uri, ContentValues[] values) {
    final SQLiteDatabase db = feederDbHelper.getWritableDatabase();
    int match = sUriMatcher.match(uri);
    switch (match) {
        case CODE_INSERT_SOURCE:
            //Return the number of rows inserted from our implementation of bulkInsert
            return insertRecords(FeederContract.SourceEntry.TABLE_NAME, db, values, uri);
        case CODE_INSERT_ARTICLE:
            return insertRecords(FeederContract.ArticleEntry.TABLE_NAME, db, values, uri);
        default:
            return super.bulkInsert(uri, values);
    }
}

Here is the insertRecords method

public int insertRecords(String tabbleName, SQLiteDatabase db, ContentValues[] values, Uri uri) {
        db.beginTransaction();
        int rowsInserted = 0;
        try {
            for (ContentValues value : values) {
                long _id = db.insertWithOnConflict(tabbleName, null, value, SQLiteDatabase.CONFLICT_REPLACE);
                if (_id != -1) {
                    rowsInserted++;
                }
            }
            db.setTransactionSuccessful();
        } finally {
            db.endTransaction();
        }

        if (rowsInserted > 0) {
            getContext().getContentResolver().notifyChange(uri, null);
        }
        //Return the number of rows inserted from our implementation of bulkInsert
        return rowsInserted;
    }

Here is what is happening when user start app first time. I have added comments as well.

@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.feeds_fragment, container, false);
    ButterKnife.bind(this, view);
    loadAd();
    initRc();
    // Starting service to download feeds
    FeederSyncUtil.startSync(getActivity());
    // Starting loader to load feeds
    getActivity().getSupportLoaderManager().initLoader(ID_FEEDS_LOADER, null, this); 
    return view;
}

these are the loader callbacks

@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
    return new CursorLoader(getActivity(),
            FeederContract.ArticleEntry.CONTENT_URI,
            MAIN_FEED_PROJECTION,
            null,
            null,
            null);
}

@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
    Log.d(TAG, data.getCount() + "");
    mFeedsAdapter.swapCursor(data);
}

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

What am i missing here? Even though i am using getContext().getContentResolver().notifyChange(uri, null) but still it doesn't refresh the Cursor Some guidance would be appreciated.


Solution

  • Err! There was something that i was missing. I spend hours on this problem looking everywhere then i went into documentation and i found this method

    /**
     * Register to watch a content URI for changes. This can be the URI of a specific data row (for 
     * example, "content://my_provider_type/23"), or a a generic URI for a content type.
     * 
     * @param cr The content resolver from the caller's context. The listener attached to 
     * this resolver will be notified.
     * @param uri The content URI to watch.
     */
    void setNotificationUri(ContentResolver cr, Uri uri);
    

    So then i added hookup a call for this method inside the query method of my provider. Here is some snippet from my provider

     public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
            int match = sUriMatcher.match(uri);
            Cursor cursor = null;
            switch (match) {
                case CODE_INSERT_SOURCE:
                    cursor = feederDbHelper.getReadableDatabase().query(FeederContract.SourceEntry.TABLE_NAME,
                            projection,
                            selection,
                            selectionArgs,
                            null,
                            null,
                            sortOrder);
                    break;
                case CODE_VIEW_ARTICLE:
    
                    cursor = feederDbHelper.getReadableDatabase().query(FeederContract.ArticleEntry.TABLE_NAME,
                            projection,
                            selection,
                            selectionArgs,
                            null,
                            null,
                            sortOrder);
                    break;
    
                default:
                    throw new UnsupportedOperationException("Unknown uri " + uri);
            }
            cursor.setNotificationUri(getContext().getContentResolver(),uri);
            return cursor;
        }
    

    So this line cursor.setNotificationUri(getContext().getContentResolver(),uri); does all the magic. When there is any change in URI underlying data using insert(),delete(),update(), bulkInsert() it notify the contentresolver about the change. Since CursorLoader doesn't automatically detect data changes, we need to use setNotificationUri(ContentResolver cr, Uri uri);

    I hope this will help someone. :)