Android: notifyDataSetChanged() not updating listview after orientation change - android

I have a semi-complicated problem and hoping that someone here will be able to help me.
On a click event I create a thread and start a long-running operation based on this method. After the long-running task is completed, it does a callback to another method, which does a post to the handler:
#Override
public void contentSearchModelChanged(Model_ContentSearch csm, ArrayList<Class_Reminder> newRemindersList) {
remindersList = newRemindersList;
mHandler.post(mUpdateDisplayRunnable);
}
Which calls a Runnable:
// post this to the Handler when the background thread completes
private final Runnable mUpdateDisplayRunnable = new Runnable() {
public void run() {
updateDisplay();
}
};
Finally, here is what my updateDisplay() method is doing:
private void updateDisplay() {
if (csModel.getState() != Model_ContentSearch.State.RUNNING) {
if(remindersList != null && remindersList.size() > 0){
r_adapter = new ReminderAdapater(Activity_ContentSearch.this, remindersList, thisListView);
thisListView.setAdapter(r_adapter);
r_adapter.notifyDataSetChanged();
}
}
}
This works beautifully when I do this normally. However, if I change the orientation while the long-running operation is running, it doesn't work. It does make the callback properly, and the remindersList does have items in it. But when it gets to this line:
r_adapter.notifyDataSetChanged();
Nothing happens. The odd thing is, if I do another submit and have it run the whole process again (without changing orientation), it actually updates the view twice, once for the previous submit and again for the next. So the view updates once with the results of the first submit, then again with the results of the second submit a second later. So the adapater DID get the data, it just isn't refreshing the view.
I know this has something to do with the orientation change, but I can't for the life of me figure out why. Can anyone help? Or, can anyone suggest an alternative method of handling threads with orientation changes?
Bara

The problem is that when you change orientations a new activity is spun up from the beginning (onCreate). Your long running process has a handle to the old (no longer visible) activity. You are properly updating the old activity but since it isn't on screen anymore, you don't see it.
This is not an easy problem to fix. There is a library out there that may help you though. It is called DroidFu. Here is a blog post that (much more accurately than I) describes the root cause of what you are seeing and how the DroidFu library combats it: http://brainflush.wordpress.com/2009/11/16/introducing-droid-fu-for-android-betteractivity-betterservice-and-betterasynctask/
Edit: (Adding code for tracking active activity)
In your application class add this:
private Activity _activeActivity;
public void setActiveActivity(Activity activity) {
_activeActivity = activity;
}
public Activity getActiveActivity() {
return _activeActivity;
}
In your Activities, add this:
#Override
public void onResume() {
super.onResume();
((MyApplicationClassName)getApplication()).setActiveActivity(this);
}
Now you can get the active activity by calling MyApplicationClassName.getActiveActivity();
This is not how DroidFu does it. DroidFu sets the active activity in onCreate but I don't feel that is very robust.

I had a similar problem, having a time consuming thread sendEmptyMessage to a handler, which in turn called notifyDataSetChanged on a ListAdapter. It worked fine until I changed orientation.
I solved it by declaring a second handler in the UI thread and make the first handler sendEmptyMessage to this handler, which in turn called notifyDataSetChanged on the ListAdapter. And having the ListAdapter declared as static.
I'm a newbie so I don't know if it is an ugly solution, but it worked for me...
From Jere.Jones description I would assume this works as: The long running process sendEmptyMessage to the handle from the old Activity, which in turn sendEmptyMessage to the handle in the new Activity.

Related

Can I block my Service thread while I call a method on the UI thread?

I have what seems like a stupid requirement:
I need to block my IntentService thread while I run a single method which must be accessed from the UI thread. How can I do this?
Obviously I can run the UI method via a Handler with Looper.getMainLooper() but of course the rest of my service processing would then continue.
A bit more detail:
My Service Sync's content while using binder callbacks to progressively update the UI with the new items. All methods which affect the list of items are UI-Thread bound to avoid my StaggeredGridLayout throwing ConcurrentModificationExceptions.
However, when my service starts I want to call the list to get current id's before I sync each source of content, this is what requires the UI thread access.
The reason I can't just provide this list while starting the Service is that the app must remain responsive (meaning an item can be deleted as we sync), and the list needs to be checked before each additional source of content is synced
Solutions:
The best solution I came up with, is to create two Handlers, one on the Main Looper for the UI method, the other for everything else and send messages between them. It doesn't feel like an acceptable, clean solution
The other was to make a threadsafe version of the same UI method, starting by doing an arraycopy of the contents and looping with the copy. I'm not sure if the arraycopy operation is unsafe (prone to ConcurrentModificationException) too, as it's hard to trigger the bug. So I'm not sure this is acceptable either.
I wrote a solution that uses AsyncTask.
Here's an Activity that calls the task:
public class CallingActivity extends Activity {
// method that does all the synchronization
private void doSync() {
// for each item you want to sync
int itemId;
new ItemUpdateAsyncTask(itemId, this).execute();
}
// this method is called for each synced item
public void syncItem(int itemId, Object syncedItem) {
// update list etc.
}
}
Now, the AsyncTask that updates the items in background:
public class ItemUpdateAsyncTask extends AsyncTask<Void, Void, Object> {
// the id of the item to be updated
private final int mItemId;
// reference to the activity that will be notified
// of the item update
private final WeakReference<CallingActivity> mCallingActivity;
public ItemUpdateAsyncTask(int itemId, CallingActivity callingActivity) {
super();
mItemId = itemId;
mCallingActivity = new WeakReference<>(callingActivity);
}
#Override
protected Object doInBackground(Void... params) {
// sync item in background
Object syncedItem;
return syncedItem;
}
#Override
protected void onPostExecute(Object syncedItem) {
CallingActivity callingActivity = mCallingActivity.get();
if (callingActivity != null) {
// update item in main thread
callingActivity.syncItem(mItemId, syncedItem);
}
}
}
I think it is easier to design this solution using AsyncTasks instead of Services because of the callback object passing. Services and Activities can only communicate with each other by means of Intents, which can carry only serializable data. As you can see, asynctasks can reference callback objects, which makes things easier. Or else, you would have to register a broadcast receiver in the activity and the service would have to send a broadcast with the updated item, which would need to be serializable. That's a viable solution as well, though.
the user can go between several activites during the time it is running
It is a point worth noting when you design an architecture like this, that does something in background and pushes the result to the caller. The caller may be e.g. an Activity that may be already finished and garbage-collected by the time that the background work finishes. That's the reason I wrapped the caller in a WeakReference.

How to deliver and persist changes to the UI from an asynchronous task hosted by a retained fragment?

Using a retained fragment to host asynchronous tasks is not a new idea (see Alex Lockwood's excellent blog post on the topic)
But after using this I've come up against issues when delivering content back to my activity from the AsyncTask callbacks. Specifically, I found that trying to dismiss a dialog could result in an IllegalStateException. Again, an explanation for this can be found in another blog post by Alex Lockwood. Specifically, this section explains what is going on:
Avoid performing transactions inside asynchronous callback methods.
This includes commonly used methods such as AsyncTask#onPostExecute()
and LoaderManager.LoaderCallbacks#onLoadFinished(). The problem with
performing transactions in these methods is that they have no
knowledge of the current state of the Activity lifecycle when they are
called. For example, consider the following sequence of events:
An activity executes an AsyncTask.
The user presses the "Home" key,
causing the activity's onSaveInstanceState() and onStop() methods to
be called.
The AsyncTask completes and onPostExecute() is called,
unaware that the Activity has since been stopped.
A FragmentTransaction is committed inside the onPostExecute() method,
causing an exception to be thrown.
However, it seems to me that this is part of a wider problem, it just happens that the fragment manager throws an exception to make you aware of it. In general, any change you make to the UI after onSaveInstanceState() will be lost. So the advice
Avoid performing transactions inside asynchronous callback methods.
Actually should be:
Avoid performing UI updates inside asynchronous callback methods.
Questions:
If using this pattern, should you therefore cancel your task, preventing callbacks in onSaveInstanceState() if not rotating?
Like so:
#Override
public void onSaveInstanceState(Bundle outState)
{
if (!isChangingConfigurations())
{
//if we aren't rotating, we need to lose interest in the ongoing task and cancel it
mRetainedFragment.cancelOnGoingTask();
}
super.onSaveInstanceState(outState);
}
Should you even bother using retained fragments at all for retaining ongoing tasks? Will it be more effective to always mark something in your model about an ongoing request? Or do something like RoboSpice where you can re-connect to an ongoing task if it is pending. To get a similar behaviour to the retained fragment, you'd have to cancel a task if you were stopping for reasons other than a config change.
Continuing from the first question: Even during a config change, you should not be making any UI updates after onSaveInstanceState() so should you actually do something like this:
Rough code:
#Override
public void onSaveInstanceState(Bundle outState)
{
if (!isChangingConfigurations())
{
//if we aren't rotating, we need to lose interest in the ongoing task and cancel it
mRetainedFragment.cancelOnGoingTask();
}
else
{
mRetainedFragment.beginCachingAsyncResponses();
}
super.onSaveInstanceState(outState);
}
#Override
public void onRestoreInstanceState(Bundle inState)
{
super.onRestoreInstanceState(inState);
if (inState != null)
{
mRetainedFragment.stopCachingAndDeliverAsyncResponses();
}
}
The beginCachingAsyncResponses() would do something like the PauseHandler seen here
From a developer's point of view, avoiding NPEs' in a live app is the first order of business. To methods like onPostExecute() of AsyncTask and onResume() & onError() in a Volley Request, add:
Activity = getActivity();
if(activity != null && if(isAdded())){
// proceed ...
}
Inside an Activity it should be
if(this != null){
// proceed ...
}
This is inelegant. And inefficient, because the work on other thread continues unabated. But this will let the app dodge NPEs'. Besides this, there is the calling of various cancel() methods in onPause(), onStop() and onDestroy().
Now coming to the more general problem of configuration changes and app exits. I've read that AsyncTasks and Volley Requests should only be performed from Services and not Activitys, because Services continue to run even if the user "exits" the app.
So I ended up digging around a bit on this myself and came up with quite a nice answer.
Although not documented to do so, activity state changes are performed in synchronous blocks. That is, once a config change starts, the UI thread will be busy all the way from onPause to onResume. Therefore it's unnecessary to have anything like beginCachingAsyncResponses as I had in my question as it would be impossible to jump onto the main thread after a config change started.
You can see this is true by scanning the source: http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/5.0.2_r1/android/app/ActivityThread.java#3886 looking at this, it looks like onSaveInstancestate is done sequentially with handleDestroyActivity ... And so it would be impossible to update the UI an have it lost during a config change.
So this should be sufficient:
#Override
public void onSaveInstanceState(Bundle outState)
{
if (!isChangingConfigurations())
{
//if we aren't rotating, we need to lose interest in the ongoing task and cancel it
mRetainedFragment.cancelOnGoingTask();
}
super.onSaveInstanceState(outState);
}
From the retained fragment it's crucial to access the activity from the main thread:
public void onSomeAsyncNetworkIOResult(Result r)
{
Handler mainHandler = new Handler(Looper.getMainLooper());
Runnable myRunnable = new Runnable()
{
//If we were to call getActivity here, it might be destroyed by the time we hit the main thread
#Override
public void run()
{
//Now we are running on the UI thread, we cannot be part-way through a config change
// It's crucial to call getActivity from the main thread as it might change otherwise
((MyActivity)getActivity()).handleResultInTheUI(r);
}
};
mainHandler.post(myRunnable);
return;
}

How can I update UI of an app that is in the background without bringing it to the foreground?

I have an Android app with an AlarmManager that repeats every 15 minutes. Depending on the results from this I want to update the UI, but I don't want Android to bring the app to the foreground if it doesn't have focus already.
How can I do this? Alternatively, how can I make the UI updates run in onResume?
I'm calling methods like public static void updateRunningStatusTextView(Boolean inStatus) on the main activity from services and classes
I'm pretty sure your first option can't happen. There is postInvalidate() that can update the View from a non UI thread but this is available
...only when this View is attached to a window.
according to the Docs
Alternatively, how can I make the UI updates run in onResume?
This depends on many things such as the View, the data, and what you have in your code but you could set the data in a static class or save it in something like SharedPreferences then retrieve the data from there, say a String to use in setText() of a TextView
#Override
protected void onRestart() {
super.onRestart();
new Handler().postDelayed(new Runnable() {
public void run() {
// update UI here from cached values
}
}, 100);
}
Well, you don't. If it is in background, you don't need to update the UI!
Then you just need to update the UI when you resume your app (onResume), with the latest data you have.

Android UI Update Thread - saving and restoring it

How do I properly do that?
I have a stopwatch and I'm saving it's state in onSaveInstance and restoring it's state in onRestoreInstance...
Now I've following problem: if I stop the thread in onSaveInstance and the screen get's locked or turned off, onRestoreInstance is not called and the stopwatch is not continuing...
If I don't stop it, the stopwatch is running in background on and on even when the screen is off or the activity is not active anymore...
So what's the usual way to handle such a thing?
PS:
I even have a working solution, a local variable to save the running state in the onStop event and restarting the thread in the onStart event... But I still want to know if there's a "default" solution using the android system itself....
Ok. I better now understand what you're doing. I thought you were using the thread to count. Right now it sounds like you're using it to update the UI.
Instead, what you probably should be doing is using a self-calling Handler. Handlers are nifty little classes that can run asynchronously. They're used all over the place in Android because of their diversity.
static final int UPDATE_INTERVAL = 1000; // in milliseconds. Will update every 1 second
Handler clockHander = new Handler();
Runnable UpdateClock extends Runnable {
View clock;
public UpdateClock(View clock) {
// Do what you need to update the clock
clock.invalidate(); // tell the clock to redraw.
clockHandler.postDelayed(this, UPDATE_INTERVAL); // call the handler again
}
}
UpdateClock runnableInstance;
public void start() {
// start the countdown
clockHandler.post(this); // tell the handler to update
}
#Override
public void onCreate(Bundle icicle) {
// create your UI including the clock view
View myClockView = getClockView(); // custom method. Just need to get the view and pass it to the runnable.
runnableInstance = new UpdateClock(myClockView);
}
#Override
public void onPause() {
clockHandler.removeCallbacksAndMessages(null); // removes all messages from the handler. I.E. stops it
}
What this will do is post messages to the Handler which will run. It posts every 1 second in this case. There is a slight delay because Handlers are message queues that run when available. They also run on the thread that they're created on, so if you create it on the UI thread you will be able to update the UI without any fancy tricks. You remove the messages in the onPause() to stop updating the UI. The clock can continue to run in the background, but you won't be showing it to the user anymore.
I just got into Android programming, but I don't think onRestoreInstance will be called in that situation because you're not switching from one activity to another. I think your best bet is to call onPause which will then call onSaveInstance if you need it to, but use onResume which might or might not call onRestoreInstance.

stop or refresh an app's activity

I have a START and STOP button in the main screen of an App. there are some GUI and threads that are instantiated when I click on START. When I click on stop, I want everything to be stopped and the activity should come back to its origin state. To the state that is exactly same like when launched (when we tapped on App icon in mobile).
Is it possible to do this? I tried with finish() , this killed the app and exited . I don't want to exit from main screen. rather, on clicking STOP I want app to come back to origin or born state. Thanks.
How are you running your threads? Are they vanilla threads or subclasses of AsyncTask?
If these are instances of an AsyncTask object, you can use the cancel() method to cancel it and then inside your doInBackground() method, you could check the isCancelled() method to see if it has indeed been canceled, and then exit gracefully.
Pseudo code below:
private YourTask taskRef;
public void btnStartHandler() {
taskRef = new YourTask();
taskRef.execute();
}
public void btnStopHandler() {
taskRef.cancel();
}
and then, in your AsyncTask:
public Void doInBackground(Void... arg0) {
// Background loop start
if (this.isCancelled()) {
return;
}
// Background loop continue...
}
If you're using threads, you can interrupt them and catch the exception and handle it there. Furthermore, you could create a method that you call from onCreate() called initApp() or something that initializes everything. You could also use that initApp() from the STOP button click handler to reset values back to startup defaults.
You can restart the activity with finish() and then call startActivity(getIntent());. This will effectively restart your activity and put it in its default state, no matter how it was started.
Before doing that make sure to cancel all threads or AsyncTasks as TJF suggested (you can and should do this in the onDestroy overload).
For more info about restarting an activity, and a discussion about pros and cons, see this question: Reload activity in Android

Categories

Resources