8

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
  • 8,290
  • 12
  • 57
  • 106
  • possible duplicate of [Replacing same fragment doesn't show anything](http://stackoverflow.com/questions/14959198/replacing-same-fragment-doesnt-show-anything) – waqaslam Jun 17 '13 at 13:12
  • @Waqas The title I chose is wrong, I will correct it. I am not replacing a fragment with itself, I am creating a new fragment of the same class as the current fragment (you can see it in the code I posted) – ilomambo Jun 17 '13 at 13:27

4 Answers4

3

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.

ilomambo
  • 8,290
  • 12
  • 57
  • 106
  • Regarding the source code: Class [BackStackRecord](https://github.com/android/platform_frameworks_base/blob/android-cts-4.2_r2/core/java/android/app/BackStackRecord.java#L172) extends [FragmentTransaction](https://github.com/android/platform_frameworks_base/blob/android-cts-4.2_r2/core/java/android/app/FragmentTransaction.java). – devconsole Jun 18 '13 at 10:34
  • @devconsole I think you got it backwards. `BackstackRecord` extends `FragmentTransaction`. It is not the `FragmentTransaction` implementation. – ilomambo Jun 18 '13 at 11:25
  • `FragmentTransaction` is an abstract class, `BackStackRecord` extends that class, thereby providing an implementation. See also [FragmentManager line 468](https://github.com/android/platform_frameworks_base/blob/android-cts-4.2_r2/core/java/android/app/FragmentManager.java#L468): `FragmentManager.beginTransaction()` actually returns a `BackStackRecord`. – devconsole Jun 18 '13 at 11:34
  • @devconsole Thanks, I found the reason to the problem and added an explanation. – ilomambo Jun 18 '13 at 12:24
  • 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? – devconsole Jun 18 '13 at 12:40
  • @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 Jun 18 '13 at 15:32
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/31958/discussion-between-ilomambo-and-devconsole) – ilomambo Jun 18 '13 at 15:47
  • I've facing the same problem. I try this, but doesn't work. Are there any solution in this? – nafsaka Nov 02 '15 at 08:08
1

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;
        }
    }
}
devconsole
  • 7,875
  • 1
  • 34
  • 42
0

Did you try to first remove your fragment with remove(fragMgr.findFragmentByTag("MY"))and then add the new one ? PS : I assume you don't keep any reference to this fragment.

  • 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.` – ilomambo Jun 17 '13 at 13:20
  • yeah, that should do the trick... Just try `remove` maybe the implementation is a bit different. – Sébastien Morand Jun 17 '13 at 13:28
  • I tried splitting `replace` into `remove` + `add`, but still the same behavior – ilomambo Jun 17 '13 at 13:49
0

If I understand you correctly: the fragment you want to replace what is currently being displayed and the user does something to cause it to re-display itself?

If this is correct then have done something similar this way:

 public class MyFragment extends Fragment {
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
            // Inflate the layout for this fragment
            final View V = inflater.inflate(R.layout.myLayout, container, false);

            // Call method that fills the layout with data
            displayData(V);

            // Put a listener here that checks for user input
            Button redisplayButton = (Button) V.findViewById(R.id.my_button);
                // if the button is clicked....
               redisplayButton.setOnClickListener(new OnClickListener() {
                    public void onClick(View v){
                    //
                    //  do some stuff
                    //
                    //  ....then eventually...
                    displayData(V); 
                }
              });

        return V;
        }

Later on you can have the displayData() method that defines what the fragment displays....

    public void displayData(View V){

       //  Do something


    return;
    }

Hope this helps!

BryanJ
  • 113
  • 1
  • 2
  • 6
  • 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 Jun 17 '13 at 13:35