Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is onCreateOptionsMenu() called in each fragment using FragmentTransaction add() method?

I've been trying to figure this out and must have a misunderstanding of how fragments work here despite a lot of reading, or I'm encountering a bug.

I have one activity, which contains a FrameLayout.

When the activity is created I add FragmentA.

    if (savedInstanceState == null) {            
        Fragment newFragment = FragmentA.newInstance();
        FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
        ft.add(R.id.fragment_container, newFragment, FRAGMENT_A_TAG).commit();
    }

FragmentA's onCreateOptionsMenu method is called as I'd expect (although, weirdly onPrepareOptionsMenu is called twice the first time?).

FragmentA creates it's own menu, just a simple menu item in this case. When the menu item is pressed an event is fired back to the activity to create FragmentB.

    Fragment newFragment = FragmentB.newInstance();
    FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
    ft.add(R.id.fragment_container, newFragment, FRAGMENT_B_TAG);
    ft.addToBackStack(null);
    ft.commit();

Now here's the problem. At this stage I'd expect only FragmentB's onCreateOptionsMenu to be called. But this isn't the case. FragmentA's onCreateOptionsMenu is called, followed by FragmentB's.

The main activity does not have any associated menu, only the fragments.

Why is this?

If I use ft.replace(...), I don't have this problem. But that means recreating the view each time FragmentB is popped and I'm trying to avoid that.

I'd hope that's enough to go on, but here is the code for the activity and both fragments for clarity.

public class MainActivity extends AppCompatActivity {

    private static String FRAGMENT_A_TAG = "FRAGMENT_A_TAG";
    private static String FRAGMENT_B_TAG = "FRAGMENT_B_TAG";

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

        // Watch for button clicks.
        Button button = (Button) findViewById(R.id.btn_switch_fragment);

        button.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                addFragmentToStack();
            }
        });

        if (savedInstanceState == null) {
            // Do first time initialization -- add initial fragment.
            Fragment newFragment = FragmentA.newInstance();
            FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
            ft.add(R.id.fragment_container, newFragment, FRAGMENT_A_TAG).commit();
        }
    }

    private void addFragmentToStack() {

        Fragment newFragment = FragmentB.newInstance();
        FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
        ft.add(R.id.fragment_container, newFragment, FRAGMENT_B_TAG);
        ft.addToBackStack(null);
        ft.commit();
    }


}

public class FragmentA extends Fragment {

    public FragmentA() {
        // Required empty public constructor
    }

    public static FragmentA newInstance() {
        FragmentA fragment = new FragmentA();
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setHasOptionsMenu(true);

        System.out.println("onCreate called");
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {

        System.out.println("onCreateView called");

        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_a, container, false);
    }



    @Override
    public void onPrepareOptionsMenu(Menu menu) {

        System.out.println("onPrepareOptionsMenu called");

       super.onPrepareOptionsMenu(menu);
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        inflater.inflate(R.menu.fragment_example_a, menu);
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);

    }

    @Override
    public void onDetach() {
        super.onDetach();

    }

    @Override
    public void onResume() {
        super.onResume();

        System.out.println("onResume called");
    }
}

public class FragmentB extends Fragment {

    public FragmentB() {
        // Required empty public constructor
    }

    public static FragmentB newInstance() {
        FragmentB fragment = new FragmentB();
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setHasOptionsMenu(true);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_b, container, false);
    }

    @Override
    public void onPrepareOptionsMenu(Menu menu) {
        super.onPrepareOptionsMenu(menu);
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        inflater.inflate(R.menu.fragment_example_b, menu);
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);

    }

    @Override
    public void onDetach() {
        super.onDetach();

    }

}
like image 775
Ricky Avatar asked Oct 19 '22 00:10

Ricky


1 Answers

If you call:

ft.add(R.id.container, fragmentA, "tag").addToBackStack(null).commit();

FragmentA will stay "resumed" and its menu will be "inflated". Then if you call:

ft.add(R.id.container, fragmentB, "tag").addToBackStack(null).commit();

FragmentA is still "resumed" and now FragmentB is also on state "resumed" and its menu will be also "inflated".

When you need to update a view you should use view.invalidate(), and this method will "redraw" the view.

Said so...

If your MainActivity has a menu, it will call invalidateOptionsMenu() to redraw MainActivity's menu and also draw FragmentA's menu. After adding FragmentB the toolbar needs to add its menu, so it will call invalidateOptionsMenu() to redraw MainActivity and FragmentA menu and also draw FragmentB's menu. That's why it's called every time you change Fragment, because the menu view needs to be redrawn.

That does not happen if you use

ft.replace(...)

because the first Fragment will be destroyed.

Hope It helps understand.


Log your MainActivity's onCreateOptionsMenu and also will be called every time you add a Fragment which has menu.

like image 121
elmontoya7 Avatar answered Oct 21 '22 05:10

elmontoya7