Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Fragments list and BackStackEntry dynamics

Below is a piece of test code in my quest to understand Fragment processing. The first output is to log the list of fragments. There are 2: GameMenuFragment and Game1Fragment0. Then the back stack is logged. It contains Game1Fragment0. Then the back stack is popped. So then back stack log shows it has nothing. Bueno. But now, looping again through the fragment list, while the size() still comes back as 2, it crashes on an NPE because (apparently) Game1Fragment0 is not there.
So, apparently, popping the back stack has removed that fragment from the fragment list, yet the size is still 2. Can someone explain?

Output:
Game1Fragment(1617): fragmentList size: 2
Game1Fragment(1617): fragmentList: 2131492865 :GameMenuFragment
Game1Fragment(1617): fragmentList: 2131492865 :Game1Fragment0
Game1Fragment(1617): getBackStackEntryCount: 1 (0-based)
Game1Fragment(1617): BackStackEntry: Game1Fragment0
Game1Fragment(1617): getBackStackEntryCount2: 0 (0-based)
Game1Fragment(1617): fragmentList2 size: 2
Game1Fragment(1617): fragmentList2: 2131492865 : GameMenuFragment
AndroidRuntime(1617): FATAL EXCEPTION: main
AndroidRuntime(1617): java.lang.NullPointerException

FragmentManager fragmentManager = fragmentActivity.getSupportFragmentManager();

List<Fragment> fragmentList = fragmentManager.getFragments();
Log.w("Game1Fragment", "fragmentList size: " + fragmentList.size());
for (Fragment fragment : fragmentList) {
    Log.w("Game1Fragment", "fragmentList: " + fragment.getId() + " : "+ fragment.getTag());
}

Log.w("Game1Fragment", "getBackStackEntryCount: " + fragmentManager.getBackStackEntryCount() + " (0-based)");
for(int entry = 0; entry < fragmentManager.getBackStackEntryCount(); entry++){
    Log.w("Game1Fragment", "BackStackEntry: " + fragmentManager.getBackStackEntryAt(entry).getName());
}

fragmentManager.popBackStackImmediate(); // pop Game1Fragment0

Log.w("Game1Fragment", "getBackStackEntryCount2: " + fragmentManager.getBackStackEntryCount() + " (0-based)");
for(int entry = 0; entry < fragmentManager.getBackStackEntryCount(); entry++){
    Log.w("Game1Fragment", "BackStackEntry: " + fragmentManager.getBackStackEntryAt(entry).getName());
}

List<Fragment> fragmentList2 = fragmentManager.getFragments();
Log.w("Game1Fragment", "fragmentList2 size: " + fragmentList2.size());
for (Fragment fragment : fragmentList2) {
    Log.w("Game1Fragment", "fragmentList2: " + fragment.getId() + " : "+ fragment.getTag()); // throws NPE on 2nd time through loop
}
like image 442
Al Lelopath Avatar asked Jul 22 '26 15:07

Al Lelopath


1 Answers

When a Fragment is created, a reference is maintained to it in the active Fragment array which is of type ArrayList<Fragment>. An index is assigned to the Fragment instance, which is the index of the array element which keeps the reference.

Now when the Fragment is destroyed, the reference should be removed from ArrayList<Fragment>. If the element referencing the Fragment is removed from ArrayList, other elements within ArrayList might get re-positioned (e.g. if you remove 0th element, 1st element will now become 0th element). This will make the index maintained within Fragments instances invalid. Hence the element is not destroyed from the ArrayList, rather null is assigned to that index.

Here is the code which handles it. Look at line mActive.set(f.mIndex, null);

void makeInactive(Fragment f) {
    if (f.mIndex < 0) {
        return;
    }

    if (DEBUG) Log.v(TAG, "Freeing fragment index " + f);
    mActive.set(f.mIndex, null);
    if (mAvailIndices == null) {
        mAvailIndices = new ArrayList<Integer>();
    }
    mAvailIndices.add(f.mIndex);
    mActivity.invalidateFragment(f.mWho);
    f.initState();
}

To keep track of released indexes, ArrayList<Integer> mAvailIndices is maintained, which will be used while assigning index to newly created Fragments in future.

Here is the code of getFraments method which returns mActive:

@Override
public List<Fragment> getFragments() {
    return mActive;
}

The second time when you call fragmentManager.getFragments(), the list contains null for that released index. Hence we should always do a null check on Fragment instance before using it.

Popping from back stack will reverse the transaction. If it was added in the transaction then it will be removed and further destroyed. Here is the call stack of a sample app I had written:

MainActivity$Fragment2.onDestroy() line: 53 
MainActivity$Fragment2(Fragment).performDestroy() line: 1912    
FragmentManagerImpl.moveToState(Fragment, int, int, int, boolean) line: 1008    
FragmentManagerImpl.removeFragment(Fragment, int, int) line: 1162   
BackStackRecord.popFromBackStack(boolean) line: 714 
like image 125
Manish Mulimani Avatar answered Jul 25 '26 07:07

Manish Mulimani



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!