How to distinguish between orientation change and leaving application android - android

I understand that when the orientation of the screen changes, the current activities onDestroy() is called followed by the onCreate() to effectively recreate the activity.
I need to know how to programmatically tell if the application is being exited or if just the orientation is being changed.
One method is for the previous activity to notify me when its onResume() method is being called, this will let me know that the user has pressed the back button and the orientation hasn't been changed.
P.S. I'm looking for a solution more elegant than listening for a back hardware button click.
Here was what i wanted to do:
I have two tabs, when the activity is being entered for the first time or the user has left the activity and is now entering it, a certain tab is displayed based on some criterion.
When the orientation changes, i need to stay on the same tab.

Use the Activity's isFinishing() method.
#Override
protected void onDestroy() {
super.onDestroy();
if (isFinishing()) {
// do stuff
} else {
//It's an orientation change.
}
}

you can use isChangingConfigurations() Read from documentation
Check to see whether this activity is in the process of being
destroyed in order to be recreated with a new configuration. This is
often used in onStop() to determine whether the state needs to be
cleaned up or will be passed on to the next instance of the activity
via onRetainNonConfigurationInstance().
Returns If the activity is being torn down in order to be recreated
with a new configuration, returns true; else returns false
Explain in simple way with example
isChangingConfigurations()
is method used to check if the activity will be destroyed to be re-created again (as result of change in orientation )
How to use it ?
if you use api >= 11 then no problem , but if you use api < 11 then we must handle this method manual I make boolean variable called IsconfigChange
private boolean IsconfigChange ;
...
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
IsconfigChange = true ;
}
#TargetApi(Build.VERSION_CODES.HONEYCOMB)
#Override
public boolean isChangingConfigurations() {
if(android.os.Build.VERSION.SDK_INT >= 11){
Log.i("DEBUG", "Orientation changed api >= 11 ");
return super.isChangingConfigurations();
}else {
Log.i("DEBUG", "Orientation changed api < 11 ");
return IsconfigChange;
}
}
#TargetApi(Build.VERSION_CODES.HONEYCOMB)
#Override
protected void onStop() {
super.onStop();
if(isChangingConfigurations()){
Log.i("DEBUG", "isChangingConfirgurations OnStop Called");
} else{
Log.i("DEBUG", "OnStop Called");
}
}
Summery
you can use isChangingConfigurations in onStop to check if app stop to be destroyed or due to change in orientation .
or you can use isFinishing check my answer here

for API lvl >= 11 Activity has a isChangingConfigurations() method

You could grab the value of the Activity.getChangingConfigurations() method in your onDestroy callback. This will return a result such as ORIENTATION_PORTRAIT, which you could check against your current orientation.
Note that the activity closing and orientation changes aren't the only conditions to consider here: What about returning to the Home screen, incoming phone calls and other apps stealing the focus, and all the other scenarios when your Activity is no longer at the front of the stack?
Most of the time you will not need to do this. If you are trying to fix some Activity state issue (often manifesting as a NullPointerException when you rotate the screen)
by capturing the orientation event; review the Android Activity lifecycle and make sure you are not just trying to hack a fix for a design flaw. Post up your original problem on this site if you are unsure.

Related

SplashScreen crashes when device screen is turned off [duplicate]

I have a Live Android application, and from market i have received following stack trace and i have no idea why its happening as its not happening in application code but its getting caused by some or the other event from the application (assumption)
I am not using Fragments, still there is a reference of FragmentManager.
If any body can throw some light on some hidden facts to avoid this type of issue:
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at android.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1109)
at android.app.FragmentManagerImpl.popBackStackImmediate(FragmentManager.java:399)
at android.app.Activity.onBackPressed(Activity.java:2066)
at android.app.Activity.onKeyDown(Activity.java:1962)
at android.view.KeyEvent.dispatch(KeyEvent.java:2482)
at android.app.Activity.dispatchKeyEvent(Activity.java:2274)
at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchKeyEvent(PhoneWindow.java:1668)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at com.android.internal.policy.impl.PhoneWindow$DecorView.superDispatchKeyEvent(PhoneWindow.java:1720)
at com.android.internal.policy.impl.PhoneWindow.superDispatchKeyEvent(PhoneWindow.java:1258)
at android.app.Activity.dispatchKeyEvent(Activity.java:2269)
at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchKeyEvent(PhoneWindow.java:1668)
at android.view.ViewRoot.deliverKeyEventPostIme(ViewRoot.java:2851)
at android.view.ViewRoot.handleFinishedEvent(ViewRoot.java:2824)
at android.view.ViewRoot.handleMessage(ViewRoot.java:2011)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:132)
at android.app.ActivityThread.main(ActivityThread.java:4025)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:491)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:841)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:599)
at dalvik.system.NativeStart.main(Native Method)
This is the most stupid bug I have encountered so far. I had a Fragment application working perfectly for API < 11, and Force Closing on API > 11.
I really couldn't figure out what they changed inside the Activity lifecycle in the call to saveInstance, but I here is how I solved this :
#Override
protected void onSaveInstanceState(Bundle outState) {
//No call for super(). Bug on API Level > 11.
}
I just do not make the call to .super() and everything works great. I hope this will save you some time.
EDIT: after some more research, this is a known bug in the support package.
If you need to save the instance, and add something to your outState Bundle you can use the following :
#Override
protected void onSaveInstanceState(Bundle outState) {
outState.putString("WORKAROUND_FOR_BUG_19917_KEY", "WORKAROUND_FOR_BUG_19917_VALUE");
super.onSaveInstanceState(outState);
}
EDIT2: this may also occur if you are trying to perform a transaction after your Activity is gone in background. To avoid this you should use commitAllowingStateLoss()
EDIT3: The above solutions were fixing issues in the early support.v4 libraries from what I can remember. But if you still have issues with this you MUST also read #AlexLockwood 's blog : Fragment Transactions & Activity State Loss
Summary from the blog post (but I strongly recommend you to read it) :
NEVER commit() transactions after onPause() on pre-Honeycomb, and onStop() on post-Honeycomb
Be careful when committing transactions inside Activity lifecycle methods. Use onCreate(), onResumeFragments() and onPostResume()
Avoid performing transactions inside asynchronous callback methods
Use commitAllowingStateLoss() only as a last resort
Looking in Android source code on what causes this issue gives that flag mStateSaved in FragmentManagerImpl class (instance available in Activity) has value true. It is set to true when the back stack is saved (saveAllState) on call from Activity#onSaveInstanceState.
Afterwards the calls from ActivityThread don't reset this flag using available reset methods from FragmentManagerImpl#noteStateNotSaved() and dispatch().
The way I see it there are some available fixes, depending on what your app is doing and using:
Good ways
Before anything else: I would advertise Alex Lockwood article. Then, from what I've done so far:
For fragments and activities that don't need to keep any state information, call commitAllowStateLoss. Taken from documentation:
Allows the commit to be executed after an activity's state is saved. This is dangerous because the commit can be lost if the activity needs to later be restored from its state, so this should only be used for cases where it is okay for the UI state to change unexpectedly on the user`. I guess this is alright to use if the fragment is showing read-only information. Or even if they do show editable info, use the callbacks methods to retain the edited info.
Just after the transaction is commit (you just called commit()), make a call to FragmentManager.executePendingTransactions().
Not recommended ways:
As Ovidiu Latcu mentioned above, don't call super.onSaveInstanceState(). But this means you will lose the whole state of your activity along with fragments state.
Override onBackPressed and in there call only finish(). This should be OK if you application doesn't use Fragments API; as in super.onBackPressed there is a call to FragmentManager#popBackStackImmediate().
If you are using both Fragments API and the state of your activity is important/vital, then you could try to call using reflection API FragmentManagerImpl#noteStateNotSaved(). But this is a hack, or one could say it's a workaround. I don't like it, but in my case it's quite acceptable since I have a code from a legacy app that uses deprecated code (TabActivity and implicitly LocalActivityManager).
Below is the code that uses reflection:
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
invokeFragmentManagerNoteStateNotSaved();
}
#SuppressWarnings({ "rawtypes", "unchecked" })
private void invokeFragmentManagerNoteStateNotSaved() {
/**
* For post-Honeycomb devices
*/
if (Build.VERSION.SDK_INT < 11) {
return;
}
try {
Class cls = getClass();
do {
cls = cls.getSuperclass();
} while (!"Activity".equals(cls.getSimpleName()));
Field fragmentMgrField = cls.getDeclaredField("mFragments");
fragmentMgrField.setAccessible(true);
Object fragmentMgr = fragmentMgrField.get(this);
cls = fragmentMgr.getClass();
Method noteStateNotSavedMethod = cls.getDeclaredMethod("noteStateNotSaved", new Class[] {});
noteStateNotSavedMethod.invoke(fragmentMgr, new Object[] {});
Log.d("DLOutState", "Successful call for noteStateNotSaved!!!");
} catch (Exception ex) {
Log.e("DLOutState", "Exception on worka FM.noteStateNotSaved", ex);
}
}
Cheers!
Such an exception will occur if you try to perform a fragment transition after your fragment activity's onSaveInstanceState() gets called.
One reason this can happen, is if you leave an AsyncTask (or Thread) running when an activity gets stopped.
Any transitions after onSaveInstanceState() is called could potentially get lost if the system reclaims the activity for resources and recreates it later.
Simply call super.onPostResume() before showing your fragment or move your code in onPostResume() method after calling super.onPostResume(). This solve the problem!
This can also happen when calling dismiss() on a dialog fragment after the screen has been locked\blanked and the Activity + dialog's instance state has been saved. To get around this call:
dismissAllowingStateLoss()
Literally every single time I'm dismissing a dialog i don't care about it's state anymore anyway, so this is ok to do - you're not actually losing any state.
Short And working Solution :
Follow Simple Steps :
Step 1 : Override onSaveInstanceState state in respective fragment. And remove super method from it.
#Override
public void onSaveInstanceState(Bundle outState) {
};
Step 2 : Use CommitAllowingStateLoss(); instead of commit(); while fragment operations.
fragmentTransaction.commitAllowingStateLoss();
I think Lifecycle state can help to prevent such crash starting from Android support lib v26.1.0 you can have the following check:
if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED)){
// Do fragment's transaction commit
}
or you can try:
Fragment.isStateSaved()
more information here
https://developer.android.com/reference/android/support/v4/app/Fragment.html#isStateSaved()
this worked for me... found this out on my own... hope it helps you!
1) do NOT have a global "static" FragmentManager / FragmentTransaction.
2) onCreate, ALWAYS initialize the FragmentManager again!
sample below :-
public abstract class FragmentController extends AnotherActivity{
protected FragmentManager fragmentManager;
protected FragmentTransaction fragmentTransaction;
protected Bundle mSavedInstanceState;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mSavedInstanceState = savedInstanceState;
setDefaultFragments();
}
protected void setDefaultFragments() {
fragmentManager = getSupportFragmentManager();
//check if on orientation change.. do not re-add fragments!
if(mSavedInstanceState == null) {
//instantiate the fragment manager
fragmentTransaction = fragmentManager.beginTransaction();
//the navigation fragments
NavigationFragment navFrag = new NavigationFragment();
ToolbarFragment toolFrag = new ToolbarFragment();
fragmentTransaction.add(R.id.NavLayout, navFrag, "NavFrag");
fragmentTransaction.add(R.id.ToolbarLayout, toolFrag, "ToolFrag");
fragmentTransaction.commitAllowingStateLoss();
//add own fragment to the nav (abstract method)
setOwnFragment();
}
}
I was always getting this when I tried to show fragment in onActivityForResult() method, so the problem was next:
My Activity is paused and stopped, which means, that onSaveInstanceState() was called already (for both pre-Honeycomb and post-Honeycomb devices).
In case of any result I made transaction to show/hide fragment, which causes this IllegalStateException.
What I made is next:
Added value for determining if action I want was done (e.g. taking photo from camere - isPhotoTaken) - it can be boolean or integer value depending how much different transactions you need.
In overriden onResumeFragments() method I checked for my value and after made fragment transactions I needed. In this case commit() was not done after onSaveInstanceState, as state was returned in onResumeFragments() method.
I solved the issue with onconfigurationchanged. The trick is that according to android activity life cycle, when you explicitly called an intent(camera intent, or any other one); the activity is paused and onsavedInstance is called in that case. When rotating the device to a different position other than the one during which the activity was active; doing fragment operations such as fragment commit causes Illegal state exception. There are lots of complains about it. It's something about android activity lifecycle management and proper method calls.
To solve it I did this:
1-Override the onsavedInstance method of your activity, and determine the current screen orientation(portrait or landscape) then set your screen orientation to it before your activity is paused. that way the activity you lock the screen rotation for your activity in case it has been rotated by another one.
2-then , override onresume method of activity, and set your orientation mode now to sensor so that after onsaved method is called it will call one more time onconfiguration to deal with the rotation properly.
You can copy/paste this code into your activity to deal with it:
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
Toast.makeText(this, "Activity OnResume(): Lock Screen Orientation ", Toast.LENGTH_LONG).show();
int orientation =this.getDisplayOrientation();
//Lock the screen orientation to the current display orientation : Landscape or Potrait
this.setRequestedOrientation(orientation);
}
//A method found in stackOverflow, don't remember the author, to determine the right screen orientation independently of the phone or tablet device
public int getDisplayOrientation() {
Display getOrient = getWindowManager().getDefaultDisplay();
int orientation = getOrient.getOrientation();
// Sometimes you may get undefined orientation Value is 0
// simple logic solves the problem compare the screen
// X,Y Co-ordinates and determine the Orientation in such cases
if (orientation == Configuration.ORIENTATION_UNDEFINED) {
Configuration config = getResources().getConfiguration();
orientation = config.orientation;
if (orientation == Configuration.ORIENTATION_UNDEFINED) {
// if height and widht of screen are equal then
// it is square orientation
if (getOrient.getWidth() == getOrient.getHeight()) {
orientation = Configuration.ORIENTATION_SQUARE;
} else { //if widht is less than height than it is portrait
if (getOrient.getWidth() < getOrient.getHeight()) {
orientation = Configuration.ORIENTATION_PORTRAIT;
} else { // if it is not any of the above it will defineitly be landscape
orientation = Configuration.ORIENTATION_LANDSCAPE;
}
}
}
}
return orientation; // return value 1 is portrait and 2 is Landscape Mode
}
#Override
public void onResume() {
super.onResume();
Toast.makeText(this, "Activity OnResume(): Unlock Screen Orientation ", Toast.LENGTH_LONG).show();
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR);
}
I had the same problem, getting IllegalStateException, but replacing all my calls to commit() with commitAllowingStateLoss() did not help.
The culprit was a call to DialogFragment.show().
I surround it with
try {
dialog.show(transaction, "blah blah");
}
catch(IllegalStateException e) {
return;
}
and that did it. OK, I don't get to show the dialog, but in this case that was fine.
It was the only place in my app where I first called FragmentManager.beginTransaction() but never called commit() so I did not find it when I looked for "commit()".
The funny thing is, the user never leaves the app. Instead the killer was an AdMob interstitial ad showing up.
My solution for that problem was
In fragment add methods:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
...
guideMapFragment = (SupportMapFragment)a.getSupportFragmentManager().findFragmentById(R.id.guideMap);
guideMap = guideMapFragment.getMap();
...
}
#Override
public void onDestroyView() {
SherlockFragmentActivity a = getSherlockActivity();
if (a != null && guideMapFragment != null) {
try {
Log.i(LOGTAG, "Removing map fragment");
a.getSupportFragmentManager().beginTransaction().remove(guideMapFragment).commit();
guideMapFragment = null;
} catch(IllegalStateException e) {
Log.i(LOGTAG, "IllegalStateException on exit");
}
}
super.onDestroyView();
}
May be bad, but couldn't find anything better.
I got this issue.But I think this problem is not related to commit and commitAllowStateLoss.
The following stack trace and exception message is about commit().
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1341)
at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1352)
at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:595)
at android.support.v4.app.BackStackRecord.commit(BackStackRecord.java:574)
But this exception was caused by onBackPressed()
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at android.support.v4.app.FragmentManagerImpl.checkStateLoss(Unknown Source)
at android.support.v4.app.FragmentManagerImpl.popBackStackImmediate(Unknown Source)
at android.support.v4.app.FragmentActivity.onBackPressed(Unknown Source)
They were all caused by checkStateLoss()
private void checkStateLoss() {
if (mStateSaved) {
throw new IllegalStateException(
"Can not perform this action after onSaveInstanceState");
}
if (mNoTransactionsBecause != null) {
throw new IllegalStateException(
"Can not perform this action inside of " + mNoTransactionsBecause);
}
mStateSaved will be true after onSaveInstanceState.
This problem rarely happens.I have never encountered this problem.I can not reoccurrence the problem.
I found issue 25517
It might have occurred in the following circumstances
Back key is called after onSaveInstanceState, but before the new activity is started.
use onStop() in code
I'm not sure what the root of the problem is.
So I used an ugly way.
#Override
public void onBackPressed() {
try{
super.onBackPressed();
}catch (IllegalStateException e){
// can output some information here
finish();
}
}
I have got the same issue in my App. I have been solved this issue just calling the super.onBackPressed(); on previous class and calling the commitAllowingStateLoss() on the current class with that fragment.
onSaveInstance will be called if a user rotates the screen so that it can load resources associated with the new orientation.
It's possible that this user rotated the screen followed by pressing the back button (because it's also possible that this user fumbled their phone while using your app)
Another lifecycle way to solve the issue is to use the latest released lifecycle-ktx with kotlin.
lifecycleScope.launchWhenResumed {
// your code with fragment or dialogfragment
}
The closure will be run after resume state, so even this method is called after
stop, it'll be safly excuted when the next resume come.
You can also choose like
lifecycleScope.launchWhenCreated
// or
lifecycleScope.launchWhenStarted
to fit your situation.
The code will be cancelled when destroy is come.
The Google document link:
https://developer.android.com/kotlin/ktx#lifecycle
Read
http://chris-alexander.co.uk/on-engineering/dev/android-fragments-within-fragments/
article.
fragment.isResumed() checking helps me in onDestroyView w/o using onSaveInstanceState method.
Same issue from me and after a day long analysis of all articles, blog and stackoverflow i've found a simple solution. Don't use savedInstanceState at all, this is the condition with one line of code. On the fragment code:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(null);
.....
This happens whenever you are trying to load a fragment but the activity has changed its state to onPause().This happens for example when you try to fetch data and load it to the activity but by the time the user has clicked some button and has moved to next activity.
You can solve this in two ways
You can use transaction.commitAllowingStateLoss() instead of transaction.commit() to load fragment but you may end up losing commit operation that is done.
or
Make sure that activity is in resume and not going to pause state when loading a fragment.
Create a boolean and check if activity is not going to onPause() state.
#Override
public void onResume() {
super.onResume();
mIsResumed = true;
}
#Override
public void onPause() {
mIsResumed = false;
super.onPause();
}
then while loading fragment check if activity is present and load only when activity is foreground.
if(mIsResumed){
//load the fragment
}
Thanks #gunar, but I think there is a better way.
According to doc :
* If you are committing a single transaction that does not modify the
* fragment back stack, strongly consider using
* {#link FragmentTransaction#commitNow()} instead. This can help avoid
* unwanted side effects when other code in your app has pending committed
* transactions that expect different timing.
*
* #return Returns true if there were any pending transactions to be
* executed.
*/
public abstract boolean executePendingTransactions();
So use commitNow to replace:
fragmentTransaction.commit();
FragmentManager.executePendingTransactions()
Well, after trying all the above solutions without success (because basically i dont have transactions).
On my case i was using AlertDialogs and ProgressDialog as fragments that, sometimes, on rotation, when asking for the FragmentManager, the error rises.
I found a workaround mixing some many similar posts:
Its a 3 step solution, all done on your FragmentActivity (in this case, its called GenericActivity):
private static WeakReference<GenericActivity> activity = null; //To avoid bug for fragments: Step 1 of 3
#Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
//To avoid bug for fragments: Step 2 of 3
activity = new WeakReference<GenericActivity>(this);
}
#Override
public FragmentManager getSupportFragmentManager(){
//To avoid bug for fragments: Step 3 of 3
if (this == activity.get()) {
return super.getSupportFragmentManager();
}
return activity.get().getSupportFragmentManager();
}
When i use startactivity in one fragment, i will get this exception;
When i change to use startactivityforresult, the exception is gone :)
So the easy way to fix it is use the startActivityForResult api :)
I was getting this exception when I was pressing back button to cancel intent chooser on my map fragment activity.
I resolved this by replacing the code of onResume()(where I was initializing the fragment and committing transaction) to onStart() and the app is working fine now.
Hope it helps.
This is fixed in Android 4.2 and also in the support library's source.[*]
For details of the cause (and work-arounds) refer to the the Google bug report:
http://code.google.com/p/android/issues/detail?id=19917
If you're using the support library then you shouldn't have to worry about this bug (for long)[*]. However, if you're using the API directly (i.e. Not using the support library's FragmentManager) and targeting an API below Android 4.2 then you will need to try one of the work-arounds.
[*] At the time of writing the Android SDK Manager is still distributing an old version that exhibits this bug.
Edit I'm going to add some clarification here because I've obviously somehow confused whoever down-voted this answer.
There are several different (but related) circumstances that can cause this exception to be thrown. My answer above is referring to the specific instance discussed in the question i.e. a bug in Android which has subsequently been fixed. If you're getting this exception for another reason it's because you're adding/removing fragments when you shouldn't be (after fragment states have been saved). If you're in such a situation then perhaps "Nested Fragments - IllegalStateException “Can not perform this action after onSaveInstanceState”" can be of use to you.
After researching a bit the solution to this problem is to do your fragment commits in the onresume.
Source: https://wenchaojames.wordpress.com/2013/01/12/illegalstateexception-from-onactivityresult/
My use case: I have used listener in fragment to notify activity that some thing happened. I did new fragment commit on callback method. This works perfectly fine on first time. But on orientation change the activity is recreated with saved instance state. In that case fragment is not created again implies that the fragment have the listener which is old destroyed activity. Any way the call back method will get triggered on action. It goes to destroyed activity which cause the issue. The solution is to reset the listener in fragment with current live activity. This solve the problem.
What I found is that if another app is dialog type and allows touches to be sent to background app then almost any background app will crash with this error.
I think we need to check every time a transaction is performed if the instance was saved or restored.
In my case, with the same error exception, i put the "onBackPressed()" in a runnable (you can use any of your view):
myView.post(new Runnable() {
#Override
public void run() {
onBackPressed()
}
});
I do not understand why, but it works!
You may be calling fragmentManager.popBackStackImmediate(); when activity is paused. Activity is not finished but is paused and not on foreground. You need to check whether activity is paused or not before popBackStackImmediate().
I noticed something very interesting. I have in my app the option to open the phone's gallery and the device asks what app to use, there I click on the gray area away from the dialog and saw this issue. I noticed how my activity goes from onPause, onSaveInstanceState back to onResume, it doesn't happen to visit onCreateView. I am doing transactions at onResume. So what I ended up doing is setting a flag being negated onPause, but being true onCreateView. if the flag is true onResume then do onCommit, otherwise commitAllowingStateLoss. I could go on and waste so much time but I wanted to check the lifecycle. I have a device which is sdkversion 23, and I don't get this issue, but I have another one which is 21, and there I see it.

Loader not being called when screen orientation change

I facing a bit of trouble when using AsyncTaskLoader and the screen orientation changes. Let me give you some context on how my app is structured: I have a very simple app which fetches results from a url and displays it. The app consists of one FragmentActivity and three Fragments. My three fragment are as follows:
First fragment is an edittext and a button to submit input.
Second fragment shows a loading spinner
Third fragment replaces the loading-fragment when the data is fetched to show the results.
The AsyncTaskLoader is used to load data from either a content provider (if it is cached) or from the network.
Everything works great until the screen oreintaion changes! I looked at the output of LoaderManager.enableDebugLogging(true) and it seems the source of the problem is that LoaderCallbacks.onLoadFinished doesn't get called in my activity.
Here is a piece of the code that launches the loader:
/* This function is defined in the "first" fragment in an interface and
* implemented in the activity */
#Override
public void onFormSubmission(String word) {
showLoadingFragment();
if ( mWord == null || mWord.equals(word) ) {
getSupportLoaderManager().initLoader(0, word, this);
} else {
getSupportLoaderManager().restartLoader(0, word, this);
}
// Store the word
mWord = word;
}
The code that displays the result:
#Override
public void onLoadFinished(Loader<Bundle> loader, Bundle data) {
// Validation and other stuff
ResultFragment fragment = new ResultFragment();
// Add the fragment to the 'result_fragment_container' FrameLayout
getSupportFragmentManager()
.beginTransaction()
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
.setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out)
// Replace loading fragment with the result
.replace(R.id.result_fragment_container, fragment)
.commitAllowingStateLoss();
}
Approaches I tried but failed:
Setting setRetaininstance(true) in fragments.
Setting android:configChanges="orientation|keyboardHidden|keyboard" in the manifest.
I'm out of ideas, so any help would be appreciated.
You should put getSupportLoaderManager().initLoader(0, null, this); in the onActivityCreated method. The initLoader will create or reconnect with a loader if it already exists
Hi I can suggest 2 possible solutions:
1) From your question I'm not entirely sure what process your performing which is never calling OnLoadFinished() in the AsyncTask. I'll assume is a Async DB call or Web server call etc?
Regardless the onLoadFinished() probably doesn't get called because your Activity is Destroyed on rotation. you need to save the state you updated before rotation and onDestroy() is called
You can try keep a class variable status flag. This is set in your "update" Async task which flags whether your Async task is still running ie "updating" or not.
So:
bUpdating = true when your update async task starts. Ie within
onPreExecute()
bUpdating = false when its finished. Ie within
onPostExecute()
Then when onDestroy() is called (always called before the screen rotate) you could check bUpdating. IF TRUE then you could save all details on screen, which would have been saved in the Async task, to SharePreferences
Within onResume() check your specific SharePreferences variables and if they exist then kickoff the Async update again.
So in short this will:
save details to SharedPreferences BEFORE screen rotation - onDestroy() - if boolean says Async task running.
onResume() AFTER screen rotation check SharedPreferences and save details via Async.
Following this you should be guaranteed that your onLoadFinished() method is called.
There is probably a more reliable way also to check for Screen Rotation, i haven't done much with it.
2) Of course another alternative is to actually block screen rotation entirely on your particular Activity (because really, do you NEED rotation on that screen?).
I have an application which records video in background and uploads 10second segments. It was causing large headaches with screen rotation and my Async tasks, so I blocked rotation entirely as it was completely unnecessary. Here is code for the AndroidManifest.xml file to lock Screen orientation as Portrait:
<activity
android:name="com.blah.YourActivity"
android:label="Register"
android:screenOrientation="portrait">
</activity>
I hope that can help you out
Edit: Also, could be better more categorical ways to know that screen is rotating:
ie this sort of code:
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
Log.e(TAG, "ORIENTATION_LANDSCAPE");
} else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
Log.e(TAG, "ORIENTATION_PORTRAIT");
}
}
Example taken from this post
you could just put your SharePreferences save in there. but onResume() will still be used to grab your details back from.
Also a link to SharePreferences incase you've not used it before
Edit2!: One other thing. I just re-read your question, and you mention "Loader" for some reason i had it in my head this was saving data, not loading. Have you tried simply re-calling AsyncTaskLoader() when onResume() is called?? If your not saving then you can ignore everything i said about retaining your data to SharePreferences because you can just kickoff the load Async task again at onResume() (which runs after Rotation)

how to retain the state of activity in android

How do I retain the state of an activity in android? I have two layouts for portrait and landscape in layout and layout-land. I am loading the value from service at the time I am showing progress dialog. If loaded user rotates the device to landscape at the time also loading. How do I avoid that? user typed content in webview that also refreshed. How do I avoid that, can anybody provide an example?
Thanks
When orientation changes, the Activity is reloaded by default. If you do not want this behavior then add this to the Activity definition in your manifest:
android:configChanges="orientation|keyboardHidden"
For more detail, see Handling Runtime Changes
You can use the onRetainNonConfigurationChange() callback to store arbitrary data. It is called just before your application is about to be recreated.
Then, in onCreate() just check if some data were put aside by calling getLastNonConfigurationInstance() that returns the Object you put aside or null.
See this article on android developers.
Here's a sample borrowed from the link above:
#Override
public Object onRetainNonConfigurationInstance() {
//this is called by the framework when needed
//Just return what you want to save here.
return MyBigObjectThatContainsEverythingIWantToSave;
}
Automagic restore of previously saved state:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
final MyDataObject MyBigObjectThatContainsEverythingIWantToSave = (MyDataObject) getLastNonConfigurationInstance();
if (MyBigObjectThatContainsEverythingIWantToSave == null) {
//No saved state
MyBigObjectThatContainsEverythingIWantToSave = loadMyData();
} else {
//State was restored, no need to download again.
}
...
}
When orientation changes, the Activity is reloaded by default. If you do not want this behavior then add this to the Activity definition in your android manifest file :
android:configChanges="orientation|screenSize|keyboardHidden"

getting exception "IllegalStateException: Can not perform this action after onSaveInstanceState"

I have a Live Android application, and from market i have received following stack trace and i have no idea why its happening as its not happening in application code but its getting caused by some or the other event from the application (assumption)
I am not using Fragments, still there is a reference of FragmentManager.
If any body can throw some light on some hidden facts to avoid this type of issue:
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at android.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1109)
at android.app.FragmentManagerImpl.popBackStackImmediate(FragmentManager.java:399)
at android.app.Activity.onBackPressed(Activity.java:2066)
at android.app.Activity.onKeyDown(Activity.java:1962)
at android.view.KeyEvent.dispatch(KeyEvent.java:2482)
at android.app.Activity.dispatchKeyEvent(Activity.java:2274)
at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchKeyEvent(PhoneWindow.java:1668)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at com.android.internal.policy.impl.PhoneWindow$DecorView.superDispatchKeyEvent(PhoneWindow.java:1720)
at com.android.internal.policy.impl.PhoneWindow.superDispatchKeyEvent(PhoneWindow.java:1258)
at android.app.Activity.dispatchKeyEvent(Activity.java:2269)
at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchKeyEvent(PhoneWindow.java:1668)
at android.view.ViewRoot.deliverKeyEventPostIme(ViewRoot.java:2851)
at android.view.ViewRoot.handleFinishedEvent(ViewRoot.java:2824)
at android.view.ViewRoot.handleMessage(ViewRoot.java:2011)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:132)
at android.app.ActivityThread.main(ActivityThread.java:4025)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:491)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:841)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:599)
at dalvik.system.NativeStart.main(Native Method)
This is the most stupid bug I have encountered so far. I had a Fragment application working perfectly for API < 11, and Force Closing on API > 11.
I really couldn't figure out what they changed inside the Activity lifecycle in the call to saveInstance, but I here is how I solved this :
#Override
protected void onSaveInstanceState(Bundle outState) {
//No call for super(). Bug on API Level > 11.
}
I just do not make the call to .super() and everything works great. I hope this will save you some time.
EDIT: after some more research, this is a known bug in the support package.
If you need to save the instance, and add something to your outState Bundle you can use the following :
#Override
protected void onSaveInstanceState(Bundle outState) {
outState.putString("WORKAROUND_FOR_BUG_19917_KEY", "WORKAROUND_FOR_BUG_19917_VALUE");
super.onSaveInstanceState(outState);
}
EDIT2: this may also occur if you are trying to perform a transaction after your Activity is gone in background. To avoid this you should use commitAllowingStateLoss()
EDIT3: The above solutions were fixing issues in the early support.v4 libraries from what I can remember. But if you still have issues with this you MUST also read #AlexLockwood 's blog : Fragment Transactions & Activity State Loss
Summary from the blog post (but I strongly recommend you to read it) :
NEVER commit() transactions after onPause() on pre-Honeycomb, and onStop() on post-Honeycomb
Be careful when committing transactions inside Activity lifecycle methods. Use onCreate(), onResumeFragments() and onPostResume()
Avoid performing transactions inside asynchronous callback methods
Use commitAllowingStateLoss() only as a last resort
Looking in Android source code on what causes this issue gives that flag mStateSaved in FragmentManagerImpl class (instance available in Activity) has value true. It is set to true when the back stack is saved (saveAllState) on call from Activity#onSaveInstanceState.
Afterwards the calls from ActivityThread don't reset this flag using available reset methods from FragmentManagerImpl#noteStateNotSaved() and dispatch().
The way I see it there are some available fixes, depending on what your app is doing and using:
Good ways
Before anything else: I would advertise Alex Lockwood article. Then, from what I've done so far:
For fragments and activities that don't need to keep any state information, call commitAllowStateLoss. Taken from documentation:
Allows the commit to be executed after an activity's state is saved. This is dangerous because the commit can be lost if the activity needs to later be restored from its state, so this should only be used for cases where it is okay for the UI state to change unexpectedly on the user`. I guess this is alright to use if the fragment is showing read-only information. Or even if they do show editable info, use the callbacks methods to retain the edited info.
Just after the transaction is commit (you just called commit()), make a call to FragmentManager.executePendingTransactions().
Not recommended ways:
As Ovidiu Latcu mentioned above, don't call super.onSaveInstanceState(). But this means you will lose the whole state of your activity along with fragments state.
Override onBackPressed and in there call only finish(). This should be OK if you application doesn't use Fragments API; as in super.onBackPressed there is a call to FragmentManager#popBackStackImmediate().
If you are using both Fragments API and the state of your activity is important/vital, then you could try to call using reflection API FragmentManagerImpl#noteStateNotSaved(). But this is a hack, or one could say it's a workaround. I don't like it, but in my case it's quite acceptable since I have a code from a legacy app that uses deprecated code (TabActivity and implicitly LocalActivityManager).
Below is the code that uses reflection:
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
invokeFragmentManagerNoteStateNotSaved();
}
#SuppressWarnings({ "rawtypes", "unchecked" })
private void invokeFragmentManagerNoteStateNotSaved() {
/**
* For post-Honeycomb devices
*/
if (Build.VERSION.SDK_INT < 11) {
return;
}
try {
Class cls = getClass();
do {
cls = cls.getSuperclass();
} while (!"Activity".equals(cls.getSimpleName()));
Field fragmentMgrField = cls.getDeclaredField("mFragments");
fragmentMgrField.setAccessible(true);
Object fragmentMgr = fragmentMgrField.get(this);
cls = fragmentMgr.getClass();
Method noteStateNotSavedMethod = cls.getDeclaredMethod("noteStateNotSaved", new Class[] {});
noteStateNotSavedMethod.invoke(fragmentMgr, new Object[] {});
Log.d("DLOutState", "Successful call for noteStateNotSaved!!!");
} catch (Exception ex) {
Log.e("DLOutState", "Exception on worka FM.noteStateNotSaved", ex);
}
}
Cheers!
Such an exception will occur if you try to perform a fragment transition after your fragment activity's onSaveInstanceState() gets called.
One reason this can happen, is if you leave an AsyncTask (or Thread) running when an activity gets stopped.
Any transitions after onSaveInstanceState() is called could potentially get lost if the system reclaims the activity for resources and recreates it later.
Simply call super.onPostResume() before showing your fragment or move your code in onPostResume() method after calling super.onPostResume(). This solve the problem!
This can also happen when calling dismiss() on a dialog fragment after the screen has been locked\blanked and the Activity + dialog's instance state has been saved. To get around this call:
dismissAllowingStateLoss()
Literally every single time I'm dismissing a dialog i don't care about it's state anymore anyway, so this is ok to do - you're not actually losing any state.
Short And working Solution :
Follow Simple Steps :
Step 1 : Override onSaveInstanceState state in respective fragment. And remove super method from it.
#Override
public void onSaveInstanceState(Bundle outState) {
};
Step 2 : Use CommitAllowingStateLoss(); instead of commit(); while fragment operations.
fragmentTransaction.commitAllowingStateLoss();
I think Lifecycle state can help to prevent such crash starting from Android support lib v26.1.0 you can have the following check:
if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED)){
// Do fragment's transaction commit
}
or you can try:
Fragment.isStateSaved()
more information here
https://developer.android.com/reference/android/support/v4/app/Fragment.html#isStateSaved()
this worked for me... found this out on my own... hope it helps you!
1) do NOT have a global "static" FragmentManager / FragmentTransaction.
2) onCreate, ALWAYS initialize the FragmentManager again!
sample below :-
public abstract class FragmentController extends AnotherActivity{
protected FragmentManager fragmentManager;
protected FragmentTransaction fragmentTransaction;
protected Bundle mSavedInstanceState;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mSavedInstanceState = savedInstanceState;
setDefaultFragments();
}
protected void setDefaultFragments() {
fragmentManager = getSupportFragmentManager();
//check if on orientation change.. do not re-add fragments!
if(mSavedInstanceState == null) {
//instantiate the fragment manager
fragmentTransaction = fragmentManager.beginTransaction();
//the navigation fragments
NavigationFragment navFrag = new NavigationFragment();
ToolbarFragment toolFrag = new ToolbarFragment();
fragmentTransaction.add(R.id.NavLayout, navFrag, "NavFrag");
fragmentTransaction.add(R.id.ToolbarLayout, toolFrag, "ToolFrag");
fragmentTransaction.commitAllowingStateLoss();
//add own fragment to the nav (abstract method)
setOwnFragment();
}
}
I was always getting this when I tried to show fragment in onActivityForResult() method, so the problem was next:
My Activity is paused and stopped, which means, that onSaveInstanceState() was called already (for both pre-Honeycomb and post-Honeycomb devices).
In case of any result I made transaction to show/hide fragment, which causes this IllegalStateException.
What I made is next:
Added value for determining if action I want was done (e.g. taking photo from camere - isPhotoTaken) - it can be boolean or integer value depending how much different transactions you need.
In overriden onResumeFragments() method I checked for my value and after made fragment transactions I needed. In this case commit() was not done after onSaveInstanceState, as state was returned in onResumeFragments() method.
I solved the issue with onconfigurationchanged. The trick is that according to android activity life cycle, when you explicitly called an intent(camera intent, or any other one); the activity is paused and onsavedInstance is called in that case. When rotating the device to a different position other than the one during which the activity was active; doing fragment operations such as fragment commit causes Illegal state exception. There are lots of complains about it. It's something about android activity lifecycle management and proper method calls.
To solve it I did this:
1-Override the onsavedInstance method of your activity, and determine the current screen orientation(portrait or landscape) then set your screen orientation to it before your activity is paused. that way the activity you lock the screen rotation for your activity in case it has been rotated by another one.
2-then , override onresume method of activity, and set your orientation mode now to sensor so that after onsaved method is called it will call one more time onconfiguration to deal with the rotation properly.
You can copy/paste this code into your activity to deal with it:
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
Toast.makeText(this, "Activity OnResume(): Lock Screen Orientation ", Toast.LENGTH_LONG).show();
int orientation =this.getDisplayOrientation();
//Lock the screen orientation to the current display orientation : Landscape or Potrait
this.setRequestedOrientation(orientation);
}
//A method found in stackOverflow, don't remember the author, to determine the right screen orientation independently of the phone or tablet device
public int getDisplayOrientation() {
Display getOrient = getWindowManager().getDefaultDisplay();
int orientation = getOrient.getOrientation();
// Sometimes you may get undefined orientation Value is 0
// simple logic solves the problem compare the screen
// X,Y Co-ordinates and determine the Orientation in such cases
if (orientation == Configuration.ORIENTATION_UNDEFINED) {
Configuration config = getResources().getConfiguration();
orientation = config.orientation;
if (orientation == Configuration.ORIENTATION_UNDEFINED) {
// if height and widht of screen are equal then
// it is square orientation
if (getOrient.getWidth() == getOrient.getHeight()) {
orientation = Configuration.ORIENTATION_SQUARE;
} else { //if widht is less than height than it is portrait
if (getOrient.getWidth() < getOrient.getHeight()) {
orientation = Configuration.ORIENTATION_PORTRAIT;
} else { // if it is not any of the above it will defineitly be landscape
orientation = Configuration.ORIENTATION_LANDSCAPE;
}
}
}
}
return orientation; // return value 1 is portrait and 2 is Landscape Mode
}
#Override
public void onResume() {
super.onResume();
Toast.makeText(this, "Activity OnResume(): Unlock Screen Orientation ", Toast.LENGTH_LONG).show();
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR);
}
I had the same problem, getting IllegalStateException, but replacing all my calls to commit() with commitAllowingStateLoss() did not help.
The culprit was a call to DialogFragment.show().
I surround it with
try {
dialog.show(transaction, "blah blah");
}
catch(IllegalStateException e) {
return;
}
and that did it. OK, I don't get to show the dialog, but in this case that was fine.
It was the only place in my app where I first called FragmentManager.beginTransaction() but never called commit() so I did not find it when I looked for "commit()".
The funny thing is, the user never leaves the app. Instead the killer was an AdMob interstitial ad showing up.
My solution for that problem was
In fragment add methods:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
...
guideMapFragment = (SupportMapFragment)a.getSupportFragmentManager().findFragmentById(R.id.guideMap);
guideMap = guideMapFragment.getMap();
...
}
#Override
public void onDestroyView() {
SherlockFragmentActivity a = getSherlockActivity();
if (a != null && guideMapFragment != null) {
try {
Log.i(LOGTAG, "Removing map fragment");
a.getSupportFragmentManager().beginTransaction().remove(guideMapFragment).commit();
guideMapFragment = null;
} catch(IllegalStateException e) {
Log.i(LOGTAG, "IllegalStateException on exit");
}
}
super.onDestroyView();
}
May be bad, but couldn't find anything better.
I got this issue.But I think this problem is not related to commit and commitAllowStateLoss.
The following stack trace and exception message is about commit().
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1341)
at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1352)
at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:595)
at android.support.v4.app.BackStackRecord.commit(BackStackRecord.java:574)
But this exception was caused by onBackPressed()
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at android.support.v4.app.FragmentManagerImpl.checkStateLoss(Unknown Source)
at android.support.v4.app.FragmentManagerImpl.popBackStackImmediate(Unknown Source)
at android.support.v4.app.FragmentActivity.onBackPressed(Unknown Source)
They were all caused by checkStateLoss()
private void checkStateLoss() {
if (mStateSaved) {
throw new IllegalStateException(
"Can not perform this action after onSaveInstanceState");
}
if (mNoTransactionsBecause != null) {
throw new IllegalStateException(
"Can not perform this action inside of " + mNoTransactionsBecause);
}
mStateSaved will be true after onSaveInstanceState.
This problem rarely happens.I have never encountered this problem.I can not reoccurrence the problem.
I found issue 25517
It might have occurred in the following circumstances
Back key is called after onSaveInstanceState, but before the new activity is started.
use onStop() in code
I'm not sure what the root of the problem is.
So I used an ugly way.
#Override
public void onBackPressed() {
try{
super.onBackPressed();
}catch (IllegalStateException e){
// can output some information here
finish();
}
}
I have got the same issue in my App. I have been solved this issue just calling the super.onBackPressed(); on previous class and calling the commitAllowingStateLoss() on the current class with that fragment.
onSaveInstance will be called if a user rotates the screen so that it can load resources associated with the new orientation.
It's possible that this user rotated the screen followed by pressing the back button (because it's also possible that this user fumbled their phone while using your app)
Another lifecycle way to solve the issue is to use the latest released lifecycle-ktx with kotlin.
lifecycleScope.launchWhenResumed {
// your code with fragment or dialogfragment
}
The closure will be run after resume state, so even this method is called after
stop, it'll be safly excuted when the next resume come.
You can also choose like
lifecycleScope.launchWhenCreated
// or
lifecycleScope.launchWhenStarted
to fit your situation.
The code will be cancelled when destroy is come.
The Google document link:
https://developer.android.com/kotlin/ktx#lifecycle
Read
http://chris-alexander.co.uk/on-engineering/dev/android-fragments-within-fragments/
article.
fragment.isResumed() checking helps me in onDestroyView w/o using onSaveInstanceState method.
Same issue from me and after a day long analysis of all articles, blog and stackoverflow i've found a simple solution. Don't use savedInstanceState at all, this is the condition with one line of code. On the fragment code:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(null);
.....
This happens whenever you are trying to load a fragment but the activity has changed its state to onPause().This happens for example when you try to fetch data and load it to the activity but by the time the user has clicked some button and has moved to next activity.
You can solve this in two ways
You can use transaction.commitAllowingStateLoss() instead of transaction.commit() to load fragment but you may end up losing commit operation that is done.
or
Make sure that activity is in resume and not going to pause state when loading a fragment.
Create a boolean and check if activity is not going to onPause() state.
#Override
public void onResume() {
super.onResume();
mIsResumed = true;
}
#Override
public void onPause() {
mIsResumed = false;
super.onPause();
}
then while loading fragment check if activity is present and load only when activity is foreground.
if(mIsResumed){
//load the fragment
}
Thanks #gunar, but I think there is a better way.
According to doc :
* If you are committing a single transaction that does not modify the
* fragment back stack, strongly consider using
* {#link FragmentTransaction#commitNow()} instead. This can help avoid
* unwanted side effects when other code in your app has pending committed
* transactions that expect different timing.
*
* #return Returns true if there were any pending transactions to be
* executed.
*/
public abstract boolean executePendingTransactions();
So use commitNow to replace:
fragmentTransaction.commit();
FragmentManager.executePendingTransactions()
Well, after trying all the above solutions without success (because basically i dont have transactions).
On my case i was using AlertDialogs and ProgressDialog as fragments that, sometimes, on rotation, when asking for the FragmentManager, the error rises.
I found a workaround mixing some many similar posts:
Its a 3 step solution, all done on your FragmentActivity (in this case, its called GenericActivity):
private static WeakReference<GenericActivity> activity = null; //To avoid bug for fragments: Step 1 of 3
#Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
//To avoid bug for fragments: Step 2 of 3
activity = new WeakReference<GenericActivity>(this);
}
#Override
public FragmentManager getSupportFragmentManager(){
//To avoid bug for fragments: Step 3 of 3
if (this == activity.get()) {
return super.getSupportFragmentManager();
}
return activity.get().getSupportFragmentManager();
}
When i use startactivity in one fragment, i will get this exception;
When i change to use startactivityforresult, the exception is gone :)
So the easy way to fix it is use the startActivityForResult api :)
I was getting this exception when I was pressing back button to cancel intent chooser on my map fragment activity.
I resolved this by replacing the code of onResume()(where I was initializing the fragment and committing transaction) to onStart() and the app is working fine now.
Hope it helps.
This is fixed in Android 4.2 and also in the support library's source.[*]
For details of the cause (and work-arounds) refer to the the Google bug report:
http://code.google.com/p/android/issues/detail?id=19917
If you're using the support library then you shouldn't have to worry about this bug (for long)[*]. However, if you're using the API directly (i.e. Not using the support library's FragmentManager) and targeting an API below Android 4.2 then you will need to try one of the work-arounds.
[*] At the time of writing the Android SDK Manager is still distributing an old version that exhibits this bug.
Edit I'm going to add some clarification here because I've obviously somehow confused whoever down-voted this answer.
There are several different (but related) circumstances that can cause this exception to be thrown. My answer above is referring to the specific instance discussed in the question i.e. a bug in Android which has subsequently been fixed. If you're getting this exception for another reason it's because you're adding/removing fragments when you shouldn't be (after fragment states have been saved). If you're in such a situation then perhaps "Nested Fragments - IllegalStateException “Can not perform this action after onSaveInstanceState”" can be of use to you.
After researching a bit the solution to this problem is to do your fragment commits in the onresume.
Source: https://wenchaojames.wordpress.com/2013/01/12/illegalstateexception-from-onactivityresult/
My use case: I have used listener in fragment to notify activity that some thing happened. I did new fragment commit on callback method. This works perfectly fine on first time. But on orientation change the activity is recreated with saved instance state. In that case fragment is not created again implies that the fragment have the listener which is old destroyed activity. Any way the call back method will get triggered on action. It goes to destroyed activity which cause the issue. The solution is to reset the listener in fragment with current live activity. This solve the problem.
What I found is that if another app is dialog type and allows touches to be sent to background app then almost any background app will crash with this error.
I think we need to check every time a transaction is performed if the instance was saved or restored.
In my case, with the same error exception, i put the "onBackPressed()" in a runnable (you can use any of your view):
myView.post(new Runnable() {
#Override
public void run() {
onBackPressed()
}
});
I do not understand why, but it works!
You may be calling fragmentManager.popBackStackImmediate(); when activity is paused. Activity is not finished but is paused and not on foreground. You need to check whether activity is paused or not before popBackStackImmediate().
I noticed something very interesting. I have in my app the option to open the phone's gallery and the device asks what app to use, there I click on the gray area away from the dialog and saw this issue. I noticed how my activity goes from onPause, onSaveInstanceState back to onResume, it doesn't happen to visit onCreateView. I am doing transactions at onResume. So what I ended up doing is setting a flag being negated onPause, but being true onCreateView. if the flag is true onResume then do onCommit, otherwise commitAllowingStateLoss. I could go on and waste so much time but I wanted to check the lifecycle. I have a device which is sdkversion 23, and I don't get this issue, but I have another one which is 21, and there I see it.

Android: How to detect if current stack of activities (task) moves to background?

The official documentation describes tasks as follows:
*All the activities in a task move together as a unit. The entire task (the entire activity stack) can be brought to the foreground or sent to the background. Suppose, for instance, that the current task has four activities in its stack — three under the current activity. The user presses the HOME key, goes to the application launcher, and selects a new application (actually, a new task). The current task goes into the background and the root activity for the new task is displayed. Then, after a short period, the user goes back to the home screen and again selects the previous application (the previous task). That task, with all four activities in the stack, comes forward.
Is there a way to programmatically detect when the task of the current Activity moves into and out of the background? I would like to know when the user has switched switched to another application, vs. when the user navigated to another Activity in the current app.
Note: I've always had this design in my head but never got around to concrete tests (reasonably sure it works though), so if you end up trying this, tell me how it goes. :)
I don't think there's an official API method that'll give you this functionality (I may be wrong), but you can certainly construct your own. This is just my way of doing it; there may, of course, be better implementations.
In your application, you always know when you're starting another activity. With that said, you can create a global class that handles starting all of your application activities and keeps track of a boolean that represents when you're starting an activity:
public class GlobalActivityManager {
private static boolean isActivityStarting = true;
public static boolean isActivityStarting() {
return isActivityStarting;
}
public static void setIsActivityStarting(boolean newValue) {
isActivityStarting = newValue;
}
public static void startActivity(Activity previous, Class activityClass) {
isActivityStarting = true;
// Do stuff to start your activity
isActivityStarting = false;
}
}
Then in each of your activities (maybe make this a superclass), override the onPause() method to check if it's another one of your application's activities starting or a foreign task's application coming in front of your application. This works because onPause is executed when anything comes in front of the activity, meaning it's called when you start your activity in GlobalActivityManager where isActivityStarting is true.
public void onPause() {
super.onPause();
if (GlobalActivityManager.isActivityStarting()) {
// It's one of your application's activities. Everything's normal.
} else {
// Some other task has come to the foreground. Do what needs to be done.
}
}
public boolean isCurrentTaskInBackground() {
List<RunningTaskInfo> taskInfoList = mActivityManager.getRunningTasks(1);
if (taskInfoList != null && taskInfoList.size() > 0) {
RunningTaskInfo info = taskInfoList.get(0);
ComponentName componentName = info.baseActivity;
MobileTvLog.debug("App#isCurrentTaskInBackground -> baseActivity = " + componentName);
String packageName = componentName.getPackageName();
return !YOUR_PKG_NAME.equals(packageName);
} else {
return true;
}
}
Maybe this can be helpfull, tell me if it worked for you.
only when you return from background the value of activities would be 0 (zero)
the rest of the time would be a number higher than 0(zero) when the onRestart()
is executed.
public class FatherClass extends Activity {
private static int activities = 0;
public void onCreate(Bundle savedInstanceState, String clase) {
super.onCreate(savedInstanceState);
}
protected void onRestart()
{
super.onRestart();
if(activities == 0){
Log.i("APP","BACK FROM BACKGROUND");
}
}
protected void onStop(){
super.onStop();
activities -= 1;
}
protected void onStart(){
super.onStart();
activities += 1;
}
}
All of your classes must extend from this class for this to work.
That will not work. Your activity could get an onPause for reasons other than your app explicitly starting another activity. For instance, the user could have hit the back button, taking you back to a previous Activity in your stack.
Try to read about android:finishOnTaskLaunch
http://developer.android.com/guide/topics/manifest/activity-element.html#finish
It solved my similiar problem ;-)
I've been looking for a good answer to this question. It seems Andy Zhang had a good idea, but it may need a bit more work to handle other things, like the Back button.
Another approach which seems promising is to use the ActivityManager.getRunningAppProcesses() method, which will give you a list of RunningAppProcessInfo which each have an "importance" variable which can be used to detect if your process is in the foreground or other states.
The problem of course is there is no Detection technique here, just a way to tell what is running and its state, but no way to get informed when the state changes.
So possibly just run a background thread to poll this info. Seems a bit kludgy but it might work.
My app uses location services (GPS) so it automatically gets called when the location changes, and I can check the RunningAppProcessInfo list when this happens to determine if my App is still in the foreground. Seems like a lot of work, so maybe Andy's approach can be improved on.
I have been using the method I describe here:
Simple check for Android application backgrounding
It has been working wonderfully for me.
you might be able to use the isFinishing() method in onPause(). That will tell you if the application is actually finishing or is just being paused. When your task stack goes to the background, the activities aren't finished, they are just paused. This will also get called if you start another activity from this one though, so you would have to check for that.
#Override
public void onPause() {
super.onPause();
if(!isFinishing()) {
//app going to background
}
}

Categories

Resources