Replacing a fragment with another fragment of the same class

10,798

Solution 1

Bypass Solution
Explanation (hoover to see it)

Since the source code for fragmentTransaction.replace/add/remove is not available I could not find what really happens. But it is reasonable to think that at some point it compares the current class name with the replacement class name and it exits if they are the same.. Thanks to @devconsole for pointing out the source code. I know now why this happens. The FragmentManager.removeFragment() method does not reset the fragment state, it remains RESUMED, then the method moveToState(CREATED) only initilizes a fragment if (f.mState < newState) = if (RESUMED < CREATED) = false. Else, ergo, it just resumes the fragment.

So to solve this problem I created an almost empty fragment which only purpose is to replace itself with the target fragment.

public class JumpFragment {

    public JumpFragment() {
    }

    @Override
    public void onStart() {
        Bundle data = getArguments();
        int containerId = data.getString("containerID");
        String tag = data.getString("tag");
        //Class<?> c = data.get???("class");            
        //Fragment f = (Fragment) c.newInstance();          
        Fragment f = (Fragment) new MyFragment();
        FragmentManager fragmentManager = getFragmentManager();
        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
        f.setArguments(data);
        fragmentTransaction.replace(containerId, f, tag);
        fragmentTransaction.addToBackStack(null);
        fragmentTransaction.commit();
    }

}

And I use it:

data.setInt("Layout index",i);
data.setInt("containerID",R.id.fragmentContent);
data.setString("tag","MY");
fragmentTab0 = (Fragment) new JumpFragment();
fragmentTab0.setArguments(data);
fragmentTransaction.replace(R.id.fragmentContent, fragmentTab0);
fragmentTransaction.commit();

Now no fragment is replaced by a same class fragment:

MyFragment -> JumpFragment -> MyFragment

I haven't figured out how to pass the class through the arguments bundle, to make it totally generic.

Solution 2

The following worked without problems for me. Notice that I used the Android Support Library.

activity_main.xml:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" >

    <Button
        android:id="@+id/button_one"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="ONE" />

    <Button
        android:id="@+id/button_two"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@id/button_one"
        android:text="TWO" />

    <FrameLayout
        android:id="@+id/main_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_below="@id/button_one" >
    </FrameLayout>

</RelativeLayout>

MainActivity.java:

public class MainActivity extends FragmentActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        if (savedInstanceState == null) {
            FragmentManager fragmentManager = getSupportFragmentManager();
            FragmentTransaction transaction = fragmentManager.beginTransaction();
            Fragment fragment = DetailsFragment.newInstance("INITIAL");
            transaction.add(R.id.main_container, fragment);
            transaction.commit();
        }

        findViewById(R.id.button_one).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                update("Button 1 clicked");
            }
        });

        findViewById(R.id.button_two).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                update("Button 2 clicked");
            }
        });
    }

    protected void update(String value) {
        FragmentManager fragmentManager = getSupportFragmentManager();
        FragmentTransaction transaction = fragmentManager.beginTransaction();
        Fragment fragment = DetailsFragment.newInstance(value);
        transaction.replace(R.id.main_container, fragment);
        transaction.commit();
    }

    public static final class DetailsFragment extends Fragment {
        public static DetailsFragment newInstance(String param) {
            DetailsFragment fragment = new DetailsFragment();
            Bundle args = new Bundle();
            args.putString("param", param);
            fragment.setArguments(args);
            return fragment;
        }

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
            TextView textView = new TextView(container.getContext());
            textView.setText(getArguments().getString("param"));
            return textView;
        }
    }
}
Share:
10,798
ilomambo
Author by

ilomambo

I have a solid programming education. Trying to learn how everything works and interacts. Hopefully, to write great applications.

Updated on June 13, 2022

Comments

  • ilomambo
    ilomambo almost 2 years

    I have a fragment (let's call it MyFragment) that inflates different layouts according to a parameter passed in the arguments.
    All works well if MyFragment is started from a different fragment. But if MyFragment is active and I want to launch a new MyFragment with a different layout parameter, the fragmentManager does not create a new fragment at all.

    data.setInt("Layout index",i);
    fragmentTab0 = (Fragment) new MyFragment();
    fragmentTab0.setArguments(data);
    fragmentTransaction.replace(R.id.fragmentContent, fragmentTab0, "MY");
    fragmentTransaction.addToBackStack(null);
    fragmentTransaction.commit();
    

    How can I force convince the fragmentTransaction to launch the fragment again?

    NOTE: The important point here is I need to inflate again a layout, which is different from the layout inflated before. The code looks like:

    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    
        switch( getArguments().getInt("Layout index") ) {
        case 1:
            rootView = inflater.inflate(R.layout.firstlayout, container, false);
            break;
        case 2:
            rootView = inflater.inflate(R.layout.secondlayout, container, false);
            break;
        case 3:
            rootView = inflater.inflate(R.layout.thirdlayout, container, false);
            break;
        default: break;
        }       
    
  • ilomambo
    ilomambo almost 11 years
    I used replace which according to the documentation: 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.
  • Sébastien Morand
    Sébastien Morand almost 11 years
    yeah, that should do the trick... Just try remove maybe the implementation is a bit different.
  • ilomambo
    ilomambo almost 11 years
    Not exactly what I need. As I wrote, I inflate different layouts based on an argument passed on the fragment data bundle. your example inflates only once.
  • ilomambo
    ilomambo almost 11 years
    I tried splitting replace into remove + add, but still the same behavior
  • devconsole
    devconsole almost 11 years
    Regarding the source code: Class BackStackRecord extends FragmentTransaction.
  • ilomambo
    ilomambo almost 11 years
    @devconsole I think you got it backwards. BackstackRecord extends FragmentTransaction. It is not the FragmentTransaction implementation.
  • devconsole
    devconsole almost 11 years
    FragmentTransaction is an abstract class, BackStackRecord extends that class, thereby providing an implementation. See also FragmentManager line 468: FragmentManager.beginTransaction() actually returns a BackStackRecord.
  • ilomambo
    ilomambo almost 11 years
    @devconsole Thanks, I found the reason to the problem and added an explanation.
  • devconsole
    devconsole almost 11 years
    I still cannot reproduce the problem on my Nexus 4 running Android 4.2.2. I tried both the native APIs and the Android Support Library, my example works in both cases, see my answer. Maybe the problem was fixed in a version <= 4.2.2?
  • ilomambo
    ilomambo almost 11 years
    @devconsole I tried your answer code, and it works on the emulator with android17 as target. I even inflated two different layouts based on arguments, as I described here, and it worked, I added the transaction to the back stack, and it worked. I dropped v4.support and used android classes, and it worked. At this point myheadache is big as a house, too bad the debugger in Eclispe refuses to follow the code into the fragmentManager. I need to figure out what is really happening.
  • ilomambo
    ilomambo almost 11 years
  • nafsaka
    nafsaka over 8 years
    I've facing the same problem. I try this, but doesn't work. Are there any solution in this?