I came across inconsistent return values from Fragment.isRemoving()
when the activity has just added the fragment to the back stack. The first time the fragment is temporarily destroyed due to configuration change, isRemoving()
returns true. If the fragment is temporarily destroyed a second time, isRemoving()
returns false!
My code:
public class MainActivityFragment extends Fragment {
private static final String TAG = "MainActivityFragment";
private static final String LEVEL = "MainActivityFragment.LEVEL";
public MainActivityFragment() {
}
public static MainActivityFragment newInstance(int n) {
MainActivityFragment f = new MainActivityFragment();
f.setArguments(new Bundle());
f.getArguments().putInt(LEVEL, n);
return f;
}
private int getLevel() {
return (getArguments() == null) ? 0 : getArguments().getInt(LEVEL);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_main, container, false);
Button button = (Button) rootView.findViewById(R.id.button);
button.setText(String.valueOf(getLevel()));
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
getActivity().getSupportFragmentManager()
.beginTransaction()
.replace(R.id.fragment, MainActivityFragment.newInstance(getLevel() + 1))
.addToBackStack(null)
.commit();
}
});
return rootView;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.i(TAG, String.valueOf(getLevel()) + ": onCreate");
}
@Override
public void onDestroy() {
super.onDestroy();
Log.i(TAG, String.valueOf(getLevel()) + ": onDestroy");
Log.i(TAG, String.valueOf(getLevel()) + ": isChangingConfigurations() == " + getActivity().isChangingConfigurations());
Log.i(TAG, String.valueOf(getLevel()) + ": isRemoving() == " + isRemoving());
}
The log (lines starting with # are my comments):
# Start Activity
I/MainActivityFragment: 0: onCreate
# Click button in fragment 0 to add it to back stack and replace it with fragment 1
I/MainActivityFragment: 1: onCreate
# Rotate the device
I/MainActivityFragment: 0: onDestroy
I/MainActivityFragment: 0: isChangingConfigurations() == true
I/MainActivityFragment: 0: isRemoving() == true # ???????
I/MainActivityFragment: 1: onDestroy
I/MainActivityFragment: 1: isChangingConfigurations() == true
I/MainActivityFragment: 1: isRemoving() == false
I/MainActivityFragment: 0: onCreate
I/MainActivityFragment: 1: onCreate
# Rotate the device a second time
I/MainActivityFragment: 0: onDestroy
I/MainActivityFragment: 0: isChangingConfigurations() == true
I/MainActivityFragment: 0: isRemoving() == false # Correct result
I/MainActivityFragment: 1: onDestroy
I/MainActivityFragment: 1: isChangingConfigurations() == true
I/MainActivityFragment: 1: isRemoving() == false
I/MainActivityFragment: 0: onCreate
I/MainActivityFragment: 1: onCreate
# Click button in fragment 1 to add it to back stack and replace it with fragment 2
I/MainActivityFragment: 2: onCreate
# Rotate the device
I/MainActivityFragment: 0: onDestroy
I/MainActivityFragment: 0: isChangingConfigurations() == true
I/MainActivityFragment: 0: isRemoving() == false # Ok, correct
I/MainActivityFragment: 1: onDestroy
I/MainActivityFragment: 1: isChangingConfigurations() == true
I/MainActivityFragment: 1: isRemoving() == true # WHY????
I/MainActivityFragment: 2: onDestroy
I/MainActivityFragment: 2: isChangingConfigurations() == true
I/MainActivityFragment: 2: isRemoving() == false
I/MainActivityFragment: 0: onCreate
I/MainActivityFragment: 1: onCreate
I/MainActivityFragment: 2: onCreate
Is this a bug in Android or am I understanding this wrong?
Update: I added a call to Fragment.dump() in onDestroy and I got the following results:
Before the fragment is put in the back stack:
mFragmentId=#7f0c006b mContainerId=#7f0c006b mTag=null
mState=2 mIndex=0 mWho=android:fragment:0 mBackStackNesting=0
mAdded=true mRemoving=false mResumed=false mFromLayout=false mInLayout=false
mHidden=false mDetached=false mMenuVisible=true mHasMenu=false
mRetainInstance=false mRetaining=false mUserVisibleHint=true
mFragmentManager=FragmentManager{336d670b in HostCallbacks{387c69e8}}
mHost=android.support.v4.app.FragmentActivity$HostCallbacks@387c69e8
mSavedViewState={2131492979=android.view.AbsSavedState$1@6adf801}
Child FragmentManager{2b6916a6 in null}}:
FragmentManager misc state:
mHost=null
mContainer=null
mCurState=0 mStateSaved=true mDestroyed=true
After the fragment is put in the back stack and is destroyed the first time:
mFragmentId=#7f0c006b mContainerId=#7f0c006b mTag=null
mState=1 mIndex=0 mWho=android:fragment:0 mBackStackNesting=1
mAdded=false mRemoving=true mResumed=false mFromLayout=false mInLayout=false
mHidden=false mDetached=false mMenuVisible=true mHasMenu=false
mRetainInstance=false mRetaining=false mUserVisibleHint=true
mFragmentManager=FragmentManager{34638ae1 in HostCallbacks{2db8e006}}
mHost=android.support.v4.app.FragmentActivity$HostCallbacks@2db8e006
mSavedViewState={2131492979=android.view.AbsSavedState$1@6adf801}
Child FragmentManager{169d66c7 in null}}:
FragmentManager misc state:
mHost=null
mContainer=null
mCurState=0 mStateSaved=true mDestroyed=true
Destroyed the second time:
mFragmentId=#7f0c006b mContainerId=#7f0c006b mTag=null
mState=1 mIndex=0 mWho=android:fragment:0 mBackStackNesting=1
mAdded=false mRemoving=false mResumed=false mFromLayout=false mInLayout=false
mHidden=false mDetached=false mMenuVisible=true mHasMenu=false
mRetainInstance=false mRetaining=false mUserVisibleHint=true
mFragmentManager=FragmentManager{23beb2bc in HostCallbacks{c0f9245}}
mHost=android.support.v4.app.FragmentActivity$HostCallbacks@c0f9245
mSavedFragmentState=Bundle[{android:view_state={2131492979=android.view.AbsSavedState$1@6adf801}}]
mSavedViewState={2131492979=android.view.AbsSavedState$1@6adf801}
The differences between the first (not in back stack yet) and second (put in back stack) are:
- mState=2 (
ACTIVITY_CREATED
) vs. mState=1 (CREATED
) - mBackStackNesting=0 vs. mBackStackNesting=1
- mAdded=true vs. mAdded=false
- mRemoving=false vs. mRemoving=true (obviously)
The differences between the second (first time destroyed) and third (second+ time destoyed) are:
- mRemoving=true vs. mRemoving=false
- mSavedFragmentState=null vs mSavedFragmentState=Bundle[...]
- has Child FragmentManager vs. has no Child FragmentManager
However, I have no idea how to interpret these results.
I'm starting to think isRemoving
is not what I need (what I actually need is something equivalent to Activity.isFinishing
but for fragments. I need to know that "this fragment will never be reused again", so I can cancel background tasks. Right now I'm using isRemoving() && !getActivity().isChangingConfigurations()
but I'm not sure it's the right solution).
isRemoving()
. I agree that the results that you are seeing are very strange. You might consider usingdump()
onFragment
, to get more state information, instead of the logging that you're doing, to see if that gives you any more clues. – CommonsWareFragment.dump()
– imgx64