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).
Related
My app has a Foreground Service and I prefer that all my network calls happen in that, to keep things sane. I also implement a Room database in the Service.
My plan is to have all database operations take place based on triggers, at the Service end. The broadcast has the data passed to the service in the form of a Bundle in the Intent.
However, my problem is:
BroadcastListeners operate on the main thread.
Room DB operations need to be off the main thread so the app is not ANR'd
When I implement a HandlerThread or a Thread with Runnable in the BroadcastListener, I am unable to pass the data as the intent variable is 'accessed from an inner class and needs to be declared final'
My code (as it stands) is as below.
BroadcastReceiver houseCallCancelReceiver = new BroadcastReceiver () {
#Override
public void onReceive(Context context, Intent intent) {
new Thread(new Runnable() {
#Override
public void run() {
Bundle bundle = intent.getParcelableExtra ("housecall");
String email = bundle.getString("email");
String housecallid = bundle.getString("housecallid");
.
.
.
}
}
}).start();
}
};
Atm I can think of several options, including:
Writing the DB logic (and thread) in a function and passing the
intent as a parameter to the thread. - this leads to code that is
not all together and makes it harder to read as I will have one
function per operation
Making the call in an AsyncTask - same as above plus a lot of one-off AsyncTask calls. Plus can I call an AsyncTask in a ForegroundService?
Upgrading to Java 1.8 and using Lambdas - I'm worried this may break compatibility with older phones.
Any help is much appreciated!
I found an answer (of sorts) in this thread: Runnable with a Parameter
By moving the Room DB logic to a separate function, and declaring a class in the function itself and invoking the function in the constructor, I can pass the Intent to the Class as a parameter.
BroadcastReceiver refreshHousecallsReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
Log.d(TAG, "onReceive: In refreshHousecallsReceiver");
class OneShotTask implements Runnable {
// idea from https://stackoverflow.com/questions/5853167/runnable-with-a-parameter
Intent intent;
OneShotTask(Intent intent1) { intent= intent1; }
public void run() {
refreshHouseCalls(intent);
}
}
Thread t = new Thread(new OneShotTask(intent));
t.start();
}
};
I tried this and it works. However, this is not a reusable way to do things, and I am on the lookout for other ways to do the same thing - please share your examples or ideas as well!
Imagine I have a very simple long-running task as an AsyncTaskLoader:
public class DumbLoader extends AsyncTaskLoader{
private static final String TAG = "DumbLoader";
public DumbLoader(Context context) {
super(context);
}
#Override
public List<String> loadInBackground() {
List<String> allData = new ArrayList<>();
List<String> someData = DummyData.getItems();
notify(someData.size()) ;// publish amount of elements of someData
List<String> someOtherData = DummyData.getSomeOtherItems();
notify(someOtherData.size()); //publish amount of elements of someOtherData
allData.addAll(someData);
allData.addAll(someOtherData);
return allData;
}
}
And I have an activity implementing LoaderCallbacks:
#Override
public Loader<List<String> onCreateLoader(int i, Bundle bundle) {
return new DumbLoader(this);
}
#Override
public void onLoadFinished(Loader<String> dummyLoader, List<String> result) {
// do something with result
}
#Override
public void onLoaderReset(Loader<String> dummyLoader) {
}
How would you implement an AsyncTask-like publishProgress?
The whole purpose of using LoaderManager is to not have to work with references, when Context/Orientation changes - however, with publishProgress the only way I can think of is passing a Handler into my DumbLoader which then notifies me. Is this a safe way? Are there better ways?
Edit: My example might be a bit misleading. In case of having two seperate functions which both return the values of my "final" result I could easily call the AsyncTaskLoader seperately for each function. I modified it to visualize that the data "published" can be different from the final result (in this example, I would like to know the size of the data, but not the data).
An indeterminate ProgressBar seems to me the obvious choice, for this use case. I would instantiate it when the onCreateLoader is called and dismiss it in onLoadFinished
however, with publishProgress the only way I can think of is passing a
Handler into my DumbLoader which then notifies me. Is this a safe way?
Are there better ways?
I would use the LocalBroadcastManager. The intent will be broadcasted only within your app. In your Activity/Fragment register a BroadcastReceiver and update the progress when onReceiver is invoked. Nice thing of BroadcastReceiver is that it runs always on the ui thread
I have 2 views of essentially the same data:
List of items in a android.support.v4.app.ListFragment
Markers on a map in a com.google.android.gms.maps.SupportMapFragment
Both of the above are using loader pattern to obtain the same data (extending LoaderCallbacks, querying ContentProvider, and so on)
Both are hosted within a single activity inside a ViewPager.
What will be the best strategy to synchronize currently selected list item / marker for both of these fragments? (Think of "My Places" edit UI, or "Directions" of the Google Maps with their left-hand pane and a map in the center).
Scenarios i'm thinking of so far:
Make every fragment manually notify it's counterpart about selection change via callback interface (this will probably involve underlying activity to coordinate inter-fragment communications, as it is suggested by Android docs).
Somehow make both fragments use the same Cursor, or even ListAdapter (whatever it means for a map, because now it's populated directly from the cursor).
(Something else?)
Maybe someone has already dealt with this exact case? (I'll sure find some solution, just wanted to avoid "reinventing the wheel". Sorry for a too conceptual question.)
EDIT (Solution)
I think Maciej has answered my exact question ("best strategy", and so on..), so the answers are both 1 and 2 ;-)
Going into more details, my implementation went like this:
At first I frightened by enormous overhead of dealing with publisher/subscriber pattern in Java (involving interfaces, finding proper places for callbacks, and what's not). Fortunately, Otto bus implementation caught my eyes, which made communication between fragments a trivial thing. Not only it is possible to notify all subscribers about selection change, but also the whole Loader Patter fit nicely:
Borrow BusProvider class from Otto's sample code.
Create few message contracts to carry notification data:
public class LocationSelectedEvent {
public long id;
}
public class LocationsLoadedEvent {
public Cursor cursor;
}
Annotate "receiver" methods in fragments with #Subscribe (example below is for loader case, for selection change it's no more complex):
#Subscribe
public void onLoadFinished(LocationsLoadedEvent event) {
final CursorAdapter a = (CursorAdapter) getListAdapter();
a.swapCursor(event.cursor);
}
Make fragments "listening" to notifications:
#Override
public void onActivityCreated(Bundle savedInstanceState) {
BusProvider.getInstance().register(this);
}
Make fragments to stop listening when they're not "alive" (specially true for fragments API, learned it the hard way):
#Override
public void onDestroy() {
super.onDestroy();
BusProvider.getInstance().unregister(this);
}
Finally, trigger notifications where desired (example below deomnstrates how to notify from LocationList activity when cursor has been loaded):
#Override
public void onResume() {
if(null == getLoaderManager().getLoader(0)) {
getSupportLoaderManager().initLoader(0, null, new LoaderCallbacks<Cursor>() {
#Override
public Loader<Cursor> onCreateLoader(int paramInt, Bundle paramBundle) {
return new CursorLoader(LocationsList.this, Locations.CONTENT_URI, null, null, null, null);
}
#Override
public void onLoadFinished(Loader<Cursor> paramLoader, Cursor cursor) {
BusProvider.getInstance().post(new LocationsLoadedEvent(cursor));
}
#Override
public void onLoaderReset(Loader<Cursor> paramLoader) {
BusProvider.getInstance().post(new LocationsLoadedEvent(null));
}
});
}
super.onResume();
}
Bonus: notifications flow visualization
For click-coordination your point 1 is what I would do, using Activity and interfaces of course.
I just have hard time understanding why would you want to load the same data from ContentProvider twice. Why not load it once in a shared object? Some object inside Application, injected singleton or even another Fragment, which notifies Activity of data load complete and it pushes data to your two Fragments?
I have a background service which receive messages from a server and with those message it updates inner properties of objects which are shown in a ListView.
I always uses the runOnUiThread method to run the listArrayAdapter.notifyOnDataSetChanged() command.
From some reason sometimes the ListView is refreshed and it does show me the property update and sometimes it doesn't..
For testing i've added a "refresh" Button to my ListView and when it pressed the listArrayAdapter.notifyOnDataSetChanged() is executed.
Every click on the button the view is refreshed perfectly..
I can't really understand why when trying to refresh from the service it doesn't always work but i think i maybe not always runs on the UIThread...
I'm really hopeless and will glad to get help..
My Code
ServerConnectionManager.java - extends Service
//example of a command executed when a specific message received from the server:
//app is the Application variable
public void unFriend(int userId)
{
serverResponseManager.onUnFriend(app.getApplicationFriend(userId),false);
}
ServerResponseManager.java - a class that handle all application responses to server messages:
public void onUnFriend(FacebookUser facebookUser, boolean isYouRemovedClient) {
//this is the property which will effect the ListView view when calling the
//arrayListAdataper.notifyOnDataSetChanged();
facebookUser.setApplicationFriend(false);
app.getApplicationFriends().remove(facebookUser);
app.getDatabaseManager().deleteApplicationFriend(facebookUser.getId());
//if the application is currently running in the UI (not on the background) it will run a method inside the BaseActivity
if (app.isApplicationInForeground())
{
app.getCurrentActivity().onUnFriend(facebookUser);
if (isYouRemovedClient)
app.showToast(facebookUser.getName() + " has removed from your friends", true);
else
app.showToast(facebookUser.getName() + " has removed you from friends", true);
}
}
BaseActivity.java - an Activity which set all default configuration for all Activities which extends it
//in this exemple the BaseActivity method does nothing but the ListViewActivity.java method override it
public void onUnFriend(FacebookUser facebookUser)
{
}
ListViewActivity.java - extends BaseActivity and have a ListView in it which should reflect the change in the FacebookUser object property which being made in public void onUnFriend(FacebookUser facebookUser, boolean isYouRemovedClient) in ServerResponseManager.
#Override
public void onUnFriend(FacebookUser facebookUser)
{
updateView();
}
private void updateView()
{
runOnUiThread(updateViewRunnable());
}
private Runnable updateViewRunnable()
{
Runnable run = new Runnable() {
#Override
public void run() {
listArrayAdapter.notifyDataSetChanged();
}
};
return run;
}
Don't mix business logic. It looks so complicated that is hard to read.
In your service, broadcast an intent with information about update.
In Activity where ListView is, create and register BroadcastReceiver with IntentFilter for your update events.
In onReceive method of your BroadcastReceiver handle update events, for example update list.
A Service should usually independent from UI concerns. A great way to decouple services and UI related stuff is the event bus pattern. For Android, check out https://github.com/greenrobot/EventBus.
In the ServerConnectionManager, you could post an event:
EventBus.getDefault().post(new UnfriendEvent(userId));
Now register your activity to the event bus, and the event will be delivered to the activity by calling the onEvent method:
public void onEventMainThread(UnfriendEvent event) {...}
Like this, you decouple your components leading to a neat and clean software design, which is very flexible to changes.
You could use a Cursor in your ListView to display your Data.
The Service writes/updates the Data in your ContentProvider. At the End of your Database Transaction you simple use:
getContext().getContentResolver().notifyChange(PROVIDER_URI,null);
and your ListView gets updated automaticly.
Instead use
notifyDataSetChanged on onDestroy of service.
the list view will get refreshed
You can use this tutorial for proper architecture of your code
developing an app with a background service
It shows how to receive notifications from the service and update the UI.
runOnUiThread is mostly used before AsyncTask calls are made. I think you should use a handler instead (it updates the UI and allows the thread to run). Try using the handler and see what happens
(I'm sorry for not being so clear in my first post)
Here is the situation: I have data that is to be refreshed from the Internet. Let's call it Model.
What I want to do: Basically it sounds like an MVC model, where the Model is also kept persistent in local (private) storage. The Model and its associated methods are application-wise. There are several Activity's that display and manipulate different aspects of it:
User
navigates across different Activity's
that display Model
from different perspectives. Currently I have a ListActivity for all elements, and an Activity for one element's details
Sometimes Model needs refreshing.
Surely this is done on a different thread. Refreshing can be triggered from several Activity's.
There are several (time consuming) common
tasks that can be triggered from different Activity's
My application loads and saves Model
to private storage when it starts
and stops
My problem: I'm not sure where to put Model and the related tasks in. Also, I don't know what mechanism to use to notify Activity's. Currently I come up with 2 approaches:
Use Service and send broadcasts. Saving to disk is performed in Service#onDestroyed(), so I want to minimize that by binding it to Activity's. At this point, I'm also not sure how to deliver the updated information: whether to provide a getter in Binder, or include that in the broadcast message.
Customize the Application object so that refreshing methods and getters are available globally. I then perform update from Activity's using AsyncTask. If there are other Activity's that are behind the current Activity, they will update in onResume() when the user navigates back.
Reasons I'm not using a class with static methods:
I need to save and store Model to disk.
Some of the methods need a Context
for displaying toasts, notifications, caching, etc.
Also, I don't put these functionalities in an Activity because there are several activities that manipulate the same piece of persistent data.
Below are pseudocode illustrating what I mean:
Using Service:
/** Service maintaining state and performing background tasks */
class MyService extends Service {
Model mModel;
Binder mBinder;
onCreate() {
super.onCreate();
mBinder = new Binder();
// load mModel from disk, or do default initialization
}
onDestroy() {
super.onDestroy();
// save mModel to disk
}
onBind() {
return mBinder;
}
class Binder {
refresh() {
new AsyncTask() {
doInBackground() {
// update mModel from Internet
}
onPostExecute() {
sendBroadcasts(new Intent("my.package.REFRESHED"));
}
}.execute();
}
getState() {
return mModel.getState();
}
}
}
/** Activity displaying result */
class MyActivity extends ListActivity {
MyService.Binder mBinder;
onCreate() {
super.onCreate();
// register mReceiver
// bind service
}
onDestroy() {
super.onDestroy();
// unbind service
// unregister mReceiver
}
/** Invokes time-consuming update */
refresh() {
// binding is asynchronous, and user may trigger refreshing too early
if (mBinder != null) {
mBinder.refresh();
}
}
BroadcastReceiver mReceiver = new BroadcastReceiver() {
onReceive(Intent intent) {
if ("my.package.REFRESHED".equals(intent.getAction())
&& mBinder != null) {
updateViews(mBinder.getState());
}
}
};
}
Make the functionality globally accessible in the custom Application object
/** Custom Application providing domain specific functionalities */
class MyApplication extends Application {
Model mModel;
onCreate() {
super.onCreate();
// load mModel from disk, or do default initialization
}
onTerminate() {
super.onTerminate();
// save mModel to disk
}
void refresh() {
/** time-consuming */
}
getState() {
return mModel.getState();
}
}
/** Activity displaying result */
class MyActivity extends ListActivity {
onResume() {
super.onResume();
// in case some top Activities have refreshed
// and user is navigating back
updateViews(((MyApplication)getApplicationContext()).getState());
}
/** Invokes time-consuming update */
refresh() {
new AsyncTask() {
doInBackground() {
((MyApplication)getApplicationContext()).refresh();
}
onPostExecute() {
// update the ListView according to result
updateViews(((MyApplication)getApplicationContext()).getState());
}
}.execute();
}
}
Weaknesses I can think of for the Service approach is complexity, since Binding is asynchronous. And it's very likely that I have to repeat some code because I have both ListActivity and Activity
For the Application approach, the documentation says not to rely on onTerminate() being called.
I know I'm being very awkward. What is the conventional way to solve this sort of problem?
Many thanks.
Services are mostly suitable for something that is not bound to a single Activity (and usually work together with NotificationManager or a Widget). This doesn't seem to be the case.
So my suggestion is to have a well-engineered AsyncTask that manages state via SharedPreferences/SQLite itself (instead of abusing Applicaion) and will be launched from the ListActivity.
Well, you could extend a BroadcastReceiver instead of a Service, and when it's done doing what it needs to do, it loads up an Activity with the results.
You didn't explain what kind of info are you getting but this line is important:
These tasks involve states that need
to be loaded and saved gracefully when
the application starts and stops
If that is the case, why don't you do an AsynTask inside the Activity?
I had your same worries about sending Intents with ArrayList inside but I have an app which does exactly that and I am not having performance issues.