Refreshing fragments in FragmentActivity after syn

2019-05-15 14:46发布

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.

3条回答
乱世女痞
2楼-- · 2019-05-15 15:27

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.

查看更多
forever°为你锁心
3楼-- · 2019-05-15 15:32

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.

查看更多
Rolldiameter
4楼-- · 2019-05-15 15:35

Recreate your adapter from scratch with the new data, and reassign it to the ListView (or whatever Views you have).

查看更多
登录 后发表回答