I have a case where I have two clickable items in a fragment. Item A detaches the current fragment from the current activity, and attaches a new fragment. Item B causes some UI updates. My problem is, if I click on both of them in quick succession, the app crashes. I get a NullPointerException in the UI updates because apparently the fragment has already been detached from item A. A more visual representation of the log:
Click item B
Click item A shortly thereafter
Item A causes the current fragment to detach, and eventually causes onDestroyView to be called.
Item B attempts to update the UI about 150ms later, but the UI elements are now null because onDestroyView has already executed, causing a NPE.
I'm using a Nexus 4 running 4.2.2. I'm using Dagger to do dependency injection, which I thought could be the source of the issues, but I'm seeing similar edge cases throughout my app where two things can be clicked on almost simultaneously and the crashes sometimes happen with things that aren't injected. In each case, both actions will happen in a seemingly undefined order, which causes weird behavior and crashes.
Is there a way to stop all subsequent or in flight events in onStop or onDestroyView? Or do I need to add an isDetached() check to every one of my UI listeners? I also tried detaching all of my listeners in onDestroy, which eliminated some, but not all of the crashes. It seems that I'm having a similar issue to this unanswered question: Fragment's OnClickListener called after onDestroyView.
When you finish the Activity explicitly, it takes sometimes to destroy the fragment, so the following check will be more generic.
if(getActivity() == null || getActivity().isFinishing() ||
fragment.isRemoving() || fragment.isDetached() ||
!fragment.isAdded()) {
// fragment is stopped
}
It is not possible to give exact solution to the problem without seeing your actual implementation, but here is what I'd do:
Pass the instance of the fragment to the method where I am doing the UI update and check if the fragment is currently being removed, is added or was not already detached from the UI (or any combination of these) before actually attempting the update. Example:
private static void updateUi(MyFragment fragment, Object param) {
if (fragment.isRemoving() || fragment.isDetached() || !fragment.isAdded()) {
return;
}
// Update the UI
}
Hope this helps.
Related
I understand the basic Lifecycle of Activity/Fragment but sometimes, when user puts the app in background state for long time I'm not sure what's going to happen when he opens it again. Recently I've encountered a bug: User gets nullpointer exception by calling method of a view saved in class variable (textView = findViewById(...)), inside fragment's OnResume method. The variable is set in OnViewCreated(). Is that possible that over long period of time fragment might lose it's fields due to lack of memory? When onResume() will be called and when onCreate()?
If the app is in background for a long time its process will be killed by OS, or if the device is running low memory. To test how your app works under these conditions use flag "Do not keep activities" in Developer options on your device. In the described case onCreate will be called when Activity will come to the foreground.
If the process is not yet killed then onResume will be triggered. Normal variables persist, but the problem is that you can never be sure when you're calling onResume and when you're calling onCreate (since you have no control over when Android just goes and tosses stuff on the stack out the window... anything not currently being used is eligible for destruction).
So my solution is to add a null check with if condition: if the variable is null then initialize and perform actions, if not then just preform actions.
I want to manipulate a Variable (Object) in my Fragment once the User returns to the App from the Homescreen. Inside the Fragment i hold a reference to it. Say like this:
Object a;
However, when i try to manipulate the Variable in one of the following Methods (wich i overwrite inside the Fragment): onStart, onResume or onViewStateRestored OR in one of the following in the Activity onResume, onStart, onPostResume the variable is always null and callls on the the Object (like a.doSomething()) fail.
The Use Case: User starts my App, presses Home Button and returns to the said App via "recent Apps Screen"
Any ideas how to solve this?
Preferably not with a delayed Threading Solution like "overwrite onResume, make Runnable that starts after 2 seconds and does the stuff in there"
I already noticed this only occurs when the User is quick and the Fragment is still alive in the Background and does not need to be recreated from scratch so to say, because when the fragment goes through onCreateView i am handling it via the normal use case of a normal app start
thanks
When are the view's 'killed' and no more exist in Android ?
For example, suppose I have an asynctask and I run some network related stuff in the doInBackground() method. Then, I need to update my UI views in the onPostExecute() method.
Assume my doInBackground() took a while and while it was being processed the user moved back or even pressed the home button. The task will continue because doInBackground runs on a seperate thread, however, once it is finished and onPostExecute is called to update the views, the views might not be there.
Even if the activity is not visible (either gone to home screen, or another activity), what happens when the views try to get accessed and modifed ? How long do they stay in the 'heap/memory/whatever', do they get garbage collected after onDestroy ? or they stay around after that ?
I know a way to not get into this hastle is to use a switch that gets turned on and off inside onResume and onStop and check it before updating the views in onPostExecute, but I am unsure if this is solid approach applied in the android apps ? if not, what is the suggested way ?
A view inside an activity can be considered like any other object within the activity class. It will stay in the memory as long as it is referenced by the some other object. This object could be another view or activity. This means the view will be gone if:
1) The activity or the parent view remove it removeView() from the view tree thus no one keeps a reference to it.
2) The activity or parent view that contain the view are destroyed/gone from the memory.
If the activity is not visible (either gone to home screen, or another
activity), what happens when the views try to get accessed and modifed
? How long do they stay in the 'heap/memory/whatever', do they get
garbage collected after onDestroy ? or they stay around after that ?
You can access the view, as long as your activity is available. And you can find more about that by reading the Activity Lifecycle
When you try to access a view that is gone from the memory, you will get a NullPointerException. The simple & solid way how you can handle onPostExecute is by checking for null before updating, example:
// inside onPostExecute
if(textView != null) {
textView.setText("Background Method Finished");
}
The advantage of this approach is:
1) You do not have to explicitly keep track of show/hide.
2) Sometimes, view is not on the screen does not mean that it gone from the memory. For example, let say your AsyncTask finishes while your activity is paused/stopped not destroyed. In this case, you can still update the view, so that when the activity is resumed the update is visible and is not lost.
Cancel async task when user leave from that activity. After cancelled task, onPostExecute will not called and capture cancel event in onCanceled event in async task class.
You have many ways
1.You can cancel the asynctask
2.You can kill the process,so there will be nothing in the menory or heap.
I recently converted my Activities to Fragments.
Using something similar to Tab-Navigation the fragments are replaced when the user selects another tab.
After the fragment is populated I start at least one AsyncTask to get some information from the internet. However - if the user switches to another tab just as the doBackground-method from my AsyncTask is being executed - the fragment is replaced and thus I am getting a NullPointerException in the marked lines:
#Override
protected Object doInBackground(Object... params) {
...
String tempjson = helper.SendPost(getResources().getText(R.string.apiid)); //ERROR: Fragment not attached
...
}
protected onPostExecute(Object result) {
...
getActivity().getContentResolver() //NULLPOINTEREXCEPTION
getView().findViewById(R.id.button) //NULL
...
}
getActivity() and getResources() causes an error because my Fragment is replaced.
Things I've tried:
Calling cancel method on my AsyncTask (won't fix first error nor the second error if the fragment is replaced while onPostExecute() is executed)
checking if getActivity() is null or calling this.isDetached() (not a real fix and I'd need to check it whenever I call getActivity() and so on)
So my question is: what would be the best to get rid of these AsyncTask problems? I did not have these problems using Activities as they weren't "killed" / detached on tab change (which resulted in higher memory usage - the reason why I like to switch to Fragments)
Since AsyncTask is running in the background, your fragment may become detached from its parent activity by the time it finishes. As you've found out, you can use isDetached() to check. There's nothing wrong with that, and you don't have to check every time, just consider the fragment and activity life cycles.
Two other alternatives:
Use Loaders, they are designed to play nicer with fragments
Move your AsyncTask loading to the parent activity and use interfaces to decouple from the fragments. The activity would know whether a fragment is there or not, and act accordingly (by possibly discarding the result if the fragment is gone).
Today I've faced the same problem: when I changed the fragment being displayed if the AsyncTask has not finished yet, and it tries to access the viewto populate it with some more elements, it would return a NullPointerException.
I solved the problem overriding one method of the fragments lifecycle: onDetach(). This method is called in the moment before the fragment is detached from the activity.
What you need to do is to call the cancel() method on your AsyncTask. This will stop the task execution avoid the NullPointerExecption.
Here's a sample of onDetach():
#Override
public void onDetach() {
super.onDetach();
task.cancel(true);
}
Check this page to get more information about fragments lifecycle:
http://developer.android.com/reference/android/app/Fragment.html#Lifecycle
And this to view more about Cancelling a task:
http://developer.android.com/reference/android/os/AsyncTask.html
Have you try calling setRetainInstance(true); in the onCreate() function of your fragment class?
I have a strange situation I am seeing.
I am reading some info from a database , then bringing up a dialog :
PSEUDO
val = DBaseManager.readValue(i,POS_ONE);
if(val == 1)
{
Dialog_Test myDialog = new
Dialog_Test (myContext,"",new addListener(),DBaseManager);
myDialog.show();
}
as you can see I pass into the Dialog the DBaseManager so it can use it also.
then in there I use it like this :
DBaseManager.readValue(k,POS_TWO);
etc.
Now this works 99% of the time, however I have had some crash logs pointing to these lines in the dialog with null pointer exceptions.
To me this is indicating that on some devices my onPause or OnDestory methods are being called in the main activity which closes and nulls DBaseManager. These are 1.6, 2.2 and 2.3 devices.
So the question is why , and how to prevent ? I have added some null pointer checks in to prevent the crashes but it still far from ideal.
UPDATE: On my devices at least when I do a screen rotate the activity is restarted and the dialog disappears - could it be on some the dialogs remains up???
This probably depends on the scope of your Dialog and where in the code you are opening it. The default behavior is for the Activity to go through its lifecycle when the orientation changes unless you explicitly handle orientation changes and override onOrientationChanged. You might want to do something like maintain the state of your dialog at the Activity level (such as adding a boolean isDialogDisplayed as a class variable) and then in onCreate or onResume check that and reopen the dialog.
As far as the DBaseManager object being cleaned up, I have experienced similar things. Large objects getting cleaned up when you don't expect them to (there are still pointers to these objects, but they've been nulled by the system somehow). I've identified these in my applications and just been more careful. Instead of null checking and reloading when null only when it's possible that your code could have nulled the variable (or using lazy loading), I'd always treat this object as if it were lazy loaded. Add a method that returns this object instead of accessing it directly, and always check for null and reload if it is.
For both of these two issues, you might want to override all the lifecycle methods of this Activity and log them. Don't forget to call the lifecycle methods on the base class within your overrides (ie. public void onPause() { super.onPause() ..., etc)!