I am re-submitting this question, as I don't think my last one really went into the problem I was having.
So I have a thread that returns and then uses a handler to update the UI as the following:
public void completeSignIn(final boolean success, final String error) {
Log.d(Constants.LogTag, "Finalising Sign In...");
final Looper mainLooper = Looper.getMainLooper();
final Handler mainHandler = new Handler(mainLooper);
mainHandler.post(new Runnable() {
#Override
public void run() {
if (success) {
TextView tv = (TextView) getActivity().findViewById(R.id.register);
tv.setText("SIGNED IN!!");
} else if (!success) {
}
}
});
}
The problem I am having is that because the thread could return at any point after it is done querying the server, there is not gaurantee that getActivity() will return the Activity. I have found that if I rotate my device as the thread is about to return, this bit of code can be called inbetween the activities destroy/create cycle. I am unsure if it is important that I am using a fragment here, but I don't think there is any harm in using the parent activity to update views?
So I am unsure how I could force the handler to wait until the activity is created - is this possible, or is thee a standard way of dealing with this? As I don't see this in other apps I have tested.
UPDATE
I put some logs in my fragment and managed to get the following to illustrate my issue:
07-09 22:17:15.164 25435-25435/? D/Kevins_Tag﹕ Detaching Activity...
07-09 22:17:15.234 25435-26702/? D/Kevins_Tag﹕ Signing in...
07-09 22:17:15.234 25435-26702/? E/Kevins_Tag﹕ Activity is null
07-09 22:17:15.234 25435-26687/? D/Kevins_Tag﹕ Finalising Sign In...
07-09 22:17:15.284 25435-25435/? D/Kevins_Tag﹕ Attaching Activity...
07-09 22:17:15.284 25435-25435/? D/Kevins_Tag﹕ Activity Exists
As you can see, the thread is calling the UI in between detach and attach...
I don't know for sure if this could solve your problem, but one thing I would try is check for an existing activity before do anything, like this:
//any code
Activity activity = getActivity();
if(activity != null) {
if (success) {
TextView tv = (TextView) activity.findViewById(R.id.register);
tv.setText("SIGNED IN!!");
} else if (!success) {
}
}
//any code
As you explain in your question, method completeSignIn() is in one of your fragments. The Runnable that completeSignIn() contains is an inner class of the fragment, and as such, holds a reference to the fragment. That is how the code in the Runnable can call fragment method getActivity()--the call is made using the "hidden" fragment reference.
You correctly observe that during a configuration change, completeSignIn() can get called after the fragment has been detached from the activity, resulting in a null activity reference. You ask:
I am unsure how I could force the handler to wait until the activity
is created
That is not possible in this case. However, even if it were, it wouldn't help. The fragment that completeSignIn() has a reference to is dead. It was detached and destroyed and will eventually be garbage collected. When your activity is recreated, the old fragment is not used--a new fragment is constructed and goes through the creation lifecyle steps using the saved state of the old fragment.
It's difficult to suggest an alternative approach without knowing how the overall server sign-in status and display is managed. One option would be to maintain the status in a singleton in addition to updating the sign-in view when sign-in completed. For configuration changes, where the view may not have been updated because it was inaccessible, the activity would be responsible for updating the view when the activity is recreated, using the status from the singleton.
Related
I'm using WebView with javascript interface and sometimes when I call loadUrl on the webView, mWebView.getContext() return null.
How can a view have no Context ? Why ?
Is a view whitout context still used or reusable ?
What should I do when my view did not have a context ?
Most important, if the view has no context, will mWebView.post(new Runnable() { ... be executed ?
Is this code relevant ?
if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
mWebView.loadUrl("javascript:...");
} else {
mWebView.post(new Runnable() {
public void run() {
mWebView.loadUrl("javascript:...");
}
});
}
2 common reasons of a null context to a view:
You're trying to get the context in the callback of an asynchronous (handler, network call) call but the activity/fragment is gone because of another action of the user (ex: pressed back). Therefore the view is detached and has no context anymore.
You have a memory leak somewhere and two or more instances of your activity and view hierarchy. Then something happen in the instance you're not refering to anymore but the view has lost the link to its context.
Regarding the handler.
I am not sure if the view has its own handler or if it uses the handler of the Activity it is attached to, you'd probably have to read the source to find out. However the question is not really relevant: if the view has no context, you have a bigger problem: it's not on the screen.
Regarding whether the code in 5. is relevant, you would need to answer that questions: Why don't you know on which thread your code is running?
When you know on which thread you are, and if it makes sense to you not to be on the main thread, then using a handler is a valid way to execute your code on the main. As well as Activity.runOnUiThread()
Just remember that a Handler's lifecycle is not tied to the activity. So you should clear the queue of messages and runnables when your activity/fragment pauses
I have a MainActivity that uses fragments.
The onCreate of MainActivity completes its onCreate with the use of
welcomeFragment = new MyWelcomeFragment();
fr.beginTransaction().replace(R.id.mainContent, welcomeFragment).commit()
As a part of MyWelcomeFragment's on onResume, a thread is started to get updates from my webserver. If the user selects an action before the thread is completed and goes to MyNewsFragment, what happens to the thread that has yet to complete running in MyWelcomeFragment's thread stack?
Thread was created with: (myThread and handler are instance variables)
myThread = new Thread(new Runnable() {
#Override
public void run() {
sendDataToServer("");
handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
public void run() {
onTaskDone();
}
});
}
});
myThread.start();
Dalvik keeps all Thread references in the runtime so your thread will keep running unless it is terminated or completes (some reference). So depending on where you start your thread, you may be creating more than one. There is no clean way to cancel a Thread and in this case you may need to first cancel the http request inside sendDataToServer and use a shared flag to stop the thread.
In a bigger picture, I would suggest
move the networking method to Activity and handle it there since it has longer lifespan than
Fragment
use Android Volley to handle networking. With it you can manage inadvertent multiple requests to send data to your server. Since each request can be attached with tag, you can cancel any with a particular tag in the queue (in your case the one corresponding to sendDataToServer process) before starting a new one.
and finally use Publisher-Subsriber pattern which has already been made available by libraries like Otto or EventBus. This allows communication between Fragments or Activities while avoiding life cycle related problems. In a gist: a publisher emits events to subscribers registered to it and unlike listeners both publisher and subscriber are totally decoupled. In your case, when sendDataToServer completes, you will not know if the fragment containing onTaskDone is still around. If this method manipulates UI while the fragment has destroyed its view then you will definitely get an error. So onTaskDone should be wrapped inside a subscriber method whose parent fragment is registered to the http event publisher and deregistered as soon as its view is destroyed.
It'll keep running until run() method completes, which is probably for how long it takes for sendDataToServer("") takes to complete, as the handler should be fairly quick in comparison to network IO - or the thread is force interrupted.
Are you still interested in the result if the user switches fragments?
Are you keeping a reference to the welcome fragment? (Via either fragment manager or activity) - if so you could still access the result.
If the user goes back to welcome fragment, the previous thread reference will be lost.
Thread will keep on running till MyWelcomeFragment is alive and If you don't kill it in onPause().
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;
}
i've been constantly frustrated by this and i can't find a good answer, so hoping someone here can offer guidance.
i have a fragment that uses AsyncTask quite extensively. i'm constantly plagued by bugs where the fragment calls getActivity(), which returns null. i assume these are happening because some method in the fragment are invoked before the activity is attached, or after it is detached.
what's the correct way to handle this in my code? i don't want to have this idiom littered all over the place,
Activity activity = getActivity();
if (activity != null) { // do something }
looking at the docs for Fragment, i can come up with many possible hooks to solve this: isDetached(), onActivityCreated(), onAttach(), isResumed(), and so on. what is the right combination?
EDIT:
A few people have suggested canceling tasks when paused, but this implies that the standard idiom,
new AsyncTask<...>.execute();
cannot be used. It implies that every exec'd AsyncTask needs to be tracked to completion, or canceled. I have simply never seen that in example code from Google or elsewhere. Something like,
private final Set<AsyncTask<?>> tasks = new HashSet<>;
...
AsyncTask<?> t = new AsyncTask<...>() {
...
public void onPostExecute(...) {
tasks.remove(this);
...
}
}
tasks.add(t);
t.execute();
...
#Override
public void onPause() {
for (AsyncTask<?> t: tasks) {
t.cancel();
}
tasks.clear();
}
Try to cancel your AsyncTasks in the onPause or onStop methods. That will prevent the onPostExecute from being called when the Fragment is not active anymore (getActivity() returns null).
Or you could check if the Fragment is attached by calling this.isAdded() in your Fragment.
I could not find a good solution. In summary, either use a Loader, or check that getActivity() does not return null before using it. I looked into using Loaders, but the pattern makes a lot of assumptions about the structure of the app and the nature of data retrieval that didn't work for me.
In terms of coordinating lifecycles, I keep onActivityCreated as a mental benchmark - it marks the point at which the underlying activity has finished its own onCreate. Prior to that I do not believe there is an activity to getActivity() from.
That get activity is returning null sounds like either you're calling getActivity() too early (i.e. before it is created) or too late (i.e. when it stopped interacting with the fragment). Stopping your tasks in onPause() would prevent getActivity from returning null since it would cut off the task once the fragment stopped interacting with the underlying activity becuase the activity itself was paused. I think waiting for onStop() may be too late since, if the task were to still be running when the underlying activity paused it may still reutrn null.
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.