Hopes Hopes - 22 days ago 12
Android Question

notifying async task loader of data changes

I have tried all i could for the past five days on how to get AsyncTask Loader to get notified of data changes using content provider but no luck and all the answers provided in stackoverflow are not getting me anyway.
I really need your help.

I am using AsyncTaskLoader with content provider to retrieve data from database, add it to my array-list and update my fragment using recyclerview adapter. New data are downloaded from server asynchronously by sync adapter and stored in the database. Normally, i will expect that the AsyncTask Loader should get notified immediately when new data are inserted into the database, but ive tried all possible ways yet takeContentChanged() method responsible for this is never called and new data cannot be updated.

see my implementation below:

public class NewsListLoader extends AsyncTaskLoader<List<News>> {
private static final String LOG_TAG = NewsListLoader.class.getSimpleName();
private List<News> mNews;
private ContentResolver mContentResolver;
private Cursor mCursor;
private Uri mUri;
private String mSortOrder;

public NewsListLoader(Context context, Uri uri, String sortOrder, ContentResolver contentResolver) {
super(context);
mContentResolver = contentResolver;
mUri = uri;
mSortOrder = sortOrder;

}

@Override
public List<News> loadInBackground() {
String[] projection = {NewsContract.NewsEntry.NEWS_TABLE_NAME
+ "." + NewsContract.NewsEntry._ID,
NewsContract.NewsEntry.NEWS_TITLE_COLUMN,
NewsContract.NewsEntry.NEWS_CATEGORY_COLUMN,
NewsContract.NewsEntry.NEWS_IMAGEURL_COLUMN,
NewsContract.NewsEntry.NEWS_URL_COLUMN,
NewsContract.NewsEntry.NEWS_DATE_COLUMN,
NewsContract.NewsEntry.NEWS_AUTHOR_COLUMN};
// NewsContract.SourceEntry.SOURCE_COLUMN};

List<News> entries = new ArrayList<News>();




mCursor = mContentResolver.query(NewsContract.NewsEntry.NEWS_TABLE_CONTENT_URI,
projection, null, null, mSortOrder);

if (mCursor != null){
if (mCursor.moveToFirst()){
do{
//int _id = mCursor.getInt(mCursor.getColumnIndex(NewsContract.NewsEntry._ID));
/*String source = mCursor.getString(
mCursor.getColumnIndex(NewsContract.SourceEntry.SOURCE_COLUMN));*/
String source = "mySource";
String title = mCursor.getString(
mCursor.getColumnIndex(NewsContract.NewsEntry.NEWS_TITLE_COLUMN));
String category = mCursor.getString(
mCursor.getColumnIndex(NewsContract.NewsEntry.NEWS_CATEGORY_COLUMN));
String imageUrl = mCursor.getString(
mCursor.getColumnIndex(NewsContract.NewsEntry.NEWS_IMAGEURL_COLUMN));
String url = mCursor.getString(
mCursor.getColumnIndex(NewsContract.NewsEntry.NEWS_URL_COLUMN));
String date = mCursor.getString(
mCursor.getColumnIndex(NewsContract.NewsEntry.NEWS_DATE_COLUMN));
String author = mCursor.getString(
mCursor.getColumnIndex(NewsContract.NewsEntry.NEWS_AUTHOR_COLUMN));


News news = new News(source,title, category, imageUrl, url, date, author);
entries.add(news);
}while (mCursor.moveToNext());
}
} else {Log.d(LOG_TAG, "Cursor is null");

//mCursor.setNotificationUri(mContentResolver, mUri);
}

return entries;

}

@Override
public void deliverResult(List<News> news) {
if (isReset()){
if (news != null){
mCursor.close();
}
}

List<News> oldNews = mNews;
if (mNews == null || mNews.size() == 0){
Log.d(LOG_TAG, "+++++++ No data returned");

}

mNews = news;
if (isStarted()){
super.deliverResult(news);
}

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

@Override
protected void onStartLoading() {
if (mNews != null){
deliverResult(mNews);
}

if (takeContentChanged() || mNews == null){
// here this forceLoad() is called only when mNews is null and never
// takeContentChanged.
// the ui gets refresh only when i restart the app
forceLoad();

}
}

@Override
protected void onStopLoading() {
cancelLoad();
}

@Override
protected void onReset() {
onStopLoading();
if (mCursor != null){
mCursor.close();
}

mNews = null;
}

@Override
public void onCanceled(List<News> news) {
super.onCanceled(news);
if (mCursor != null){
mCursor.close();
}
}

@Override
public void forceLoad() {
super.forceLoad();
}


public void deleteOldItems (int num){

String where = NewsContract.NewsEntry._ID +
" IN (SELECT " + NewsContract.NewsEntry._ID + " FROM " +
NewsContract.NewsEntry.NEWS_TABLE_NAME +
" ORDER BY " + NewsContract.NewsEntry._ID + " DESC " + " LIMIT ?)";
String limit = "2, 10";
String[] selectionArgs = new String[]{limit};

mContentResolver.delete(
NewsContract.NewsEntry.NEWS_TABLE_CONTENT_URI, where, selectionArgs );



}


}

content provider code snipplet

@Override
public int bulkInsert(Uri uri, ContentValues[] values) {
final SQLiteDatabase db = mHelper.getWritableDatabase();
final int match = sUriMatcher.match(uri);
Log.d(LOG_TAG, "Insert Uri " + uri);
switch (match) {
case NEWS:

/**
* Begins a transaction in "exclusive" mode. No other mutations can occur on the
* db until this transaction finishes.*/

db.beginTransaction();
int returnCount = 0;
try {
for (ContentValues value : values) {
long _id = db.insert(NewsContract.NewsEntry.NEWS_TABLE_NAME, null, value);
if (_id != -1) {
returnCount++;
}
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}

getContext().getContentResolver().notifyChange(uri, null);

//Log.d(LOG_TAG, "Insert Count is " + returnCount);
return returnCount;
default:
return super.bulkInsert(uri, values);
}
}

@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
// Here's the switch statement that, given a URI, will determine what kind of request it is,
// and query the database accordingly.
Cursor retCursor;
Log.d(LOG_TAG, "Match Uri is " + uri);
Log.d(LOG_TAG, "Matcher is " + sUriMatcher.match(uri));


switch (sUriMatcher.match(uri)) {

case NEWS: {

//retCursor = getNewsWithAuthor( uri,projection, sortOrder);
//String author = NewsContract.NewsEntry.getAuthorFromUri(uri);
//Log.d(LOG_TAG, "URI for News is "+ uri);
//selection = sAuthorSlection;
retCursor = mHelper.getReadableDatabase().query(
NewsContract.NewsEntry.NEWS_TABLE_NAME,
projection,
selection,
selectionArgs,
null,
null,
sortOrder
);
break;

}


case NEWS_WITH_SOURCE: {

retCursor = getNewsSourceAndCategory(uri, projection, sortOrder);
break;
}

case NEWS_WITH_CATEGORY: {

retCursor = getNewsWithCategory(uri, projection, sortOrder);
break;
}


case NEWS_WITH_SOURCE_AND_CATEGORY:{
Log.d(LOG_TAG, "Provider Uri is "+ uri);
retCursor = getNewsSourceAndCategory(uri, projection, sortOrder);
break;
}



// "news/*/*/*
case NEWS_WITH_SOURCE_DATE_AND_CATEGORY: {
// Log.d(LOG_TAG, "Provider Uri is "+ uri);
retCursor = getNewsWithSourceDateAndCategory(uri, projection, sortOrder);

break;
}



case SOURCE: {
retCursor = mHelper.getReadableDatabase().query(
NewsContract.SourceEntry.NEWS_SOURCE_TABLE_NAME,
projection,
selection,
selectionArgs,
null,
null,
null,
sortOrder
);
break;
}
// "source/*"
case SOURCE_ID: {

retCursor = mHelper.getReadableDatabase().query(
NewsContract.SourceEntry.NEWS_SOURCE_TABLE_NAME,
projection,
NewsContract.SourceEntry._ID + " = '" + ContentUris.parseId(uri) + "'",
null,
null,
null,
sortOrder
);
break;
}
// "source"


default:
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
retCursor.setNotificationUri(getContext().getContentResolver(), uri);

return retCursor;
}


fragment that implement LoaderCallback

public class RecyclerViewFragment extends Fragment implements LoaderManager.LoaderCallbacks<List<News>> {
private static final String LOG_TAG = RecyclerViewFragment.class.getSimpleName();

private RecyclerView mRecyclerView;
private NewsRecyclerViewAdapter mAdapter;
private List<News> mNews;
private static final int LOADER_ID = 0;
private ContentResolver mContentResolver;
private Object mSyncObserverHandle;
Menu mOptionsMenu;



private String[] mUris;



public static RecyclerViewFragment newInstance() {
return new RecyclerViewFragment();
}






@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

return inflater.inflate(R.layout.fragment_recyclerview, container, false);



}



@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);

mRecyclerView = (RecyclerView) view.findViewById(R.id.recyclerView);
RecyclerView.LayoutManager layoutManager;

mRecyclerView.setLayoutManager(layoutManager);
mRecyclerView.setHasFixedSize(false);

mRecyclerView.addItemDecoration(new MaterialViewPagerHeaderDecorator());




mAdapter = new NewsRecyclerViewAdapter(getActivity(),new ArrayList<News>());
mRecyclerView.setAdapter(mAdapter);


...


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


mContentResolver = getActivity().getContentResolver();
getLoaderManager().initLoader(LOADER_ID, null,this);






}

@Override
public void onAttach(Context context) {
super.onAttach(context);
....



}

@Override
public void onResume() {
super.onResume();
...
}

@Override
public void onPause() {
super.onPause();
if (mSyncObserverHandle != null) {
ContentResolver.removeStatusChangeListener(mSyncObserverHandle);
mSyncObserverHandle = null;
}
}

@Override
public Loader<List<News>> onCreateLoader(int id, Bundle args) {
mContentResolver = getActivity().getContentResolver();
String source = "techcrunch";
String category = null;
String author = "Anna Escher";
String sortOrder = NewsContract.NewsEntry.NEWS_DATE_COLUMN + " DESC";
return new NewsListLoader(getActivity(), NewsContract.NewsEntry
.buildNewsSource(source), sortOrder, mContentResolver);
}

@Override
public void onLoadFinished(Loader<List<News>> loader, List<News> news) {

mAdapter.loadData(news);
mNews = news;

mAdapter.notifyDataSetChanged();
//mNews = news;

}


@Override
public void onLoaderReset(Loader<List<News>> loader) {
mAdapter.loadData(null);
}


Recyclerview adapter code

@Override
public View_Holder onCreateViewHolder(ViewGroup parent, int viewType) {

View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.list_item_card_big, parent, false);
return new View_Holder(view);



}

@Override
public void onBindViewHolder(View_Holder holder, int position) {

List<News> newsItem = mNewsItems;
Log.d(LOG_TAG, "Processing "+ newsItem.get(position).getTitle() + " -->" + Integer.toString(position));

String title = newsItem.get(position).getTitle();
holder.title.setText(title);
String author = newsItem.get(position).getAuthor();
holder.author.setText(author);
String source = (newsItem.get(position).getSource());
holder.source.setText(source);
String date = Utility.getDayName(mContext,newsItem.get(position).getPublishedAt());
holder.publisedAt.setText(date);

Picasso.with(mContext).load(newsItem.get(position).getImageToUrl())
.fit()
.error(R.drawable.placeholder)
.placeholder(R.drawable.placeholder)
.into(holder.imageView);


}
@Override
public int getItemCount() {

return (null != mNewsItems ? mNewsItems.size(): 0);
}

public void loadData(List<News> newNews){
mNewsItems.clear();
if (newNews != null){
mNewsItems.addAll(newNews);

notifyDataSetChanged();
}



}

Answer

You need to register a ContentObserver on the Cursor and override onChange in that to call onContentChanged in the AsyncTaskLoader.

If you use CursorLoader you get that capability already baked in.

Comments