How can I update an adapter of an Activity from other Activity? - android

Problem: Activity A is a ListView that contains a ListAdapter, and clicking in any item of the adapter leads to activity B. Activity B has a button that fetches a new item (or several) from the web (using an AsyncTask) and adds it to the list displayed by activity A when pressed. That operation from B is not blocked by a ProgressDialog, so the user can move back to A before the AsyncTask that B started finishes fetching the data.
So I need a way of updating the adapter of A from B.
I have a class C with static data displayed in the ListView by A. When the button at B is pressed, it adds that value to C. That class also has the adapter from A as a static field, but I think that this leaks the memory from the Context, and that is bad. My first idea of fixing this was removing the static adapter from C and every time A onResume() (and if the data on the adapter is different from what I have at C), I load the data from C again into the adapter and notifyDatasetChanged(). Well, it works most of the time, but if the user goes back to A from B before B fetches the data from the web, then the adapter does not update, since the onResume() came before the data is fetched.
Question: Is there a better way of updating the adapter of A from B?

Don't save static references to the adapter. It will indeed leak memory and behave badly.
It appears I misunderstood the first time. Here is an updated answer:
First solution
The prettiest solution is to implement a content provider for the data storage and query that content provider in both A and B. Update the data in B using contentProvider.insert()
and read the data using contentProvider.query() returning for example a SQLiteCursor if it is backed by database or a MatrixCursor if you just save it in memory in the content provider.
The basic steps (without CursorLoader):
In onCreate of A you register yourself as a contentobserver using ContentResolver.registerContentObserver(uri, true, this) where uri is an URI using some scheme you set.
In onCreate of A you get the data by querying the contentprovider using ContentResolver.query(uri, projection, selection, selectionArgs, sortOrder) where projection, selection, selectionArgs and sortOrder can be what fits your contentprovider (maybe null). Uri refers to the data you want to query and is your choice.
When data is loaded in B you call ContentResolver.insert(). In insert of your contentprovider you call ContentResolver.notifyChange (Uri uri, null, null) where uri is the URI you used in step 1.
Implement onChange(boolean selfChange) in A and requery the content provider when it is called.
Note that you will not need to call registerContentObserver at all if you use CursorLoaders!
It will receive the new cursor in the loader since it has automatic requery when you notify change.
Second solution
A less pretty solution is to implement a singleton object that handles the data.
Something like:
Implement the class
public class DataHolder {
private static DataHolder sDataHolder;
private String data = ""; // Data represented by string.
public DataHolder getInstance() {
if (sDataHolder == null) {
sDataHolder = new DataHolder()
}
return sDataHolder;
}
private DataHolder() {} // Hidden constructor
public void setData(final String data) {
mData = data;
for (DataListener listener: mDataListeners) {
listener.onDataChanged(mData);
}
}
public void registerListener(DataListener listener) {
mDataListeners.add(listener);
}
public String unregisterListener(DataListener listener) {
mDataListeners.remove(listener);
}
public String getData() {
return mData;
}
public static interface DataListener {
public void onDataChanged(String data);
}
}
Make A implement DataListener
Read and update the data in onStart() of DataListener to make sure that it is set if the change was done when B was alive using DataHolder.getInstance().getData().
Register listener in A's onCreate/onStart using DataHolder.getInstance().registerListener(this); Let the listener update the data.
Unregister listener in A's onDestroy/onStop using DataHolder.getInstance().unregisterListener(this)
Set the data and signal any listener in B using DataHolder.getInstance().setData(data)
Also note that you can make the second solution fully thread safe by changing void registerListener() to synchronized String registerListenerAndGetValue() if you also make setValue synchronized.
Old answer based on a misunderstanding
My old answer for general result handling did not quite answer the question, but it was:
If you want to send data back to an activity A you should do the following:
Always start B with startActivityForResult (Intent intent, int requestCode)
Set the result when done in B using setResult (int resultCode)
Handle the result when you come back to A by implementing onActivityResult (int requestCode, int resultCode, Intent data)
As an addition you could add so you only fetch data in A the first time by doing it only if savedInstanceState == null in for example onCreate().

try calling mListView.invalidate();
**java-doc
Invalidate the whole view. If the view is visible, onDraw(android.graphics.Canvas) will be called at some point in the future. This must be called from a UI thread. To call from a non-UI thread, call postInvalidate().

Related

Refresh Recyclerview from Broadcast receiver

I have a notification which contains a next button and a previous button. Now when I press any of the buttons in the notification the actions are received by the Broadcast receiver.
What my problem is that I am having trouble refreshing my recyclerview when either of the buttons are pressed.
I have been able to do it if I would set my adapter to static in the main class and call it in the broadcast receiver it works like this:
BroadCast Receiver Class:
public void getPlaylistItems(){
if (list != null){
list.clear();
}
SongsDatabase SongsDB = new SongsDatabase(contexts);
Cursor data = SongsDB .getSongs();
if(data.getCount() != 0){
data.moveToFirst();
do{
Songs myList = new (data.getString(0), data.getString(1), data.getString(2), data.getString(3), data.getString(4));
list.add(myList);
} while (data.moveToNext());
}
data.close();
VideoDB.close();
adapter.notifyDataSetChanged <--- This was made static in my Main Activity which contains the recyclerview
}
The problem with this solution is that it causes a memory leak in my app.
Now the question is how would I be able to refresh my recyclerview without making any variables static?
I believe you are creating an inner class inside your activity/fragment. To avoid making adapter static, you can create this broadcast receiver inside on your onCreate function instead.
Other approaches could be using an Event Bus,Interfaces or RxJava.
Yes you can without having a static reference to the adapter of your RecyclerView. You have the ContentObserver for that.
As I can see that, you are fetching the songs and the videos from the local database, you can easily put a content observer along with your database table. In that case, you need to create an Uri first which might look something like this.
public static final Uri DB_TABLE_SONGS_URI = Uri
.parse("sqlite://" + Constants.ApplicationPackage + "/" + DB_TABLE_SONGS);
// DB_TABLE_SONGS refers to the database table that you have for storing songs.
Now, while updating or inserting a new row in the songs table you need to notify the content observer like this.
context.getContentResolver().notifyChange(DBConstants.DB_TABLE_SONGS_URI, null);
Now use LoaderCallbacks and LoaderManager to fetch the data from songs table. Hence, in onCreateLoader function you need to register the content observer which will aid to recycle your RecyclerView without having any static reference to it.
This is a sample onCreateLoader function.
#Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
return new SQLiteCursorLoader(getActivity()) {
#Override
public Cursor loadInBackground() {
SongsDatabase SongsDB = new SongsDatabase(contexts);
cursor = SongsDB.getSongs();
this.registerContentObserver(cursor, DBConstants.DB_TABLE_SONGS_URI);
}
return cursor;
}
};
}
Do not forget to destroy the loader instance in onDestroy function.
getLoaderManager().destroyLoader(SONGS_QUERY_LOADER);

How to make notifyChange() work between two activities?

I have an activity ActitvityA that holds a listview populated by a CursorLoader. I want to switch to ActivityB and change some data and see those changes reflected in listview in ActivityA.
public class ActivityA implements LoaderManager.LoaderCallbacks<Cursor>
{
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_a);
getSupportLoaderManager().initLoader(LOADER_ID, null, this);
mCursorAdapter = new MyCursorAdapter(
this,
R.layout.my_list_item,
null,
0 );
}
.
.
.
/** Implementation of LoaderManager.LoaderCallbacks<Cursor> methods */
#Override
public Loader<Cursor> onCreateLoader(int loaderId, Bundle arg1) {
CursorLoader result;
switch ( loaderId ) {
case LOADER_ID:
/* Rename v _id is required for adapter to work */
/* Use of builtin ROWID http://www.sqlite.org/autoinc.html */
String[] projection = {
DBHelper.COLUMN_ID + " AS _id", //http://www.sqlite.org/autoinc.html
DBHelper.COLUMN_NAME // columns in select
}
result = new CursorLoader( ActivityA.this,
MyContentProvider.CONTENT_URI,
projection,
null,
new String[] {},
DBHelper.COLUMN_NAME + " ASC");
break;
default: throw new IllegalArgumentException("Loader id has an unexpectd value.");
}
return result;
}
/** Implementation of LoaderManager.LoaderCallbacks<Cursor> methods */
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
switch (loader.getId()) {
case LOADER_ID:
mCursorAdapter.swapCursor(cursor);
break;
default: throw new IllegalArgumentException("Loader has an unexpected id.");
}
}
.
.
.
}
From ActivityA I switch to ActivityB where I change the underlying data.
// insert record into table TABLE_NAME
ContentValues values = new ContentValues();
values.put(DBHelper.COLUMN_NAME, someValue);
context.getContentResolver().insert( MyContentProvider.CONTENT_URI, values);
The details of MyContentProvider:
public class MyContentProvider extends ContentProvider {
.
.
.
#Override
public Uri insert(Uri uri, ContentValues values) {
int uriCode = sURIMatcher.match(uri);
SQLiteDatabase database = DBHelper.getInstance().getWritableDatabase();
long id = 0;
switch (uriType) {
case URI_CODE:
id = database.insertWithOnConflict(DBHelper.TABLE_FAVORITE, null, values,SQLiteDatabase.CONFLICT_REPLACE);
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
getContext().getContentResolver().notifyChange(uri, null); // I call the notifyChange with correct uri
return ContentUris.withAppendedId(uri, id);
}
#Override
public Cursor query(Uri uri,
String[] projection,
String selection,
String[] selectionArgs,
String sortOrder) {
// Using SQLiteQueryBuilder instead of query() method
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
int uriCode = sURIMatcher.match(uri);
switch (uriCode) {
case URI_CODE:
// Set the table
queryBuilder.setTables(DBHelper.TABLE_NAME);
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
SQLiteDatabase database = DBHelper.getInstance().getWritableDatabase();
Cursor cursor = queryBuilder.query( database, projection, selection, selectionArgs, null, null, sortOrder);
// Make sure that potential listeners are getting notified
cursor.setNotificationUri(getContext().getContentResolver(), uri);
return cursor;
}
}
As far as my knowledge goes this should be sufficient. But it does not work. Upon returning to ActivityA the listview is unchanged.
I have followed things with debugger and this is what happens.
First visit ActivityA, the methods that are called in that order
MyContentProvider.query()
ActivityA.onLoadFinished()
The listview displays the correct values.
And now I switch to activityB and change the data
MyContentProvider.insert() // this one calls getContext().getContentResolver().notifyChange(uri, null);
MyContentProvider.query()
//As we can see the MyContentProvider.query is executed. I guess in response to notifyChange().
// What I found puzzling why now, when ActivityB is still active ?
Return to ActivityA
!!! ActivityA.onLoadFinished() is not called
I have read anything I could about this, took a close look at a lot of stackoverflow questions yet all those question/answers revolve around setNotificationUri() and notifyChangeCombo() which I implemented. Why does this not work across activities?
If for example force refresh in ActivityA.onResume() with
getContentResolver().notifyChange(MyContentProvider.CONTENT_URI, null, false);
then it refreshes the list view. But that would force refresh on every resume regardless if data was changed or not.
After a two long two days of scratching my head and altruistic engagement from pskink I painted myself a picture of what was wrong.
My ActivityA is in a reality a lot more complicated. It uses ViewPager with PagerAdapter with instantiates listviews.
At first I created those components in onCreate() method something like this:
#Override
public void onCreate(Bundle savedInstanceState)
{
...
super.onCreate(savedInstanceState);
// 1 .ViewPager
viewPager = (ViewPager) findViewById(R.id.viewPager);
...
viewPager.setAdapter( new MyPagerAdapter() );
viewPager.setOnPageChangeListener(this); */
...
// 2. Loader
getSupportLoaderManager().initLoader(LOADER_ID, null, this);
...
// 3. CursorAdapter
myCursorAdapter = new MyCursorAdapter(
this,
R.layout.list_item_favorites_history,
null,
0);
}
Somewhere along the line I noticed that this is wrong order of creating. Why it didn't produce some error is because PagerAdapter.instantiateItem() is called aftter onCreate() finishes. I dont know why or how this caused the original problem. Maybe something did not wire correctly with listviews, adapters and content observers. I didn't dig into that.
I changed the order to:
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
...
// 1. CursorAdapter
myCursorAdapter = new MyCursorAdapter(
this,
R.layout.list_item_favorites_history,
null,
0);
...
// 2. Loader
getSupportLoaderManager().initLoader(LOADER_ID, null, this);
...
// 3 .ViewPager
viewPager = (ViewPager) findViewById(R.id.viewPager);
...
viewPager.setAdapter( new MyPagerAdapter() );
viewPager.setOnPageChangeListener(this); */
...
}
This magically made it work in about 75% of the cases. When I studied CatLog output I noticed that ActivityA().onStop() is called at different times. When it works it is called late and I can see in logcat that onLoadFinished() executes. Sometimes ActivityA.onStop() executes right after query and then onLoadFinished() is not called at all. This brings me to what DeeV jas posted in his answer about cursors being unregistered from ContentResolver. This just might be the case.
What made things to somehow came to light was the fact that simple demonstrator pskink insisted on did work and my app didn't although they were identical in key points. This brought my attention to asynchronous things and my onCreate() method. In reality my ActivityB is complicated so it gives enough time for ActivityA to stop.
What I noticed also (and this did make things more difficult to sort) was that if I run my 75% version in debug mode (with no breakpoints) then the success rate falls to 0. ActivityA is stopped before cursor load finishes so my onLoadFinished() is never called and listviews are never updated.
Two key points:
Somehow the order of creation od ViewPager, CursorAdapter and
CursorLoader is important
ActivityA may be (and is) stopped before
cursor is loaded.
But even this is not. If I take a look at a sequence of simplified then I see that ActivityA.onStop() is executed before content provider inserts a record. I see no query while ActivityB is active. But when i return to ActivityA a query is execeuted laodFinished() follows and listview is refreshed. Not so in my app. It always executes a query while still in ActivityB, why??? This destroy my theory about onStop() being the culprit.
(Big thanks to pskink and DeeV)
UPDATE
After a lot of waisted time on this issue I finally nailed the cause of the problem.
Short description:
I have the following classes:
ActivityA - contains a list view populated via cursor loader.
ActivityB - that changes data in database
ContentProvider - content provider used for data manipulation and also used by cursorloader.
The problem:
After data manipulation in ActivityB the changes are not shown in list view in ActivityA. List view is not refreshed.
After I lot of eyeballing and studying logcat traces I have seen that things proceed in following sequence:
ActivityA is started
ActivityA.onCreate()
-> getSupportLoaderManager().initLoader(LOADER_ID, null, this);
ContentProvider.query(uri) // query is executes as it should
ActivityA.onLoadFinished() // in this event handler we change cursor in list view adapter and listview is populated
ActivityA starts ActivityB
ActivityA.startActivity(intent)
ActivityB.onCreate()
-> ContentProvider.insert(uri) // data is changed in the onCreate() method. Retrieved over internet and written into DB.
-> getContext().getContentResolver().notifyChange(uri, null); // notify observers
ContentProvider.query(uri)
/* We can see that a query in content provider is executed.
This is WRONG in my case. The only cursor for this uri is cursor in cursor loader of ActivityA.
But ActivityA is not visible any more, so there is no need for it's observer to observe. */
ActivityA.onStop()
/* !!! Only now is this event executed. That means that ActivityA was stopped only now.
This also means (I guess) that all the loader/loading of ActivityA in progress were stopped.
We can also see that ActivityA.onLoadFinished() was not called, so the listview was never updated.
Note that ActivityA was not destroyed. What is causing Activity to be stopped so late I do not know.*/
ActivityB finishes and we return to ActivityA
ActivityA.onResume()
/* No ContentProvider.query() is executed because we have cursor has already consumed
notification while ActivityB was visible and ActivityA was not yet stopped.
Because there is no query() there is no onLoadFinished() execution and no data is updated in listview */
So the problem is not that ActivityA is stopped to soon but that it is stopped to late. The data is updated and notification
sent somewhere between creation of ActivityB and stopping of ActivityA.
The solution is to force loader in ActivityA to stop loading just before ActivityB is started.
ActivityA.getSupportLoaderManager().getLoader(LOADER_ID).stopLoading(); // <- THIS IS THE KEY
ActivityA.startActivity(intent)
This stops the loader and (I guess again) prevents cursor to consume notification while activity is in the above described limbo state.
The sequence of events now is:
ActivityA is started
ActivityA.onCreate()
-> getSupportLoaderManager().initLoader(LOADER_ID, null, this);
ContentProvider.query(uri) // query is executes as it should
ActivityA.onLoadFinished() // in this event handler we change cursor in list view adapter and listview is populated
ActivityA starts ActivityB
ActivityA.getSupportLoaderManager().getLoader(LOADER_ID).stopLoading();
ActivityA.startActivity(intent)
ActivityB.onCreate()
-> ContentProvider.insert(uri)
-> getContext().getContentResolver().notifyChange(uri, null); // notify observers
/* No ContentProvider.query(uri) is executed, because we have stopped the loader in ActivityA. */
ActivityA.onStop()
/* This event is still executed late. But we have stopped the loader so it didn't consume notification. */
ActivityB finishes and we return to ActivityA
ActivityA.onResume()
ContentProvider.query(uri) // query is executes as it should
ActivityA.onLoadFinished() // in this event handler we change cursor in list view adapter and listview is populated
/* The listview is now populated with up to date data */
This was the most elegant solution I could find. No need to restart loaders and such.
But still I would like to hear a comment on that subject from someone with a deeper insight.
I don't see anything here particularly wrong with this. As long as the Cursor is registered with the URI, the loader should be restarting itself with new information. I don't think the issue here is anything wrong with your code. I think it's the LoaderManager is unregistering the Cursor from the ContentResolver too early (it actually happens by the time onStop() is called).
Basically there's nothing you can really do about it unregistering. You can however, force restart the loader by calling LoaderManager#restartLoader(int, Bundle, LoaderCallbacks);. You can call this in onStart() (which makes the initLoader call in onCreate() useless). A more optimized approach would be to use onActivityResult(). The result of your activity is irrelevant in this case. All you're saying is that you've returned to this activity from some other activity and the data may or may not be different, so you need to reload.
protected void onActivityResult(int requestCode, int resultCode,
Intent data) {
getSupportLoaderManager().restartLoader(LOADER_ID, null, this);
}
Then just call Context#startActivityForResult() when opening new Activities.

How to update the parent activity's listview on child activity return

I have two activities: one NoteListActivity which inherits from ListActivity, and I used SimpleCursorAdapter as its adapter where the cursor is obtained as below:
public Cursor getAllNotesCursor() {
String selectQuery = "SELECT _id , title, content FROM " + NOTE_TABLE_NAME;
SQLiteDatabase db = this.getReadableDatabase();
Cursor cursor = db.rawQuery(selectQuery, null);
return cursor;
}
The another activity NoteEditorActivity is responsible for creating new note, there is a save action there and on click I will add a new note in the SQLite database then call finish to the NoteListActivity.
The problem is that the NoteListActivity didn't get updated with new note, do you know the best practice to achieve this?
One solution I can thought of is starting NoteEditorActivity by calling startActivityForResults then call cursor requery in onActivityResult, I don't know whether there is better solution?
startActivityForResults is good, but why not try to override onResume() method, with yourAdapter.notifyDataChange()
#Override
public void onResume() {
...
yourAdapter.notifyDataSetChanged();
}
Of course you have to add yourAdapter on your field class.
Whatever you are doing in onCreate method that is affecting UI to draw or show note by fetching from database.
Don't do it in onCreate.
DO IT IN onResume
#Override
public void onResume(){
//fetch here, do other operation, or set layout here
}
notifyDataSetChanged Update List View Adopter
http://androidadapternotifiydatasetchanged.blogspot.in/
try following steps..
use startActivityForResult() inside NoteListActivity to start NoteEditorActivity.
set RESULT_OK in save button click event
Populate list in onActivityResult() of NoteListActivity

How do CursorLoader automatically updates the view even if the app is inactive?

I have been working on a small To-Do list app. I used CursorLoader to update the ToDolistview from a content provider. I have a written a function onNewItemAdded(), which is called when user enters a new item in the text view and clicks enter. Refer below:
public void onNewItemAdded(String newItem) {
ContentResolver cr = getContentResolver();
ContentValues values = new ContentValues();
values.put(ToDoContentProvider.KEY_TASK, newItem);
cr.insert(ToDoContentProvider.CONTENT_URI, values);
// getLoaderManager().restartLoader(0, null, this); // commented for the sake of testing
}
#Override
protected void onResume() {
super.onResume();
//getLoaderManager().restartLoader(0, null, this); // commented for the sake of testing
}
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
CursorLoader loader = new CursorLoader(this,
ToDoContentProvider.CONTENT_URI, null, null, null, null);
Log.e("GOPAL", "In the onCreateLoader");
return loader;
}
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
int keyTaskIndex = cursor.getColumnIndexOrThrow(ToDoContentProvider.KEY_TASK);
Log.e("GOPAL", "In the onLoadFinished");
todoItems.clear();
if (cursor.moveToNext() == false) Log.e("GOPAL", "Empty Cursor");
else {
while (cursor.moveToNext()) {
ToDoItem newItem = new ToDoItem(cursor.getString(keyTaskIndex));
todoItems.add(newItem);
}
aa.notifyDataSetChanged(); // aa is arrayadapter used for the listview
}
}
I have read, CursorLoader automatically updates the view, whenever there is a data change in the content provider db. That means I suppose, getLoaderManager().restartLoader(0, null, this) has to be called implicitly whenever there is a change in data, right?
But that is not happening. Whenever I add a new item (the item is added to the db from onNewItemAdded, but restartLoader is not explicitly called), pause this activity and resume it back. I don't see any implicit call to restartLoader(even though db is changed) and the listview also is not updated with new item added. Why is that? How does a CursorLoader automatically updates the view even if app is not active???
Thanks :)
EDIT: I have also used getContext().getContentResolver().notifyChange(insertedId, null) in insert of my content provider.
I found the answer for my question. In general, CursorLoader doesn't automatically detect data changes and load them to view. We need to track URI for changes. This can be done by following steps:
Registering an Observer in content resolver through cursor using: (Done in the query method of ContentProvider)
cursor.setNotificationUri(getContext().getContentResolver(), uri);
Now when there is any change in URI underlying data using insert()/delete()/update(), we notify the ContentResolver about the change using:
getContext().getContentResolver().notifyChange(insertedId, null);
This is received by the observer, we registered in step-1 and this calls to ContentResolver.query(), which inturn calls ContentProvider's query() method to return a fresh cursor to LoaderManager. LoaderManager calls onLoadFinished() passing this cursor, along with the CursorLoader where we update the View (using Adapter.swapCursor()) with fresh data.
For Custom AsyncTaskLoaders:
At times we need our custom loader instead of CursorLoader. Here we can use someother object other than cursor to point to the loaded data (like list etc). In this we won't be having previlige to notify ContentResolver through cursor. The application may also not have a content Provider, to track URI changes. In this scenario we use BroadcastReceiver or explicit ContentObserver to achieve automatic view updation. This is as follows:
We need to define our custom loader which extends AsyncTaskLoader and implements all its abstract methods. Unlike CursorLoader, our Custom Loader may or may not use a content Provider and it's constructor may not call to ContentResolver.query(), when this loader is instatiated. So we use a broadcast receiver to serve the purpose.
We need to instantiate a BroadCastReceiver or ContentObserver in OnStartLoading() method of abstract AsyncTaskLoader class.
This BroadCast receiver should be defined to receive data-changing broadcasts from content provider or any system events(Like new app installed) and it has to call loader's onContentChanged() method, to notify the loader about the data change. Loader automatically does the rest to load the updated data and call onLoadFinished() to update the view.
For more details refer this: http://developer.android.com/reference/android/content/AsyncTaskLoader.html
I found this very useful for clear explanation : http://www.androiddesignpatterns.com/2012/08/implementing-loaders.html
Well, I think you can restart the loader on certain events. E.g. in my case I have an activity of TODOs. On clicking 'add' option, it launches new activity which has view to feed new TODO.
I am using following code in parent activity's onActivityResult()
getLoaderManager().restartLoader(0, null, this);
It works fine for me. Please share if there is any better approach.
get a reference to your loader while initializing as follows
Loader dayWeatherLoader = getLoaderManager().initLoader(LOADER_DAY_WEATHER, null, this);
then create a class that extends ContentObserver as follows
class DataObserver extends ContentObserver {
public DataObserver(Handler handler) {
super(handler);
}
#Override
public void onChange(boolean selfChange, Uri uri) {
dayWeatherLoader.forceLoad();
}
}
Then register content observer inside onResume lifecycle method as follows
#Override
public void onResume() {
super.onResume();
getContext().getContentResolver().registerContentObserver(CONTENTPROVIDERURI,true,new DayWeatherDataObserver(new Handler()));
}
Whenever there is a change in the underlying data of content provider, the onChange method of contentobserver will be called where you can ask loader to load the data again

Getting SQLiteCursorLoader to observe data changes

I'm trying to implement a DataListFragment with an adapter that uses a Loader from Commonsware. This Loader uses a SQLiteDatabase directly and doesn't require the use of ContentProviders.
The android reference states about Loaders:
"While Loaders are active they should monitor the source of their data and deliver new results when the contents change."
Under my SQLiteCursor implementation (below), this does not happen. OnLoadFinished() gets called once and that's it. Presumably, one could insert Loader.onContentChanged() calls where the underlying database gets changed, but in general the database code class does not know about loaders, so I'm not sure about the best way to go about implementing this.
Does anyone have any advice on making the Loader "data aware", or should I wrap the database stuff in as a ContentProvider and use CursorLoader instead?
import com.commonsware.cwac.loaderex.SQLiteCursorLoader;
public class DataListFragment extends ListFragment implements LoaderManager.LoaderCallbacks<Cursor>{
protected DataListAdapter mAdapter; // This is the Adapter being used to display the list's data.
public SQLiteDatabase mSqlDb;
private static final int LOADER_ID = 1;
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
int rowlayoutID = getArguments().getInt("rowLayoutID");
// Create an empty adapter we will use to display the loaded data.
// We pass 0 to flags, since the Loader will watch for data changes
mAdapter = new DataListAdapter(getActivity(),rowlayoutID, null , 0);
setListAdapter(mAdapter);
// Prepare the loader. Either re-connect with an existing one,
// or start a new one.
LoaderManager lm = getLoaderManager();
// OnLoadFinished gets called after this, but never again.
lm.initLoader(LOADER_ID, null, this);
}
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
String sql="SELECT * FROM "+TABLE_NAME+";";
String[] params = null;
SQLiteCursorLoader CursorLoader = new SQLiteCursorLoader(getActivity(), mSqlDb, sql, params);
return CursorLoader;
}
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
// Swap the new cursor in. (The framework will take care of closing the old cursor once we return.)
mAdapter.swapCursor(data);
// The list should now be shown.
if (isResumed()) { setListShown(true);}
else { setListShownNoAnimation(true); }
}
public void onLoaderReset(Loader<Cursor> loader) {
// This is called when the last Cursor provided to onLoadFinished()
// above is about to be closed. We need to make sure we are no
// longer using it.
mAdapter.swapCursor(null);
}
The Loader documentation is flawed.
100% of Loader implementations built into Android itself "monitor the source of their data and deliver new results when the contents change". Since there is only one Loader implementation built into Android itself as of now, their documentation is accurate as far as that goes.
However, quoting a book update of mine that should be released in an hour or two:
There is nothing in the framework that requires this
behavior. Moreover, there are some cases where is clearly a bad idea to do
this – imagine a Loader loading data off of the Internet, needing to
constantly poll some server to look for changes.
I do plan on augmenting SQLiteCursorLoader to be at least a bit more aware of database changes, if you route all database modifications through it. That too will have limitations, because you don't share Loader objects between activities (let alone have access to them from services).
The only reason CursorLoader works as it does is because it uses a ContentProvider -- a singleton that can therefore be aware of all operations.
At the moment, whatever portion of your code is responsible for inserts, updates, and deletes will either need to tap the SQLiteCursorLoader on the shoulder and have it update, or notify the activity of the change (e.g., broadcast from a Service) so the activity can tap the SQLiteCursorLoader on the shoulder.

Categories

Resources