After rotation, the fragment transaction gives an IllegalStateException

I have a problem with Fragments and BroadcastManager. In my application, I switched to one main action and used the new NavigationDrawer. All content is contained in fragments.

One fragment (user search) contains two tabs (search by name, search by criteria) using the "Side Navigation" navigation template 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 menu (options): search action. But only the active fragment should react to it.

I tried different approaches, the most annoying was that I cannot easily get the current fragment from the view pager directly (without relying on the details of the non-API implementation).

An approach

Now I use LocalBroadcast to notify fragments that the search has been clicked on. Each fragment registers a small Wrapper-Receiver in onResume (and deletes it in onPause), which forwards onReceive to the fragment itself (the method shown below). I override setMenuVisibility , which is a callback that FragmentStatePagerAdpater calls Fragments to find out which fragment is active. Only a fragment that has a visible menu will respond 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 them to the back stack.

Question The fragment operation works in general, but when I rotate the device and then click search, I get an IllegalStateException (I cannot catch 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 changing the commit to commitAllowingStateLoss , but then I get an IllegalStateException with "Activity was destroyed."

Do you know what is wrong here? I’m 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); } 

FragmentStatePagerAdapter in the failed tab 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 split version of the project, containing only basic information, is available at https://docs.google.com/file/d/0ByjUMh5UybW7cnB4a0NQeUlYM0E/edit?usp=sharing

Play it like this: run, you will see two tabs. Select and click the Search button in transaction ActionBar → Fragment. Use the back button, rotate the device and repeat → Fail! [The file may not be available after the bounty has ended or an error has been detected.]

Edit : (1) Clarified that onReceive lives in the context of the fragment (2) Added code for the main activity

+4
source share
2 answers

Moving my comment in response here and expanding it.

The first hint is that you only crash after rotation, and you have setRetainInstance(true) . First try setting it to false . Even if this prevents a crash, you still need to be careful. This may indicate another error that you are now hiding without saving the instance of the fragment.

Is there any special reason you had setRetainInstance(true) ?

As for what you are facing the crash of, I still cannot identify the problem. My suspicion is that the wrong onReceive is called after rotation, but it is just a shot in the dark.

If you could create a skeleton of your application and place the code somewhere, I could look at it. I know that you have already generalized how you "forward" onReceive() , but there are opportunities for errors to appear there if this is not done correctly.

If this is not possible, here I suggest - put the entries in the main life cycles of your Activity , your Fragment and in your onReceive() . This will tell you the exact sequence of what is happening.

+2
source

The reason for the error that occurs initially when using commit() is that the transaction must be committed before any call to onSaveInstanceState() for the containing action, and therefore the error was fixed when using commitAllowingStateLoss () , which allows a possible loss of state after the commit.

After changing this method, you will get an Activity has been destroyed exception during orientation changes, because the fragment commit contains a reference to the original activity that was destroyed during the orientation change. Therefore, instead of defining a fragment in your xml layout, you can define a fragment container and add your fragment to the container in the onCreate() parent activity to make sure that when the activity is recreated, you also create a new fragment and commit

Here you can find information about setRetainInstance .

+3
source

Source: https://habr.com/ru/post/1487221/


All Articles