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();
}
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With