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?
Related
In my application I have an Activity that holds 3 Fragments. The very first time the Activity is created, Fragment 1 is displayed. Next, all fragment transactions will be executed after a network operation. For example: Fragment 1 has a button to make a request to the server and when the result is ready, Fragment 1 uses a listener to call a method defined inside the parent activity, to replace fragment 1 with fragment 2.
This works fine, except when the parent activity receives the callback after its state has been saved by onSaveInstanceState(). An IllegalStateException is thrown.
I've read some answers about this problem, for example this post and I understood why this exception happens thanks to this blog.
I also take an example that I found here to try to solve the problem. This post suggests to always check if the activity is running before call commit(). So I declared a Boolean variable in the parent activity and I put its value to false in onPause() and to true in onResume().
The parent activity callback called after network operations has been completed is something like this piece of Kotlin code, where next is the number of the replacing fragment:
private fun changeFragment(next:Int){
// get the instance of the next fragment
val currentFragment = createFragment(next)
// do other stuff here
if(isRunning){
// prepare a replace fragment transaction and then commit
ft.commit()
}else{
// store this transaction to be executed when the activity state become running
}
}
This code is working fine and now I'm not getting the Exception anymore, but my question is: it's possible that onSaveInstanceState() is called after I check if(isRunning) and before I call ft.commit(), so that the commit() happens after the activity state has been saved causing IllegalStateException again?
I'm not sure if onSaveInstanceState() could interrupt my changeFragment() method at any point in time. Is it possible?
If the possibility exists and my code may be interrupted between if(isRunning) and ft.commit(), what I can do?
It could be solved adding a try{}catch(){} block like this?:
if(isRunning){
try{
ft.commit()
}catch(ie:IllegalStateException){
// store the transaction and execute it when the activity become running
}
}else{
// store the transaction and execute it when the activity become running
}
Its a bit late but as of API 26+ we can use following to check if we need to do a normal commit or commitAllowingStateLoss().
getSupportFragmentManager().isStateSaved();
Are you storing anything when you're changing states?
If not, then you can try commitAllowingStateLoss().
onSaveInstanceState() would not be able to interrupt your method if your method is being called on the main (UI) thread.
Another approach that tends to make your life easier is to not use callbacks, but rather adopt a reactive pattern like MVVM. In that pattern, your Activity or Fragment subscribe to an observable when they are interested in e.g. network responses and unsubscribe typically in the onStop or onPause lifecycle callbacks so that your methods never get called after onSaveInstanceState. For a good starting place, check the official LiveData overview.
Could anyone explain what does exactly happens (lifecycle of Asynctask) if for example I have and Activity with a Fragment and from this Fragment I execute an Asynctask where on the onPreExecute I start displaying a ProgressDialog and at some point I close the app while the Asynctask is still running?
As far as I've checked the Fragment doesn't call onDetach nor onDestroy and the Asynctask doesn't reach the onPostExecute method or onCancelled
If i am not wrong you are familiar with lifecycle of an AsyncTask. If not, refer
https://developer.android.com/reference/android/os/AsyncTask.html
To answer why onPostExecute method is not called when we exit the app while the progress bar is still running, I would say it is because, AT holds a reference to the Activity/Context which would be destroyed by the time progress bar decides it's job is done(bg task/thread).
Very useful blog on how to handle ATs wrt fragments and activities by Alex Lockwood.
http://www.androiddesignpatterns.com/2013/08/fragment-transaction-commit-state-loss.html
Have you set setRetainInstance(true) in your fragment, whenever you start asynctask inside a fragment with setRetainInstance as true the task will continue to run in background without interrupt which is not in the case of an activity
Please ensure that AsyncTask.doInBackground() has been completed. Or, what is more possible, your main thread is stuck somewhere as AsyncTask.onPostExecute() must be executed in main thread.
In the scenario you are speaking about, the Fragment methods "onPause()" and "onStop()" will be called (besides of the ones of the Activity which contains the given Fragment).
As those methods are being called, you should react according to what you want to do in the AsyncTask.
If that's not the case, refer to the link given by #stack_ved.
Anyway, if you want to do any kind of load inside a Fragment, I strongly recommend you to use "Loaders" or "AsyncTaskLoader".
https://developer.android.com/reference/android/content/AsyncTaskLoader.html
First and foremost:
*I have Fragment classes which serve as a class for each page in the viewPager.
*Each fragment class has its own AsyncTask.
My problem here is that the AsyncTask's of each fragment class are called at once when the class that has the ViewPager is called. I know because in each of the AsyncTask's onPreExecute() i put a ProgressDialog. I am expecting that every time I swipe and go to another page, that should be time when the AsyncTask of each of the fragment class will load, not on the first page all at once.
I tried putting the AsyncTask.execute() on the onActivityCreated(Bundle) but still nothing changes.
Also, every time I swipe pages, the ProgressDialog inside the AsyncTask's onPreExecute() shows up. I placed a Log in every onPreExecute() but surprisingly it prints one time only ever since theviewPager` is called.
If you want each AsyncTask started only when your Fragment is visible, you must execute it from either the Fragment's onStart() or onResume() method. The reason they're all being called at the same time is because a Fragment's onActivityCreated() is called when the parent Activity is created, not when the Fragment is visible. Take a look at the lifecycle of a Fragment to see when it would be most appropriate to execute your AsyncTask.
Additionally, since you are using Fragments, I would highly suggest using a Loader as opposed to an AsyncTask. They are much easier to manage alongside of a Fragment.
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.
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.