Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android multiple fragment transaction ordering

I have a HorizontalScrollView containing a (horizontal) LinearLayout which I use as the container for adding multiple fragments. Upon some changes, I need to remove all fragments from that container and add new ones. However, there seems to be a problem with ordering when I'm removing the old fragments.

Here are the scenarios:

  • app startup
    • correctly adding fragments A1,B1,C1,D1 in this order
  • change content
    • if not removing initial fragments, but adding A2,B2,C2 (as a single transaction), it will show A1,B1,C1,D1,A2,B2,C2
    • if removing initial fragments (either as a separate or using the same transaction), then adding A2,B2,C2, it will show C2,B2,A2

For now I found a workaround, where I'm adding the new fragments first then removing the old ones (still as part of the same transaction) and that is working properly.

EDIT: The workaround doesn't work all the time.

I'm using android.support.v4.app.Fragment.

Any ideas on what's happening?

like image 662
radu Avatar asked May 06 '14 21:05

radu


2 Answers

I enabled debugging on the FragmentManager and I found the problem.

Here's an excerpt from the logs, notice how the fragment index is allocated in reverse order:

V/FragmentManager? Freeing fragment index TimeTracesChartFragment{42ac4910 #7 id=0x7f080044}
V/FragmentManager? add: RealTimeValuesFragment{42a567b0 id=0x7f080044}
V/FragmentManager? Allocated fragment index RealTimeValuesFragment{42a567b0 #7 id=0x7f080044}
V/FragmentManager? add: TimeTracesChartFragment{42d35c38 id=0x7f080044}
V/FragmentManager? Allocated fragment index TimeTracesChartFragment{42d35c38 #6 id=0x7f080044}
V/FragmentManager? add: TimeTracesChartFragment{42d35e98 id=0x7f080044}
V/FragmentManager? Allocated fragment index TimeTracesChartFragment{42d35e98 #5 id=0x7f080044}
V/FragmentManager? add: TimeTracesChartFragment{42d36220 id=0x7f080044}
V/FragmentManager? Allocated fragment index TimeTracesChartFragment{42d36220 #4 id=0x7f080044}
V/FragmentManager? add: TimeTracesChartFragment{42d39d18 id=0x7f080044}
V/FragmentManager? Allocated fragment index TimeTracesChartFragment{42d39d18 #3 id=0x7f080044}
V/FragmentManager? add: TimeTracesChartFragment{42d3a170 id=0x7f080044}
V/FragmentManager? Allocated fragment index TimeTracesChartFragment{42d3a170 #2 id=0x7f080044}
V/FragmentManager? add: TimeTracesChartFragment{42d3a528 id=0x7f080044}
V/FragmentManager? Allocated fragment index TimeTracesChartFragment{42d3a528 #1 id=0x7f080044}
V/FragmentManager? moveto CREATED: TimeTracesChartFragment{42d3a528 #1 id=0x7f080044}

And here is the culprit code:

http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.0.1_r1/android/support/v4/app/FragmentManager.java#FragmentManagerImpl.makeActive%28android.support.v4.app.Fragment%29

 void makeActive(Fragment f) {
        if (f.mIndex >= 0) {
            return;
        }

        if (mAvailIndices == null || mAvailIndices.size() <= 0) {
            if (mActive == null) {
                mActive = new ArrayList<Fragment>();
            }
            f.setIndex(mActive.size(), mParent);
            mActive.add(f);

        } else {
            f.setIndex(mAvailIndices.remove(mAvailIndices.size()-1), mParent);
            mActive.set(f.mIndex, f);
        }
        if (DEBUG) Log.v(TAG, "Allocated fragment index " + f);
    }

Notice how the available indices are taken from the back of the list. It should probably choose the lowest index available so that it would preserve ordering.

Now to think of a workaround...

EDIT:

Here's a workaround: Create two separate transactions, one for the removals and then one for the additions, then do this:

removalTxn.commit();
getSupportFragmentManager().executePendingTransactions();
FragmentTransactionBugFixHack.reorderIndices(getSupportFragmentManager());
//create additionTxn
additionTxn.commit();

Where FragmentTransactionBugFixHack looks like this:

package android.support.v4.app;

import java.util.Collections;

public class FragmentTransactionBugFixHack {

    public static void reorderIndices(FragmentManager fragmentManager) {
        if (!(fragmentManager instanceof FragmentManagerImpl))
            return;
        FragmentManagerImpl fragmentManagerImpl = (FragmentManagerImpl) fragmentManager;
        if (fragmentManagerImpl.mAvailIndices != null)
            Collections.sort(fragmentManagerImpl.mAvailIndices, Collections.reverseOrder());
    }

}

It's not ideal, because of the two separate transaction it will flicker to white (or whatever your container's background is), but at least it will order them properly.

like image 74
radu Avatar answered Sep 27 '22 19:09

radu


other way to fix this issue:

replace ArrayList mAvailIndices by ReverseOrderArrayList

ReverseOrderArrayList.java

public class ReverseOrderArrayList<T extends Comparable> extends ArrayList<T> {
@Override
public boolean add(T object) {
    boolean value = super.add(object);
    Collections.sort(this, Collections.reverseOrder());
    return value;
}

@Override
public void add(int index, T object) {
    super.add(index, object);
    Collections.sort(this, Collections.reverseOrder());
}

@Override
public boolean addAll(Collection<? extends T> collection) {
    boolean value = super.addAll(collection);
    Collections.sort(this, Collections.reverseOrder());
    return value;
}

@Override
public boolean addAll(int index, Collection<? extends T> collection) {
    boolean value = super.addAll(index, collection);
    Collections.sort(this, Collections.reverseOrder());
    return value;
}

@Override
protected void removeRange(int fromIndex, int toIndex) {
    super.removeRange(fromIndex, toIndex);
    Collections.sort(this, Collections.reverseOrder());
}

@Override
public boolean remove(Object object) {
    boolean value = super.remove(object);
    Collections.sort(this, Collections.reverseOrder());
    return value;
}

@Override
public boolean removeAll(Collection<?> collection) {
    boolean value = super.removeAll(collection);
    Collections.sort(this, Collections.reverseOrder());
    return value;
}

@Override
public T remove(int index) {
    T value = super.remove(index);
    Collections.sort(this, Collections.reverseOrder());
    return value;
}

}

Hack

public class FragmentTransactionBugFixHack {
private static final String TAG = "FragmentTransactionBugFixHack";

public static void injectFragmentTransactionAvailIndicesAutoReverseOrder(FragmentManager fragmentManager) {
    try {
        Log.d(TAG, "injection injectFragmentTransactionAvailIndicesAutoReverseOrder");
        if (fragmentManager==null || !(fragmentManager instanceof FragmentManagerImpl)) return;
        FragmentManagerImpl fragmentManagerImpl = (FragmentManagerImpl) fragmentManager;
        if (fragmentManagerImpl.mAvailIndices!=null && fragmentManagerImpl.mAvailIndices instanceof ReverseOrderArrayList) return;
        ArrayList<Integer> backupList = fragmentManagerImpl.mAvailIndices;
        fragmentManagerImpl.mAvailIndices = new ReverseOrderArrayList<>();
        if (backupList!=null) {
            fragmentManagerImpl.mAvailIndices.addAll(backupList);
        }
        Log.d(TAG, "injection ok");
    } catch (Exception e) {
        Log.e(TAG, e);
    }
}}

Using: call FragmentTransactionBugFixHack.injectFragmentTransactionAvailIndicesAutoReverseOrder in activity-onCreate.

like image 40
Khanh Tran Avatar answered Sep 27 '22 17:09

Khanh Tran