I have an app that shows a few fragments (of the same type) in a ViewPager
and I'm having some trouble with context menu items. (I'm using the support library).
When a context menu item is selected in the context menu in one of the fragments, the wrong fragment is receiving the onContextItemSelected
event call.
For example, if I'm on fragment #3 in the pager, the fragment at position #2 receives it instead. If I swipe back to fragment #2, fragment #3 receives the call instead.
I've got a sample here.
(I'm currently working around this in my own app by having a mHandleContext
variable in each fragment and enabling/disabling it when the page is changed. That way the onContextItemSelected
call will go out to all the fragments until the right one is called.)
Am I doing something wrong or is this a bug with the support library? As a side note, this didn't happen when I was using ActionBarSherlock 3.5.1, which had its own fork of the support library.
So this is some sort of idiotic design decision by Google or something that has just gone totally unconsidered. The simplest way to work around this is to wrap the onContextItemSelected
call with an if statement like this:
if (getUserVisibleHint()) {
// Handle menu events and return true
} else
return false; // Pass the event to the next fragment
The compatibility library in ActionBarSherlock 3.5 had a hack like this.
It happens because of this:
public boolean dispatchContextItemSelected(MenuItem item) {
if (mActive != null) {
for (int i=0; i<mAdded.size(); i++) {
Fragment f = mAdded.get(i);
if (f != null && !f.mHidden) {
if (f.onContextItemSelected(item)) {
return true;
}
}
}
}
return false;
}
As you can see, FragmentManager calls Fragment.onContextItemSelected for all of his own fragments until it returns true. In your example I can offer such fix:
public static class TestListFragment extends ListFragment {
private int mNumber = 0;
private ArrayList<String> mItems;
public static TestListFragment newInstance(int number) {
Bundle args = new Bundle();
args.putInt("number", number + 1);
TestListFragment fragment = new TestListFragment();
fragment.setArguments(args);
return fragment;
}
public TestListFragment() {}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mNumber = getArguments().getInt("number");
mItems = new ArrayList<String>();
mItems.add("I am list #" + mNumber);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setListAdapter(new ArrayAdapter<String>(getActivity(), android.R.layout.simple_list_item_1, mItems));
registerForContextMenu(getListView());
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
menu.add(mNumber, 0, 0, "Hello, World!");
}
@Override
public boolean onContextItemSelected(MenuItem item) {
if(item.getGroupId() == mNumber){
Log.d("ViewPagerContextMenuBug", "onContextItemSelected called for number " + mNumber);
Toast.makeText(getActivity(), "onContextItemSelected called for number " + mNumber, Toast.LENGTH_SHORT).show();
return true;
}
return false;
}
}
Oh Google, I mean WTF?
The problem is that onContextItemSelected is very generic, and it is called for each menu item of each fragment.
You can use MenuItem.OnMenuItemClickListener to force fragment menu to not use all onContextItemSelected, but only same function of this fragment.
Use next implementation:
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
MenuInflater inflater = getActivity().getMenuInflater();
if (v == btnShare) {
inflater.inflate(R.menu.share_menu, menu);
for (int i = 0; i < menu.size(); ++i) {
MenuItem item = menu.getItem(i);
item.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
onContextItemSelected(item);
return true;
}
});
}
}
}
@Override
public boolean onContextItemSelected(MenuItem item) {
AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
switch (item.getItemId()) {
case R.id.print:
// ...
}
}
Using intent for each of the menu items worked well for me.
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextmenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
MenuInflater inflater = super.getActivity.getMenuInflater();
inflater.infalte(R.menu.list_item, menu);
for(int i = 0; i < menu.size(); i++) {
MenuItem item = menu.getItem(i);
Intent intent = new Intent();
intent.putExtra(KEY_EXTRA_FRAGMENT_ID, this.fragmentId);
if (item != null) {
item.setIntent(intent);
}
}
}
@Override
public boolean onContextItemSelected(MeniItem item) {
Intent intent = item.getIntent();
if (intent != null) {
if (intent.getIntExtra(KEY_EXTRA_FRAGMENT_ID, -1) == this.fragmentId) {
// Implement code according the item function.
return true;
}
}
return super.onContextItemSelected(item);
}
The getUserVisibleHint()
solution doesn't work for me - it always returns true
even when the fragment isn't on screen. The getGroupId()
solution doesn't work either when inflating the menu from an XML resource, which is my situation.
It seems that unless Android changes, any solution is always going to be a bit hacky. I create a global variable to store an ID reference for the current fragment in onCreateView
. I then pass it to each ContextMenu
MenuItem
in onCreateContextMenu
. When an item is selected, I validate that these two IDs are the same.
private int myFragmentReference;
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// Initialisation stuff
myFragmentReference = 12345;
}
@Override
public void onCreateContextMenu(ContextMenu contextMenu, View v, ContextMenu.ContextMenuInfo contextMenuInfo) {
// Usual stuff
int size = contextMenu.size();
for (int i = 0; i < size; i++) {
MenuItem menuItem = contextMenu.getItem(i);
Intent intent = new Intent();
intent.putExtra("id", myFragmentReference);
menuItem.setIntent(intent);
}
}
@Override
public boolean onContextItemSelected(MenuItem menuItem) {
int id = 0;
Intent intent = menuItem.getIntent();
if (intent != null) {
id = intent.getIntExtra("id", 0);
}
if (id == myFragmentReference) {
// This is the currently displayed fragment
}
}
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