I have an issue with Fragments and BroadcastManager. In my application I switched to one main activity and use the new NavigationDrawer. All content is contained in fragments.
One Fragment (Searching for users) contains two tabs (Search By Name, Search By Criteria) using the Navigation Pattern "Lateral Navigation" from the Android Design website: It has a ViewPager with a FragmentStatePagerAdapter.
| Main Activity | | Main Activity |
------------------- -------------------
| Search Fragment | | Search Fragment |
| >Tab 1< | Tab 2 | | Tab 1 | >Tab 2< |
| View Pager | | View Pager |
| Fragment 1 | | Fragment 2 |
I want both tabs to use the same action bar (options) menu: The search action. But only the active fragment should react to it.
I have tried different approaches, most annoying was that I cannot easily get the current fragment from the view pager directly (without relying on non-API implementation details).
Approach
I now use a LocalBroadcast to notify fragments that search has been clicked. Each fragment registers a small Wrapper-Receiver in onResume (and removes it in onPause) which forwards the onReceive to the fragment itself (the method shown below).
I override setMenuVisibility
which is a callback that the FragmentStatePagerAdpater calls on the Fragments to know which is the active fragment.
Only the fragment that has the menu set visible will react to the broadcast.
ActionBar Tab -> ViewPager -> ViewPagerAdapter -> Fragment.setMenuVisibility
ActionBar Menu Action -> Broadcast -> ... -> BroadcastReceiver -> Fragments
Both fragments trigger a fragment transaction to display the search results and add it to the back stack.
Issue The fragment transaction works in general, but when I rotate the device and then press search, I get an IllegalStateException (cannot commit after onSaveInstanceState).
@Override
public void onReceive(Context context, Intent intent) {
if (m_menuVisible) {
final String s = ((TextView) getView().findViewById(R.id.search_text)).getText().toString();
SearchResultsFragment f = new UserSearchResultsFragment();
Bundle b = new Bundle();
b.putString(SearchResultsFragment.EXTRA_SEARCHNAME, s);
f.setArguments(b);
FragmentTransaction transaction = getSherlockActivity().getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.fragment_container_frame, f).addToBackStack(null).commit(); // <<< CRASH
}
}
I tried to change the commit to commitAllowingStateLoss
, but then I get an IllegalStateException with "Activity has been destroyed".
Do you know what goes wrong here? I am at a loss what to do...
Additional code:
MainActivities onCreate (based on NavigationDrawer sample) and selectItem
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
setContentView(R.layout.fragment_container_layout);
setupDrawer(); // Sets up the drawer layout, adapter, etc.
if (savedInstanceState == null) {
selectItem(0); // Selects and adds the fragment(s) for the position
}
// Other setup stuff
}
private void selectItem(int position) {
// update the main content by replacing fragments
Fragment f = null;
switch (position) {
case 0:
f = ...
break;
case 1:
...
default:
throw new IllegalArgumentException("Could not find right fragment");
}
f.setRetainInstance(true);
m_drawerList.setItemChecked(position, true);
setTitle(mDrawerTitles.get(position).titleRes);
// Hide any progress bar that might be visible in the actionbar
setProgressBarIndeterminateVisibility(false);
// When we select something from the navigation drawer, the back stack is discarded
final FragmentManager fm = getSupportFragmentManager();
fm.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
fm.beginTransaction().replace(R.id.fragment_container_frame, f).commit();
// update selected item and title, then close the drawer
m_drawerLayout.closeDrawer(GravityCompat.START);
}
The FragmentStatePagerAdapter in the malfunctioning tabbed fragment:
protected class TabPagerAdapter extends FragmentStatePagerAdapter {
private List<String> m_fragmentTags = new ArrayList<String>();
public TabPagerAdapter(FragmentManager fm) {
super(fm);
}
public void addFragments(List<String> list) {
ViewPager pager = (ViewPager) getView().findViewById(R.id.viewpager);
startUpdate(pager);
for (String tag : list) {
m_fragmentTags.add(tag);
}
finishUpdate(pager);
notifyDataSetChanged();
}
public void addFragment(String tag) {
addFragments(Collections.singletonList(tag));
}
@Override
public Fragment getItem(int pos) {
// CreateFragmentForTag: Retrieves the classes, instantiates the fragment
// Does not do retainInstance or any transaction there.
return createFragmentForTag(m_fragmentTags.get(pos));
}
@Override
public int getCount() {
return m_fragmentTags.size();
}
}
A stripped down version of the project, containing only essentials, is posted at https://docs.google.com/file/d/0ByjUMh5UybW7cnB4a0NQeUlYM0E/edit?usp=sharing
Reproduce it as follows: Start up, you see the two tabs. Select either and click the Search in the ActionBar -> Fragment transaction works. Use Back key, rotate device and repeat -> Crash! [The file may not be accessible after the bounty has ended or error is identified.]
Edit: (1) Clarified that the onReceive lives in the context of the fragment (2) Added code for main activity