Can not perform this action after onSaveInstanceState (EditText focus change) - android

I have ActivityA attaching FragmentA. There's an EditText in FragmentA which, if focused, adds FragmentB (below). The stack trace starts with onDestroy in ActivityA, which triggers onFocusChange, which fires off popBackStack. The isRemovingOrPartOfRemovalChain() should be returning true at this point but it occasionally returns false causing the popBackStack, hence the exception. Is there a bug in that method?
editText.setOnFocusChangeListener(new OnFocusChangeListener(){
#Override
public void onFocusChange(View view, boolean hasFocus) {
if(hasFocus){
FragmentManager fragmentManager = getChildFragmentManager();
Fragment fragment = fragmentManager.findFragmentByTag(FRAGMENT_B);
if(fragment == null){
FragmentB fragmentB = FragmentB.newInstance();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.add(R.id.fragment_b, fragmentB, FRAGMENT_B);
fragmentExploreSearchListTransaction.addToBackStack(null);
fragmentExploreSearchListTransaction.commit();
}
else{
if(!isRemovingOrPartOfRemovalChain()){
getChildFragmentManager().popBackStack();
}
}
}
});
public boolean isRemovingOrPartOfRemovalChain(){
if(isRemoving()){
return true;
}
Fragment fragment = this.getParentFragment();
if(fragment != null){
if(((MainFragment) fragment).isRemovingOrPartOfRemovalChain()){
return true;
}
else{
return false;
}
}
else{
return(getActivity().isFinishing());
}
}
/**
* Return true if this fragment is currently being removed from its
* activity. This is <em>not</em> whether its activity is finishing, but
* rather whether it is in the process of being removed from its activity.
*/
final public boolean isRemoving() {
return mRemoving;
}

When you commit fragment after onSavedInstanceState(Bundle outState) callback (e.g. onDestroy()), the committable Fragment state will be lost (becase Fragment.onSaveInstanceState(Bundle) won't be called in this situation).
In this case, when Activity is recreated, the committed fragment will not be present in Activity's FragmentManager, and so will not be restored. This is considered a state loss.
This behaviour might break or corrupt user experience, and is considered unintentional and exceptional, so the Android framework warns you about that by throwing an exception :-) Better save than sorry, right?
In case one knows what one's doing (which is almost always not so :-)), one may stick with .commitAllowingStateLoss(), but I strongly advise against it, as it will bring a legal bughole into your application.
Just do not commit fragments after it has became known that your Activity is destroying: for example,
...
boolean fieldActivityIsDestroying;
....
public void onSaveInstanceState(Bundle out){
super.onSaveInstanceState(out);
fieldActivityIsDestroying = true;
}
and check for field value when commiting the fragment.
Also you might want to FragmentManager.executePendingTransactions() to perform any fragment transactions immediately after you commit them to manager (default commit is asynchronous).

your issue because of activity state lost.
try this
fragmentExploreSearchListTransaction.commit()
to
fragmentExploreSearchListTransaction.commitAllowingStateLoss()
but it is not good solution, so i refer you read this blog, this blog is about fragment Transaction after save Activity Instance, I hope my information will help you.

There is not enough code to provide with the complete solution, but in this case:
It's better use Fragment#isRemoving() to check if fragment removed from activity;
If focus changed expected from user interaction with the screen it's better set/remove listener at following methods:
onCreateView()/onDestroyView();
onResume()/onPause();
If there is any reason that this solutions not work feel free to clarify.
Good luck!

Why don't you just set the onFocusedChangeListener in OnResume() and remove it in OnPause()?
That should prevent it from being triggered when your activity is finishing.

Related

Can not perform this action after onSaveInstanceState in android

How can I Add a check to prevent this error
I am getting the error in this code:
private fun clearFragmentsFromContainer() {
if(supportFragmentManager.backStackEntryCount>0) {
supportFragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE)
}
}
Error on the line:
supportFragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE)
Log:
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:2080)
at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:2106)
at android.support.v4.app.FragmentManagerImpl.popBackStack(FragmentManager.java:832)
at com.caring2u.organizer.ui.activities.screen.ActSummaryEvent.clearFragmentsFromContainer(ActSummaryEvent.kt:524)
at com.caring2u.organizer.ui.activities.screen.ActSummaryEvent.onClickEventTabs(ActSummaryEvent.kt:466)
at com.caring2u.organizer.ui.activities.screen.ActSummaryEvent.dataEventsList(ActSummaryEvent.kt:162)
at com.caring2u.organizer.network.retrofit.retrofitTasks.RetroEventsSummary$initiate$1.onResponse(RetroEventsSummary.kt:62)
You are trying to change the fragment stack after onPause as can be seen from the log.
You can either use FragmentManger.commitAllowingStateLoss or be sure to not call this method after onPause
To remove all fragments in a container please use below code
for (Fragment fragment:getSupportFragmentManager().getFragments()) {
if (fragment instanceof NavigationDrawerFragment) {
continue;
}
else if (fragment!=null) {
getSupportFragmentManager().beginTransaction().remove(fragment).commit();
}
}
Probably your fragment transaction is committing after onSavedInstanceState() callback. That means that your activity is stopping and you're adding your fragment in a state that cannot be saved. Actually, during onSavedInstanceState() call Android takes a snapshot of your activity state, this means that if you commit a transaction after the state it's saved the transaction won't be remembered as it was never recorded. From the user point of view that will result in a UI state loss.
Instead of using commitAllowingStateLoss you should understand if you're calling your clearFragmentsFromContainer method from an asynchronous method, in that case probably you should simply move your transaction from the async method.
More about "commit state loss":
AndroidDesignPatterns.com
Elye on Medium
In order to understand if your activity has already called onSaveInstanceState() method, you might think to place a flag inside onSaveInstanceState callback, resetting the flag in the dual method onRestoreInstanceState, something like:
val saveInstanceStateCalled = false
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
saveInstanceStateCalled = true
}
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
saveInstanceStateCalled = false
}
Then you can check the flag before calling clearFragmentsFromContainer
I solved this using the code
if (!fragmentManager.isStateSaved()) {
if(supportFragmentManager.backStackEntryCount>0) {
fragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE)
}
}

IllegalStateException when adding & replacing Fragments; commit vs commitAllowingStateLoss

It is rare that this happens, but occasionally my app will crash due to an IllegalStateException when adding and replacing fragments. Here is how I am doing it, I do so with an animation.
private void addFragmentReplace(int containerId, Fragment fragment) {
// check if the fragment has been added already
Fragment temp = mFragmentManager.findFragmentByTag(fragment.getTag());
if (!Utils.checkIfNull(temp) && temp.isAdded()) {
return;
}
// replace fragment and transition with animation
mFragmentManager.beginTransaction().setCustomAnimations(R.anim.ui_slide_in_from_bottom_frag,
R.anim.ui_slide_out_to_bottom_frag).replace(containerId, fragment).addToBackStack(null)
.commit();
}
I have researched into changing "commit()" to "commitAllowingStateLoss()" but is that really a solution? It will prevent the crashes, however, won't it cause other conflicts such as the fragment not displaying at times or other? Is the following an odd improvement to my above code snippet?
private void addFragmentReplace(int containerId, Fragment fragment) {
// check if the fragment has been added already
Fragment temp = mFragmentManager.findFragmentByTag(fragment.getTag());
if (!Utils.checkIfNull(temp) && temp.isAdded()) {
return;
}
// replace fragment and transition with animation
try {
mFragmentManager.beginTransaction().setCustomAnimations(R.anim.ui_slide_in_from_bottom_frag,
R.anim.ui_slide_out_to_bottom_frag).replace(containerId, fragment).addToBackStack(null)
.commit();
} catch (IllegalStateException e) {
e.printStackTrace();
mFragmentManager.beginTransaction().setCustomAnimations(R.anim.ui_slide_in_from_bottom_frag,
R.anim.ui_slide_out_to_bottom_frag).replace(containerId, fragment).addToBackStack(null)
.commitAllowingStateLoss();
}
}
Thanks in advance. My concerns come from the documentation for commitAllowingStateLoss() which reads
Like {#link #commit} but 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.
Some tips or advice on this would be appreciated. Thanks!

What is logic behind before super.method or after super.method in android?

Hello everyone i want to ask what is difference between if i something write before super.onDestroyView(); and after super.onDestroyView(); see example below
Remove fragment before super.ondestoryview();
#Override
public void onDestroyView() {
try {
Fragment fragment = (getFragmentManager()
.findFragmentById(R.id.mapviews));
FragmentTransaction ft = getActivity().getSupportFragmentManager()
.beginTransaction();
ft.remove(fragment);
ft.commit();
} catch (Exception e) {
e.printStackTrace();
}
super.onDestroyView();
}
Remove fragment after super.ondestoryview();
#Override
public void onDestroyView() {
super.onDestroyView();
try {
Fragment fragment = (getFragmentManager()
.findFragmentById(R.id.mapviews));
FragmentTransaction ft = getActivity().getSupportFragmentManager()
.beginTransaction();
ft.remove(fragment);
ft.commit();
} catch (Exception e) {
e.printStackTrace();
}
}
If super was Fragment, than there is no difference how you do it, because Fragment's onDestroyView does nothing. But in some cases it matters.
As Dianne Hackborn said:
general rule: during any kind of initialization, let the super class do their work first; during any kind of finalization, you do your work first
P.S. IMHO it's not a good solution to remove fragment from other Fragment's onDestroyView method. That's strange, I think you should find better place for managing your fragments...
Helpful answer
case 1: if there is some code written in super.onDestroyView, then that code will be executed after the code you have written.
case 2: if there is some code written in super.onDestroyView, then that code will be executed first, then the code you have written will be executed.
Here is the documentation for Fragment.java onDestroyView():
/**
* Called when the view previously created by {#link #onCreateView} has
* been detached from the fragment. The next time the fragment needs
* to be displayed, a new view will be created. This is called
* after {#link #onStop()} and before {#link #onDestroy()}. It is called
* <em>regardless</em> of whether {#link #onCreateView} returned a
* non-null view. Internally it is called after the view's state has
* been saved but before it has been removed from its parent.
*/
#CallSuper
public void onDestroyView() {
mCalled = true;
}
The important line of that documentation is: Internally it is called after the view's state has been saved but before it has been removed from its parent.
If your onDestroy() method does not need any changes to views to be saved, then I think it doesn't matter when you call super().
Your code should generally do its work after the originally intended work is done - the caveat here is if the work of the super changes the state of whatever you want to work with. That said, it does come down to a case by case basis - read the code of the super - sometimes those are already super-ing something else.
onDestroy() of super should be called once u have done with your clean up handling. Its a good coding practice and cause for a less buggy programming.

How to determine fragment restored from backstack

Been searching for this issue for a while to no avail now:
How to determine fragment is being restored from backstack?
I'm using the compatibility library and a ListFragment inside a FragmentActivity. When an item inside ListFragment is selected, a new Fragment is started to replace the ListFragment.
I noticed that when the FragmentActivity gets paused, the Fragment's onSaveInstanceState is called. But when the Fragment is put into the back stack via FragmentTransaction, onSaveInstanceState doesn't get called, then the lifecycle methods onCreateView and onActivityCreated gets called with null savedInstanceState Bundle.
I'm asking this because I want to load some data when the Fragment is created or restored, but not so when user comes back via. backstack.
I've looked at How to check if Fragment was restored from a backstack?
but want to add more details in hopes this would incite an answer.
Edit:
just noticed http://developer.android.com/reference/android/app/Fragment.html#onSaveInstanceState(android.os.Bundle)
says
Note however: this method may be called at any time before onDestroy(). There are many situations where a fragment may be mostly torn down (such as when placed on the back stack with no UI showing), but its state will not be saved until its owning activity actually needs to save its state.
So onSaveInstanceState is definitely out of the question...
I think that most simple way is do this for example in onViewCreated() method:
if (savedInstanceState == null && !mAlreadyLoaded) {
mAlreadyLoaded = true;
// Do this code only first time, not after rotation or reuse fragment from backstack
}
Because when android put fragment on backstack, it only destroy its view, but don't kill instance itself, so mAlreadyLoaded will be still true when fragment will be restored from backstack.
getSupportFragmentManager().addOnBackStackChangedListener(new OnBackStackChangedListener() {
public void onBackStackChanged() {
Log.i(TAG, "back stack changed ");
int backCount = getSupportFragmentManager().getBackStackEntryCount();
if (backCount == 0){
// block where back has been pressed. since backstack is zero.
}
}
});
use this addOnBackStackChangedListener.
When a fragment goes to back-stack onDestroyView() called. Not onDestroy().
And when a fragment pops from back-stack onCreateView() called. Not onCreate().
So add a boolean mIsRestoredFromBackstack to fragment and follow as below:
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
mIsRestoredFromBackstack = false;
}
#Override
public void onResume()
{
super.onResume();
if(mIsRestoredFromBackstack)
{
// The fragment restored from backstack, do some work here!
}
}
#Override
public void onDestroyView()
{
super.onDestroyView();
mIsRestoredFromBackstack = true;
}
MAJOR EDIT: Oct 15 2013
The previous explanation (kept below for reference) fails when the application is put to the background and brought back to the foreground.
Instead, it is better to compare the current size of the backstack with the one when the fragment was created & put into the backstack.
Take a good look at Figure 2 in http://developer.android.com/guide/components/fragments.html#Creating
What this figure tells you is that when a fragment is restored from the backstack, its onCreate() is not called, while its onCreateView() is.
So, you may want to do something like this:
public class MyFragment extends Fragment {
int mBackStackSize = 0;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mBackStackSize = getFragmentManager().getBackStackEntryCount();
}
public boolean isRestoredFromBackstack() {
return mBackStackSize > getFragmentManager().getBackStackEntryCount();
}
}
If you added fragment to backstack, and after some manipulation you hide it using fragmentTransaction.hide(fragment) and then restore it from backstack like fragmentTransaction.show(fragmentManager.findFragmentByTag(fragment.getName())); you can override onHiddenChanged(boolean hidden)
#Override
public void onHiddenChanged(boolean hidden) {
// TODO Auto-generated method stub
super.onHiddenChanged(hidden);
if (!hidden) {
//fragment became visible
//your code here
}
}
In some cases you can use isVisible method to understand is it first showing of a fragment or is it restored from the backstack.

How to get a Fragment to remove itself, i.e. its equivalent of finish()?

I'm converting an app to use fragments using the compatibility library.
Now currently I have a number of activities (A B C D) which chain onto one another, D has a button 'OK' which when pressed calls finish which then bubbles up through onActivityResult() to additionally destroy C and B.
For my pre Honycomb fragment version each activity is effectively a wrapper on fragments Af Bf Cf Df. All activities are launched via startActivityForResult() and onActivityResult() within each of the fragments can happily call getActivity().finish()
The problem that I am having though is in my Honeycomb version I only have one activity, A, and fragments Bf, Cf, Df are loaded using the FragmentManager.
What I don't understand is what to do in Df when 'OK' is pressed in order to remove fragments Df, Cf, and Bf?
I tried having the fragment popping itself off the stack but this resulted in an exception. onActivityResult() is useless because I have not loaded up the fragment using startActivityForResult().
Am I thinking about this completely the wrong way? Should I be implementing some sort of listener that communicates with either the parent fragment or activity in order to do the pop using the transaction manager?
While it might not be the best approach the closest equivalent I can think of that works is this with the support/compatibility library
getActivity().getSupportFragmentManager().beginTransaction().remove(this).commit();
or
getActivity().getFragmentManager().beginTransaction().remove(this).commit();
otherwise.
In addition you can use the backstack and pop it. However keep in mind that the fragment might not be on the backstack (depending on the fragmenttransaction that got it there..) or it might not be the last one that got onto the stack so popping the stack could remove the wrong one...
You can use the approach below, it works fine:
getActivity().getSupportFragmentManager().popBackStack();
What I don't understand is what to do in Df when 'OK' is pressed in order to remove fragments Df, Cf, and Bf?
Step #1: Have Df tell D "yo! we got the OK click!" via calling a method, either on the activity itself, or on an interface instance supplied by the activity.
Step #2: Have D remove the fragments via FragmentManager.
The hosting activity (D) is the one that knows what other fragments are in the activity (vs. being in other activities). Hence, in-fragment events that might affect the fragment mix should be propagated to the activity, which will make the appropriate orchestration moves.
You should let the Activity deal with adding and removing Fragments, as CommonsWare says, use a listener. Here is an example:
public class MyActivity extends FragmentActivity implements SuicidalFragmentListener {
// onCreate etc
#Override
public void onFragmentSuicide(String tag) {
// Check tag if you do this with more than one fragmen, then:
getSupportFragmentManager().popBackStack();
}
}
public interface SuicidalFragmentListener {
void onFragmentSuicide(String tag);
}
public class MyFragment extends Fragment {
// onCreateView etc
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
suicideListener = (SuicidalFragmentListener) activity;
} catch (ClassCastException e) {
throw new RuntimeException(getActivity().getClass().getSimpleName() + " must implement the suicide listener to use this fragment", e);
}
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// Attach the close listener to whatever action on the fragment you want
addSuicideTouchListener();
}
private void addSuicideTouchListener() {
getView().setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
suicideListener.onFragmentSuicide(getTag());
}
});
}
}
In the Activity/AppCompatActivity:
#Override
public void onBackPressed() {
if (mDrawerLayout.isDrawerOpen(GravityCompat.START)) {
// if you want to handle DrawerLayout
mDrawerLayout.closeDrawer(GravityCompat.START);
} else {
if (getFragmentManager().getBackStackEntryCount() == 0) {
super.onBackPressed();
} else {
getFragmentManager().popBackStack();
}
}
}
and then call in the fragment:
getActivity().onBackPressed();
or like stated in other answers, call this in the fragment:
getActivity().getSupportFragmentManager().beginTransaction().remove(this).commit();
If you are using the new Navigation Component, is simple as
findNavController().popBackStack()
It will do all the FragmentTransaction in behind for you.
See if your needs are met by a DialogFragment. DialogFragment has a dismiss() method. Much cleaner in my opinion.
I create simple method for that
popBackStack(getSupportFragmentManager());
Than place it in my ActivityUtils class
public static void popBackStack(FragmentManager manager){
FragmentManager.BackStackEntry first = manager.getBackStackEntryAt(0);
manager.popBackStack(first.getId(), FragmentManager.POP_BACK_STACK_INCLUSIVE);
}
It's work great, have fun!
OnCreate:
//Add comment fragment
container = FindViewById<FrameLayout>(Resource.Id.frmAttachPicture);
mPictureFragment = new fmtAttachPicture();
var trans = SupportFragmentManager.BeginTransaction();
trans.Add(container.Id, mPictureFragment, "fmtPicture");
trans.Show(mPictureFragment); trans.Commit();
This is how I hide the fragment in click event 1
//Close fragment
var trans = SupportFragmentManager.BeginTransaction();
trans.Hide(mPictureFragment);
trans.AddToBackStack(null);
trans.Commit();
Then Shows it back int event 2
var trans = SupportFragmentManager.BeginTransaction();
trans.Show(mPictureFragment); trans.Commit();
If you need to popback from the fourth fragment in the backstack history to the first, use tags!!!
When you add the first fragment you should use something like this:
getFragmentManager.beginTransaction.addToBackStack("A").add(R.id.container, FragmentA).commit()
or
getFragmentManager.beginTransaction.addToBackStack("A").replace(R.id.container, FragmentA).commit()
And when you want to show Fragments B,C and D you use this:
getFragmentManager.beginTransaction.addToBackStack("B").replace(R.id.container, FragmentB, "B").commit()
and other letters....
To return to Fragment A, just call popBackStack(0, "A"), yes, use the flag that you specified when you add it, and note that it must be the same flag in the command addToBackStack(), not the one used in command replace or add.
You're welcome ;)
To Close a fragment while inside the same fragment
getActivity().onBackPressed();
kotlin -
requireActivity().onBackPressed()
parentFragmentManager.apply {
val f = this#MyFragment
beginTransaction().hide(f).remove(f).commit()
}
Why not just:
getActivity().finish();

Categories

Resources