Rikin Prajapati
Rikin Prajapati

Reputation: 1943

Strange behavior of fragmentTransaction.replace() method

I have just create a demo to understand the replace method of FragmentTransaction and I am not getting result as per Developer Guide.

Activity and Fragment

public class MainActivity extends AppCompatActivity {

    private int count;
    private Fragment prevFragment;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        final Button button = (Button) findViewById(R.id.activity_main_bv_add);


        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                count++;

                final FragmentTransaction transaction = getFragmentManager().beginTransaction();
                final TestFragment fragment = new TestFragment(count);

                if (count == 1) {
                    transaction.add(R.id.activity_main_rl_container, fragment, fragment.getClass().getSimpleName());

                } else if (count == 5) {
                    transaction.replace(R.id.activity_main_rl_container, fragment, fragment.getClass().getSimpleName());
                    transaction.addToBackStack(null);
                    if (prevFragment != null) {
                        transaction.hide(prevFragment);
                    }
                } else {
                    transaction.add(R.id.activity_main_rl_container, fragment, fragment.getClass().getSimpleName());
                    transaction.addToBackStack(null);
                    if (prevFragment != null) {
                        transaction.hide(prevFragment);
                    }
                }
                prevFragment = fragment;

                transaction.commit();


            }
        });
    }

    public class TestFragment extends Fragment {
        private final String TAG = this.getClass().getSimpleName();
        private int number;


        public TestFragment(final int number) {
            this.number = number;
        }

        public TestFragment() {
        }

        @Nullable
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
            Log.e(TAG, "onCreateView : " + number);

            final View view = inflater.inflate(R.layout.row_button, null);

            final Button button = (Button) view.findViewById(R.id.button);
            button.setText("" + number);

            return view;
        }

        @Override
        public void onDestroyView() {
            super.onDestroyView();
            Log.e(TAG, "onDestroyView : " + number);
        }
    }
}

Fragment xml

    <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:background="@color/colorPrimary"
              android:orientation="vertical">

    <Button
        android:id="@+id/button"
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:focusable="false"
        android:focusableInTouchMode="false"
        />

</LinearLayout>

Activity xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout android:id="@+id/activity_currency_select"
                xmlns:android="http://schemas.android.com/apk/res/android"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:descendantFocusability="blocksDescendants"
    >

    <Button
        android:id="@+id/activity_main_bv_add"
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:text="Add More"/>

    <RelativeLayout
        android:id="@+id/activity_main_rl_container"
        android:layout_width="match_parent"
        android:layout_height="300dp"
        android:layout_below="@id/activity_main_bv_add"
        android:layout_marginTop="20dp"
        android:background="@color/colorAccent"></RelativeLayout>


</RelativeLayout>

FragmentTransaction replace method : As per Android document, Replace an existing fragment that was added to a container. This is essentially the same as calling remove(Fragment) for all currently added fragments that were added with the same containerViewId and then add(int, Fragment, String) with the same arguments given here.

Assume Case 1 : Using replace method will remove current(last and latest) fragment and add new one in container view.

Assume Case 2 : Using replace method will remove add the fragments that are added into same container view.

But as per above code non of the above case seems right.

Let's click on button as per shown code. On every button click till count == 4, new object of Fragment will be added to container.

When we click on button, for the count == 5, here replace method is used at that time new object of Fragment will be added to container and onDestroyView method is called for the 1st and 3rd added fragments

Question 1 : If replace will remove all the fragments added in same container then why onDestroyView is not called for 2nd and 4th added fragments?

Question 2 : If replace method removes last added fragment in same container then why it does not destroy 4th fragment but 1st and 3rd instead?

As per my understanding the behaviour dose not follow the document showing on Developer site.

Correct me if I'm wrong.

Upvotes: 0

Views: 256

Answers (2)

Valentino
Valentino

Reputation: 2135

It seems a bug of replace method. Notice that this doesn't happen with the Support version of the FragmentManager.

I will refer to this and this.

The first one is marked as Obsolete and the second one as Assigned, but both seem to be very related.

In both is highlighted that the removal of the fragments (from an ArrayList) is done inside a for loop, and then ArrayList of fragments change its size inside this for loop, this causes skipping of some indexes, and then the removal of some (not all) fragments.

Here is the for loop referred inside the links:

for (int i = 0; i < mManager.mAdded.size(); i++) {
    Fragment old = mManager.mAdded.get(i);
    // ... 
    if (f == null || old.mContainerId == f.mContainerId) {
        if (old == f) {
            op.fragment = f = null;
        } else {
            if (op.removed == null) {
                op.removed = new ArrayList<Fragment>();
            }
            op.removed.add(old);
            old.mNextAnim = op.exitAnim;
            if (mAddToBackStack) {
                old.mBackStackNesting += 1;
                if (FragmentManagerImpl.DEBUG) {
                    Log.v(TAG, "Bump nesting of "
                            + old + " to " + old.mBackStackNesting);
                }
            }
            mManager.removeFragment(old, mTransition, mTransitionStyle);
        }
    }
}

In your specific case, you are adding fragments 1,2,3,4. After, when you want to replace all these four fragments with the fifth one, the for loop above is executed. So you remove the 1st (corresponding to i=0 in the for loop), and the arraylist of fragments becomes 2,3,4. Then you pick the next index in the for loop (i=1), but in the meantime the arraylist changed, so take the fragment 3 (corresponding to i=1) and remove it. Finally, this arraylist becomes 2,4 and i=2, and no fragment corresponds to the index 2.

I hope I have helped you understand, even if I do not take the 50 points :)

Upvotes: 2

gang li
gang li

Reputation: 11

for (int i=0; i<mManager.mAdded.size(); i++) {    
    Fragment old = mManager.mAdded.get(i);
    if (f ==null ||old.mContainerId == f.mContainerId) {
        mManager.removeFragment(old,mTransition, mTransitionStyle);
    }
}

MManager.mAdded is an ArrayList list, when called in the traversal of the mManager.removeFragment method, and the method calls the ArrayList remove method;

Public void removeFragment (Fragmentfragment, int transition, inttransitionStyle) {
    MAdded.remove (fragment);    
}

This means that remove is used when looping through the ArrayList list with a for loop

Upvotes: 0

Related Questions