I have an activity and a fragment.
This fragment starts a thread which lasts 3 seconds and then calls a callback method on the activity.
if configuration is changed (i.e orientation changed), my actiivty gets recreated with new references, but the thread still has the old reference, causing a mess.
Any idea to solve this? thanks
how about use service instead thread?
reference here :
https://github.com/stephanenicolas/robospice/wiki/Starter-guide
if you want communicate a fragment or activity between thread. use interface like listener. for example, if activity recreate or something change, notify to thread, change listener to new activity.
You can use an AsyncTask instead of thread. Call task.cancel() in your onDestroy() method of the activity.
Hence your activity wont get getting any callback if the onDestroy() method has been already called.
If you insist on using thread then may be you can add a check like this in your callback method
if(getActivity() == null || getActivity().isFinishing())
return;
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
My Problem: Is it possible to prevent an activity to call OnResume() when it is being created? As I saw after the OnCreate() and onStart() method runs, the next one is the onResume(), although I only want to have it when I resume the activity from the paused state.
Why do I need this: I launch my activity (FragmentActivity, so lets say OnPostResume() ) starting with a thread which takes about 2-3s to be ready getting data from an external database. After the thread is done, I call a method which needs these data and I want to call it everytime that activity gets visible. The thread runs only when the FragmentActivity is created (onCreate()), and I cannot put the method into the onResume() because onResume() would be running way before the thread would finish its task. So it would receive not-ready data.
Anyone has a better idea?
Not sure of the exact application of this but I'll make a suggestion.
If you use an AsyncTask, you can send it off to get the data you need and in the onPostExcecute() method you can call your method that requires the data or update the view as needed. (It runs on the UI thread)
If you happen to already have the data you need in certain scenarios you could also bypass the AsyncTask and directly update the view.
This AsyncTask can be triggered in the onResume() method.
If I'm missing something, please let me know and I can adjust my suggestion.
I didn't understand the purpose of this, but here's a possible solution:
If you only wish to get the even of onResume on states that didn't have the onCreate before, just use a flag.
In the onCreate, set it to true, in the onResume check the flag (and also set it to false). if it was true, it means the onCreate was called before.
I personally would prefer to check if the result available, rather than always executing the getter-code in onResume. If the user somehow resumes your activity before the background thread is finished, you'd have a call on onResume, but don't want to display a result.
Maybe it would be a good idea to calculate/fetch the values in the thread, and let the thread return immediately (and cause the values to get filled in) if the values are already cached somewhere. That way you'd only have one entry point (the thread) for updating your UI instead of two (the thread and the onResume method).
When I start my android-activity first onCreate() is called, then onResume().
When I then press the back-button to return to the home-screen and tap the app-icon again, first onCreate() is called, then onResume() and then onDestroy().
My app is still active then, but doing some action result in error since onDestroy() sets a few members to null.
Why is that?
Update: When I wait 30 seconds after pressing back everything works fine. I'm not doing anything heavy in onDestroy except setting a few variables to null and .interrupt()ing a background-thread.
Figured this out by myself. My understanding of what an Activity is was flawed. Of course Android calls onDestroy() on the old Activity instance, which can happen several seconds after the new one has been constructed.
onDestroy gets called because, by default, pressing back key results in your activity calling finish() which initiates the destroying of the activity which calls onDestroy().
To prevent doing some action in case the activity is being destroyed do like this:
if(!isFinishing()) {
// do your action here
}
isFinishing is a method of the Activity.
are you doing some heavy operations in onDestroy(). I think your activity view is destroyed, but not the activity object. And you tap on the App icon even before the previous Activity object is actually destroyed.
I think there is something in addition to what you are describing. Android doesn't just keep activity from being destroyed, something MUST be happening on the main thread.
The symptoms sound exactly as if you had either:
a service doing a longish HTTP or database operation. Are you sure there are no suxg things?
another thread (perhaps managed by an AsyncTask?) calling a synchronized method
Using android-support-v4.jar and FragmentActivity (no fragments at this point)
I have an AsyncTaskLoader which I start loading and then change the orientation while the background thread is still running. In my logs I see the responses come through to the background requests. The responses complete and I expect onLoadFinished() to be called, but it never is.
As a means of troubleshooting, in the Manifest, if I set android:configChanges="orientation" onLoadFinished() gets called as expected.
My Activity implements the loader callbacks. In the source for LoaderManager.initLoader() I see that if the loader already exists, the new callback is set to the LoaderInfo inner object class but I don't see where Loader.registerListener() is called again. registerListener only seems to be called when LoaderManagerImpl.createAndInstallLoader() is called.
I suspect that since the activity is destroyed and recreated on orientation change and since it is the listener for callbacks, the new activity is not registered to be notified.
Can anyone confirm my understanding and what the solution so that onLoadFinished is called after orientation change?
Nikolay identified the issue - Thank you.
I was calling initLoader fron onResume(). The Android documentation states:
"You typically initialize a Loader within the activity's onCreate()
method, or within the fragment's onActivityCreated() method."
Read "typically" as a bit more emphatic than I did when it comes to dealing with configuration change life cycle.
I moved my initLoader call to onCreate() and that solved my problem.
I think the reason is that in FragmentActivity.onCreate() a collection of LoaderManagers is pulled from LastNonConfigurationInstance and in FragmentActivity.onStart() there is some start up work regarding Loaders and LoaderManagers. Things are already in process by the time onResume() is called. When the Loader needs instantiated for the first time, calling initLoader from outside onCreate() still works.
It's actually not the call to initLoader() in onCreate() that's fixing it. It's the call to getLoaderManager(). In summary, what happens is that when an activity is restarted, it already knows about the loaders. It tries to restart them when your activity hits onStart(), but then it hits this code in FragmentHostCallback.doLoaderStart()*:
void doLoaderStart() {
if (mLoadersStarted) {
return;
}
mLoadersStarted = true;
if (mLoaderManager != null) {
mLoaderManager.doStart();
} else if (!mCheckedForLoaderManager) {
mLoaderManager = getLoaderManager("(root)", mLoadersStarted, false);
// WTF: Why aren't we calling doStart() here?!
}
mCheckedForLoaderManager = true;
}
Since getLoaderManager() wasn't called yet, mLoaderManager is null. It therefore skips the first condition and the call to mLoaderManager.doStart().
You can test this by simply putting a call to getLoaderManager() in onCreate(). You don't need to call init / restart loaders there.
This really seems like a bug to me.
* This is the code path even if you aren't using fragments, so don't get confused by that.