does anybody have any elegant solution for refreshing the Views
in Fragments
in a FragmentActivity's
ViewPager
after a sync Service
from a SyncAdapter
runs?
I've tried calling notifyDataSetChanged()
and notifyDataSetInvalidated()
on my adapter, as well as refreshDrawableState()
on my views (GridViews
), but to no avail. Perhaps I've been calling them from the wrong places -- I've tried doing it at setUserVisibleHint
where isVisible
=true, hoping to trigger it whenever the fragment comes into view, but it doesn't work.
I've also been using ASync calls to the SQLite database for my data needs, rather than a Content Provider
, which I think would have made this a bit easier. I can think of a couple of ways to do it without a Content Provider
, but neither are very nice.
Any ideas? I can provide code if wished. Thanks.
I'll assume that you're using an AsyncTask for loading the cursor just for the sake of the explanation, but it would work the same if you're using a Loader, an ThreadPool or whatever.
From the service, as soon as new data was changed I would send a LocalBroadcast
. The activity might be there or not, so a broadcast is a good way to let it know there's new data. So from the service you would do:
// that's an example, let's say your SyncAdapter updated the album with this ID
// but you could create a simply "mybroadcast", up to you.
Intent i = new Intent("albumId_" + albumId);
LocalBroadcastManager.getInstance(this).sendBroadcast(i);
and then from the activity/fragment that have the Cursor, you'll be listening to this broadcast like this:
public void onResume(){
// the filter matches the broadcast
IntentFilter filter = new IntentFilter("albumId_" + albumId);
LocalBroadcastManager.getInstance(this).registerReceiver(myReceiver, filter);
}
public void onPause(){
LocalBroadcastManager.getInstance(this).unregisterReceiver(myReceiver);
}
// and of course you have to create a BroadcastReceiver
private BroadcastReceiver myReceiver = new BroadcastReceiver(){
@Override
public void onReceive(Context context, Intent intent){
// here you know that your data have changed, so it's time to reload it
reloadData = new ReloadData().execute(); // you should cancel this task onPause()
}
};
as I said, this next part varies depending on what threading method you're using to load the Cursor, for this example I'll show in a AsyncTask because it's very popular (but I really believe you and every developer in the world should use the Loaders pattern).
private class ReloadData extends AsyncTask<Void, Void, Cursor> {
protected Cursor doInBackground(Void... void) {
// here you query your data base and return the new cursor
... query ...
return cursor;
}
protected void onPostExecute(Cursor result) {
// you said you're using a subclass of CursorAdater
// so you have the method changeCursor, that changes the cursor and closes the old one
myAdapter.changeCursor(result);
}
}
The above approach I tested and used before and I know it works. There's a way of making it work with the flag FLAG_REGISTER_CONTENT_OBSERVER
and override onContentChanged()
to re-execute the query and swap the cursor, but I've never tested it. It will be something like that:
init your adapter with the constructor CursorAdapter(Context context, Cursor c, int flags)
passing the flag FLAG_REGISTER_CONTENT_OBSERVER
and override onContentChanged()
. Inside onContentChanged you will execute the AsyncTask just like above. This way you don't have to use the LocalBroadcastManager
as the database will alert. The reason that method is not my main answer, it's because I've never tested it.
Note that autoRequery
have been deprecated and it's discouraged as it performs data loading in the UI thread.
edit:
I just noticed that the content observer is an API 11 thing. You have two options: 1 use the support library instead: https://developer.android.com/reference/android/support/v4/widget/CursorAdapter.html or the broadcast option.
Register a BroadcastReceiver
in the fragments you have and in its onReceive
call refresh
- this method is supposed to update the UI depending what you have inside. For making your code easy to use, have a base Fragment
class and do the registering/unregistering there along with an abstract refresh
method that will be implemented by children fragments. Something like:
public abstract class BaseRefreshableFragment extends Fragment {
private BroadcastReceiver refreshReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if ("package_name.REFRESH_ACTION".equals(intent)) {
refresh();
}
}
};
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
IntentFilter filter = new IntentFilter("package_name.REFRESH_ACTION");
LocalBroadcastManager.getInstance(getActivity()).registerReceiver(refreshReceiver, filter);
}
@Override
public void onDestroyView() {
LocalBroadcastManager.getInstance(getActivity()).unregisterReceiver(refreshReceiver);
super.onDestroyView();
}
protected abstract void refresh();
}
In your Service
, when your work is done, broadcast an intent with above action. So if there are fragments to show updated data, their receiver will be notified and that will call refresh
ON EACH FRAGMENT. Something like from your service:
Intent intent = new Intent("package_name.REFRESH_ACTION");
LocalBroadcastManager.getInstance(MySyncService.this).sendBroadcast(intent);
The advantage is that you don't need to care when the fragment is shown or not since the receiver is there for the life-time of your fragment's view.
Recreate your adapter from scratch with the new data, and reassign it to the ListView (or whatever Views you have).