Handle Fragment duplication on Screen Rotate (with sample code)

后端 未结 4 1927
死守一世寂寞
死守一世寂寞 2021-02-06 11:03

There are some similar answers, but not to this situation.


My situation is simple.

I have an Activity with two different layouts, one in Portrait,

相关标签:
4条回答
  • 2021-02-06 11:09

    In your onCreate() method you should only create your ListFrag if you are in portrait mode. You can do that by checking if the FrameLayout view that you only have in the portrait layout is not null.

    if (findViewById(R.id.yourFrameLayout) != null) {
        // you are in portrait mode
        ListFrag listFrag = new ListFrag();
        getSupportFragmentManager().beginTransaction()
                .replace(R.id.FragmentContainer, listFrag).commit();
    } else {
        // you are in landscape mode
        // get your Fragment from the xml
    }
    

    In the end if you switch from portrait to landscape layout, you don't want to have another ListFrag created, but use the Fragment you have specified in your xml.

    0 讨论(0)
  • 2021-02-06 11:09

    @midnite ,first of all thanks for your question, I had the same question, on wich I worked for days. Now I wound some tricky resolution to this problem - and want to share this to community. But If someone has better solution, please write in comments.

    So, I have same situation - different layouts for two device orientation. In portrait there is no need for DetailsFragment.

    I simply in OnCreate trying to find already created fragments:

        fragment = (ToDoListFragment) getFragmentManager().findFragmentByTag(LISTFRAGMENT);
        frameDetailsFragment = (FrameLayout) findViewById(R.id.detailsFragment);
        detailsFragment = (DetailsFragment) getFragmentManager().findFragmentByTag(DETAILS_FRAGMENT);
        settingsFragment = (SettingsFragment) getFragmentManager().findFragmentByTag(SETTINGS_FRAGMENT);
    

    Right after that I am adding my main fragment -

        if (fragment == null){
            fragment = new ToDoListFragment();
            getFragmentManager().beginTransaction()
                    .add(R.id.container, fragment, LISTFRAGMENT)
                    .commit();
        }
    

    And clear back stask of my fragments:

      if (getFragmentManager().getBackStackEntryCount() > 0 ) {
            getFragmentManager().popBackStackImmediate();
        }
    

    And the MOST interesting is here -

      destroyTmpFragments();
    

    Here is the method itself:

      private void destroyTmpFragments(){
        if (detailsFragment != null && !detailsFragment.isVisible()) {
            Log.d("ANT", "detailsFragment != null, Destroying");
            getFragmentManager().beginTransaction()
                    .remove(detailsFragment)
                    .commit();
    
            detailsFragment = null;
        }
    
        if (settingsFragment != null && !settingsFragment.isVisible()) {
            Log.d("ANT", "settingsFragment != null, Destroying");
            getFragmentManager().beginTransaction()
                    .remove(settingsFragment)
                    .commit();
    
            settingsFragment = null;
        }
    }
    

    As you can see, I clean manually all fragments, that FragmentManager gentfully saved for me (great thanks to him). In log I have next lifecycle calls:

     04-24 23:03:27.164 3204    3204    D   ANT MainActivity onCreate()
     04-24 23:03:27.184 3204    3204    I   ANT DetailsFragment :: onCreateView
     04-24 23:03:27.204 3204    3204    I   ANT DetailsFragment :: onActivityCreated
     04-24 23:03:27.204 3204    3204    I   ANT DetailsFragment :: onDestroy
     04-24 23:03:27.208 3204    3204    I   ANT DetailsFragment :: onDetach
    

    So in onActivityCreated make your views check for null - in case fragment is not visible. It will be deleted little bit later (It is so maybe cuz fragment manager's remove is async) After, in activity's code we can create brand new Fragment instance, with proper layout (fargmnet's placeholder) and fragment will be able to find it's views for example, and will not produce NullPointerException

    But, fragments that are in back stack are deleted pretty fast, without call to onActivityCreated (with th help of above code:

     if (getFragmentManager().getBackStackEntryCount() > 0 ) {
            getFragmentManager().popBackStackImmediate();
        }
    

    At last, at the end i add fragment if I am in land orientation -

     if (frameDetailsFragment != null){
            Log.i("ANT", "frameDetailsFragment != null");
    
            if (EntryPool.getPool().getEntries().size() > 0) {
                if (detailsFragment == null) {
                    detailsFragment = DetailsFragment.newInstance(EntryPool.getPool().getEntries().get(0), 0);
                }
    
                getFragmentManager().beginTransaction()
                        .replace(R.id.detailsFragment, detailsFragment, DETAILS_FRAGMENT)
                        .commit();
            }
        }
    
    0 讨论(0)
  • 2021-02-06 11:10

    The correct way to handle this is to put the following in your onCreate()

    if (savedInstanceState == null) {
         // Do fragment transaction.
    }
    
    0 讨论(0)
  • 2021-02-06 11:13

    I finally come up with a solution to prevent unwanted Fragment re-creating upon the Activity re-creates. It is like what i mentioned in the question:

    The only neat way i found is doing getSupportFragmentManager().beginTransaction().remove(getSupportFragmentManager().findFragmentById(R.id.FragmentContainer)).commit(); in onSaveInstanceState(). But it will raise another problems...

    ... with some improvements.

    In onSaveInstanceState():

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        if (isPortrait2Landscape()) {
            remove_fragments();
        }
        super.onSaveInstanceState(outState);
    }
    
    private boolean isPortrait2Landscape() {
        return isDevicePortrait() && (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE);
    }
    

    and the isDevicePortrait() would be like:

    private boolean isDevicePortrait() {
        return (findViewById(R.id.A_View_Only_In_Portrait) != null);
    }
    

    *Notice that we cannot use getResources().getConfiguration().orientation to determine if the device is currently literally Portrait. It is because the Resources object is changed RIGHT AFTER the screen rotates - EVEN BEFORE onSaveInstanceState() is called!!

    If we do not want to use findViewById() to test orientation (for any reasons, and it's not so neat afterall), keep a global variable private int current_orientation; and initialise it by current_orientation = getResources().getConfiguration().orientation; in onCreate(). This seems neater. But we should be aware not to change it anywhere during the Activity lifecycle.

    *Be sure we remove_fragments() before super.onSaveInstanceState().

    (Because in my case, i remove the Fragments from the Layout, and from the Activity. If it is after super.onSaveInstanceState(), the Layout will already be saved into the Bundle. Then the Fragments will also be re-created after the Activity re-creates. ###)

    ### I have proved this phenomenon. But the reason of What to determine a Fragment restore upon Activity re-create? is just by my guess. If you have any ideas about it, please answer my another question.

    0 讨论(0)
提交回复
热议问题