简体   繁体   中英

Multiple Fragments on Orientation Changes

I have been visited by an annoying task in Android, which is maintainig the fragment's state on Orientation changes.

First of all, i have tried every solution here on StackOver flow and i have had only a nice result which i didn't ask for.

I was able to maintain the changes in Fragment, on Orientation changes, but not able to save the fragment state when the user switches the fragment, therefore i have removed this solution and went looking for a new, better one.

My idea is to show/hide fragments, without the need to replace them as a whole, because they will be hidden for a small time only, and if they're not visible anymore, no problem in creating them again.

Less talk, more Code.

int mID = (int) drawerItem.getIdentifier();

                        String mTag = "";
                        switch (mID){
                            case 0:
                                mTag = "ViewPager";
                                break;
                            case 1:
                                mTag = "Browser";
                                break;
                            case 2:
                                mTag = "Settings";
                                break;
                        }

                        if (savedInstanceState == null) {
                            ShowHideFrags(mID);
                        } else {
                            switch (mID){
                                case 0:
                                    Log.i("ASDSADSA","4");
                                    mViewPager = (ViewPagerFragment) getSupportFragmentManager().findFragmentByTag(mTag);
                                    ShowHideFrags(mID);
                                    break;
                                case 1:
                                    Log.i("ASDSADSA","5");
                                    mWebFrag = (WVFragment) getSupportFragmentManager().findFragmentByTag(mTag);
                                    ShowHideFrags(mID);
                                    break;
                                case 2:
                                    Log.i("ASDSADSA","6");
                                    mSettings = (SettingsFragment) getSupportFragmentManager().findFragmentByTag(mTag);
                                    ShowHideFrags(mID);
                                    break;
                            }
                        }

This code handles the clicks in the Navigation drawer, i'm using 'Material Drawer' library by Mikepenz .

As the code shows, i have three fragments, which are ViewPager , Browser , and Settings .

My problem here is, its getting re-created, without orientation and the getFragment methods, no issue in the code, but when i'm adding support for this change, the fragment gets recreated again. I have already tried many times to change the code, log the changes, and see what's wrong.

In the first startup, it calls ShowHideFrags(int x) method, then it goes to the else .

ShowHideFrags(int x)'s Code:

private void ShowHideFrags(int SelectedFrag){
    if(mFragmentManager == null)
        mFragmentManager = getSupportFragmentManager();

    android.support.v4.app.FragmentTransaction ft = mFragmentManager.beginTransaction();
    switch (SelectedFrag){
        case 0:
            if(mViewPager == null)
                mViewPager = new ViewPagerFragment();

            if(!mViewPager.isAdded())
                ft.add(R.id.fragment,mViewPager,"Viewpager");

            if(!mViewPager.isVisible()){
                if((mSettings != null && mSettings.isVisible()))
                    ft.hide(mSettings);

                if((mWebFrag != null && mWebFrag.isVisible()))
                    ft.hide(mWebFrag);

                ft.show(mViewPager);
            } else {
                if((mSettings != null && mSettings.isVisible()))
                    ft.hide(mSettings);

                if((mWebFrag != null && mWebFrag.isVisible()))
                    ft.hide(mWebFrag);
            }

            ft.commit();
            break;
        case 1:
            if(mWebFrag == null)
                mWebFrag = new WVFragment();


            if(!mWebFrag.isAdded())
                ft.add(R.id.fragment,mWebFrag,"Browser");

            if(!mWebFrag.isVisible()) {
                if((mSettings != null && mSettings.isVisible()))
                    ft.hide(mSettings);

                if(mViewPager != null && mViewPager.isVisible())
                    ft.hide(mViewPager);

                ft.show(mWebFrag);

            } else {
                if((mSettings != null && mSettings.isVisible()))
                    ft.hide(mSettings);

                if((mViewPager != null && mViewPager.isVisible()))
                    ft.hide(mViewPager);
            }
            ft.commit();
            break;
        case 2:
            if(mSettings == null)
                mSettings = new SettingsFragment();

            if(!mSettings.isAdded())
                ft.add(R.id.fragment,mSettings,"Settings");

            if(!mSettings.isVisible()) {
                if((mViewPager != null && mViewPager.isVisible()))
                    ft.hide(mViewPager);

                if((mWebFrag != null && mWebFrag.isVisible()))
                    ft.hide(mWebFrag);

                ft.show(mSettings);
            } else {
                if((mViewPager != null && mViewPager.isVisible()))
                    ft.hide(mViewPager);

                if((mWebFrag != null && mWebFrag.isVisible()))
                    ft.hide(mWebFrag);
            }
            ft.commit();
            break;
    }
}

My onSaveInstanceState's Code:

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);

    try{
        long mSelectedItem = result.getCurrentSelection();
        android.support.v4.app.FragmentManager fragmentManager = getSupportFragmentManager();
        android.support.v4.app.Fragment currentFragment = fragmentManager.findFragmentById(R.id.fragment);
        getSupportFragmentManager().putFragment(outState,currentFragment.getTag(),currentFragment);
        outState.putLong("SelectedItem",mSelectedItem);
    } catch (Exception e){
        e.printStackTrace();
    }


}

so after 4 days trying a solution, i sat down and had a nice drink, then i figured out a way to solve this annoying issue.

As i'm not that awesome in explaining the progress, or the steps, i will be straight forward and try my best to be like the pro answers guys.

First of all, as my app has a Navigation drawer, and i want to show/hide fragments without re-creating them, it was a bit hard task to acheive such a mission without taking much time, so i needed to understand the life cycle and what happens, thus i've added Log to every step to know what's happening under the hood.

When the app starts, and you only switching the fragments .show() and .hide() , the bundle will be null. therefore

if (savedInstanceState == null) {
  LogMessage("Bundle Is Null, normal stuff");
  ShowHideFrags(mID);
}

the used method ShowHideFrags will be attached in the end, its task is only to check if fragments are null or not, and hide those who are not selected, and show the selected one, if it's null, the method will a create a new fragment and add it.

First thing is easy, when the orientation happens, the bundle won't be null, it will has the saved fragment, first of all we need to save that in the bundle.

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    long mSelectedItem = result.getCurrentSelection();

    try{
        FragmentManager mFM = getSupportFragmentManager();
        Fragment currentFragment = null;


        switch (mTag){
            case "ViewPager":
                currentFragment = mViewPager;
                break;
            case "Browser":
                currentFragment = mWebFrag;
                break;
            case "Settings":
                currentFragment = mSettings;
                break;
        }

        mFM.putFragment(outState,mTag,currentFragment);
        LogMessage(String.format("The following fragment '%1$s' with position %2$s was saved",mTag,String.valueOf(mSelectedItem)));
    } catch (Exception e){
        e.printStackTrace();
    }
    outState.putLong("SelectedItem",mSelectedItem);
}

A question will be asked, why using hardcoded strings without having them as a variable? because i'm lazy. Important notice, do NOT save the current fragment by findFragmentByTag method, it will give you the last applied fragment rather than the selected one, therefore i had to check manually on what fragmen is being selected or not.

Not to forget mentioning the fragments, i have these following fragments in the start of the code

private ViewPagerFragment mViewPager;
private WVFragment mWebFrag;
private SettingsFragment mSettings;

mTag is the fragment tag, inside the onClick method in the Navigation drawer, i have the mTag value added, by the following code:

switch (mID) {
  case 0:
  mTag = "Viewpager";
  break;

  case 1:
  mTag = "Browser";
  break;

  case 2:
  mTag = "Settings";
  break;
}

Now, we have mTag assigned, when bundle is null check done, and bundle saving part is done also, now we need to do the part when the bundle isn't null.

First the code, then the explaination

if(getSupportFragmentManager().findFragmentByTag(mTag) != null){
                                        LogMessage(String.format("Fragment with tag '%1$s' was found",mTag));
                                        mViewPager = (ViewPagerFragment) getSupportFragmentManager().findFragmentByTag(mTag);
                                        if(!mViewPager.isVisible()) {
                                            LogMessage("Not Visible");
                                            ShowHideFrags(mID);
                                        } else {
                                            LogMessage("Visible");
                                        }
                                    } else {
                                        LogMessage(String.format("Fragment with tag '%1$s' was NOT found", mTag));

                                           if(!ReturnCurrentFragment().equalsIgnoreCase(mTag)){
                                            LogMessage("Not the Same");
                                            ShowHideFrags(mID);
                                        } else {
                                            LogMessage("It's the Same");
                                        }
                                    }

private Fragment ReturnCurrentFragment(){
    return getSupportFragmentManager().findFragmentById(R.id.fragment);
}

the above code is inside the else clause, when the bundle isn't null. first we check if the selected fragment (which was saved in the bundle) is indeed available to be loaded or not, if so, we assign it, if it's not visible, it means other fragments are visible, thus we call the ShowHideFrags .

When the fragment is visible, we don't do anything. Furthermore, we check if it's not in the bundle, if not there, if it's already available to be reloaded or not, so we don't re-create it twice *1.

With this code, i've nearly took care of every annoying bug may happen hope .

ShowHideFrags(int x) 's code:

private void ShowHideFrags(int SelectedFrag){
    if(mFragmentManager == null)
        mFragmentManager = getSupportFragmentManager();

    android.support.v4.app.FragmentTransaction ft = mFragmentManager.beginTransaction();
    switch (SelectedFrag){
        case 0:
            if(mViewPager == null)
                mViewPager = new ViewPagerFragment();

            if(!mViewPager.isAdded())
                ft.add(R.id.fragment,mViewPager,"Viewpager");

            if(!mViewPager.isVisible()){
                if((mSettings != null && mSettings.isVisible()))
                    ft.hide(mSettings);

                if((mWebFrag != null && mWebFrag.isVisible()))
                    ft.hide(mWebFrag);

                ft.show(mViewPager);
            } else {
                if((mSettings != null && mSettings.isVisible()))
                    ft.hide(mSettings);

                if((mWebFrag != null && mWebFrag.isVisible()))
                    ft.hide(mWebFrag);
            }

            ft.commit();
            break;
        case 1:
            if(mWebFrag == null)
                mWebFrag = new WVFragment();


            if(!mWebFrag.isAdded())
                ft.add(R.id.fragment,mWebFrag,"Browser");

            if(!mWebFrag.isVisible()) {
                if((mSettings != null && mSettings.isVisible()))
                    ft.hide(mSettings);

                if(mViewPager != null && mViewPager.isVisible())
                    ft.hide(mViewPager);

                ft.show(mWebFrag);

            } else {
                if((mSettings != null && mSettings.isVisible()))
                    ft.hide(mSettings);

                if((mViewPager != null && mViewPager.isVisible()))
                    ft.hide(mViewPager);
            }
            ft.commit();
            break;
        case 2:
            if(mSettings == null)
                mSettings = new SettingsFragment();

            if(!mSettings.isAdded())
                ft.add(R.id.fragment,mSettings,"Settings");

            if(!mSettings.isVisible()) {
                if((mViewPager != null && mViewPager.isVisible()))
                    ft.hide(mViewPager);

                if((mWebFrag != null && mWebFrag.isVisible()))
                    ft.hide(mWebFrag);

                ft.show(mSettings);
            } else {
                if((mViewPager != null && mViewPager.isVisible()))
                    ft.hide(mViewPager);

                if((mWebFrag != null && mWebFrag.isVisible()))
                    ft.hide(mWebFrag);
            }
            ft.commit();
            break;
    }
}

*1: Let's remove that code block for example, when the fragment isn't there, and that block is not too, and you try to switch, the selected fragment may overlay on the previous one, or may show a blank one, thus we need to ShowHideFrags in it if it equals to the Current fragment, so we don't re-create it again and have two of the same fragments on top of each other.

Hope this answer helps you, feel free to ask and/or correct this answer.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM