Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android ViewPager with tab not maintaining state after screen rotation

I'm implementing a ViewPager with tabs similar to implementation provided by Google here.

My app has the following behaviour. My ViewPager has 3 "pages" (fragA, fragB, fragC) and I'm implementing 3 tabs on top of the ViewPager. The differece between google's implementation and mine is that the tabs aren't used to navigate between fragments. As an example, pressing one of my tabs, loads a different set of data on the visible fragment. I can have fragA with tab3 selected, fragB with tab2 selected and fragC with tab1 selected.

The problem is when I rotate the screen. If I'm on FragmentA with the third tab selected, when I rotate the screen, I stay in the same fragment but now, the first tab is selected. I want to maintain the same tab selected when rotating the screen.

This is my class for managing the tabs:

public class ManagerTabs extends Fragment {

    private TabHost.TabContentFactory mFactory = new TabHost.TabContentFactory() {

        @Override
        public View createTabContent(String tag) {
            View v = new View(getActivity());
            v.setMinimumWidth(0);
            v.setMinimumHeight(0);
            return v;
        }
    };

    public static ManagerTabs newManager() {
        return new ManagerTabs();
    }


    @Override
    public View onCreateView(LayoutInflater inflater,  ViewGroup container,
     Bundle savedInstanceState) {
        view = inflater.inflate(R.layout.datatabs, container, false);
        createTabs();
        createPager();
        return view;
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        if (savedInstanceState != null) {
            currentPage = savedInstanceState.getInt("currentPage", currentPage);
            currentTab = savedInstanceState.getInt("currentTab", currentTab);
            mTabHost.setCurrentTab(currentTab);
        }
        mPager.setCurrentItem(currentPage);
    }

    private void createPager() {
        mPager = (ViewPager) view.findViewById(R.id.datapager);
        pagerAdapter = new PagerAdapter(getChildFragmentManager());
        mPager.setAdapter(pagerAdapter);

        IconPageIndicator indicator = (IconPageIndicator) rootView.findViewById(R.id.indicator);
        indicator.setViewPager(pager);
        indicator.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
            @Override
            public void onPageSelected(int position) {
                currentTab = mTabHost.getCurrentTab();

                currentPage = mPager.getCurrentItem();

                switch (currentPage) {
                case 0:
                    ((FragmentA) pagerAdapter.getCurrentFragment(currentPage)).getData(currentTab);
                    break;
                case 1:
                    ((FragmentB) pagerAdapter.getCurrentFragment(currentPage)).getData(currentTab);
                    break;
                case 2:
                    ((FragmentC) pagerAdapter.getCurrentFragment(currentPage)).getData(currentTab);
                    break;
                }
            }
        });
    }

    private void createTabs() {

        mTabHost = (TabHost) view.findViewById(android.R.id.tabhost);
        mTabHost.setup();

        mTabHost.addTab(mTabHost.newTabSpec(getString(R.string.day))setIndicator(getString(R.string.day)).setContent(mFactory));
        mTabHost.addTab(mTabHost.newTabSpec(getString(R.string.month)).setIndicator(getString(R.string.month)).setContent(mFactory));
        mTabHost.addTab(mTabHost.newTabSpec(getString(R.string.year)).setIndicator(getString(R.string.year)).setContent(mFactory));

        mTabHost.setOnTabChangedListener(new TabHost.OnTabChangeListener() {
            public void onTabChanged(String tag) {
                currentTab = mTabHost.getCurrentTab();
                currentPage = mPager.getCurrentItem();
                switch (currentPage) {
                case 0:
                    ((FragmentA) pagerAdapter.getCurrentFragment(currentPage)).getData(currentTab);
                    break;
                case 1:
                    ((FragmentB) pagerAdapter.getCurrentFragment(currentPage)).getData(currentTab);
                    break;
                case 2:
                    ((FragmentC) pagerAdapter.getCurrentFragment(currentPage)).getData(currentTab);
                    break;
                }
            }
        });
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putInt("currentPage", mPager.getCurrentItem());
        outState.putInt("currentTab", mTabHost.getCurrentTab());
    }
}

And this is the adapter that creates the fragments:

public class PagerAdapter extends FragmentPagerAdapter implements IconPagerAdapter{

    private Fragment[] currentFragment = new Fragment[3];

    public PagerAdapter(FragmentManager fm) {
        super(fm);
    }

    @Override
    public Fragment getItem(int index) {
        Fragment fragment;
        switch (index) {
        case 0:
            fragment = FragmentA.newInstance();
            break;
        case 1:
            fragment = FragmentB.newInstance();
            break;
        case 2:
            fragment = FragmentC.newInstance();
            break;
        default:
            fragment = null;
            break;
        }

        currentFragment[index] = fragment;

        return fragment;
    }

    @Override
    public int getIconResId(int index) {

        switch (index) {
        case 0:
            return R.drawable.fraga;
        case 1:
            return R.drawable.fragb;
        case 2:
            return R.drawable.fragc;
        default:
            return -1;
        }
    }

    @Override
    public int getCount() {
        return 3;
    }

    public Fragment getCurrentFragment(int currentFrag) {
        return currentFragment[currentFrag];
    }

    public Fragment[] getCurrentFragment() {
        return currentFragment;
    }
}

And finally, a Fragment (FragmentA, FragmentB and FragmentC) are almost identically:

public class FragmentA extends Fragment {

    public static FragmentA newInstance() {
        return new FragmentA();
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        rootView = inflater.inflate(R.layout.datafragment, container, false);

        mTabHost = (TabHost) ((View) container.getParent().getParent()).findViewById(android.R.id.tabhost);

        if (savedInstanceState != null) {
            currentTab = savedInstanceState.getInt("currentTab");
        }
        populateLayout(currentTab);

        return rootView;
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putInt("currentTab", currentTab);
    }
}

The problem is that all my onCreateView are always called twice and the second time that they are called, the information is lost. I've debugged and saw this behavior,

When I rotate the screen, for example in FragmentA and with the third tab selected, this happens:

01  ManagerTabs -> onSaveInstanceState
02  FragmentA -> onSaveInstanceState
03  ManagerTabs -> onCreateView
04  ManagerTabs -> onViewCreated

In the 04 call (ManagerTabs -> onViewCreated), mTabHost.setCurrentTab(currentTab); is called but inside onTabChanged, I get an java.lang.NullPointerException because all the fragments inside the adapter are all null.

What can I do so that if I rotate the screen, I can maintain the current selected tab?

like image 756
Favolas Avatar asked Dec 23 '14 11:12

Favolas


1 Answers

After screen rotation new instance of FragmentPagerAdapter is created. But the ViewPager restores its state and state of all fragments it contains. ViewPager doesn't call adapters getView() method. Therefore Fragment[] currentFragment contains null values only.

You can get current fragment by another ways.

If using FragmentPagerAdapter:

FragmentPagerAdapter sets tags on fragments it passes to FragmentManager. This id depends on page index. How this id is generated you can see in source of the latest support lib.

To achieve this just add following method to your ManagerTabs fragment:

public Fragment getCurrentPagerFragment(int position) {
    String fragmentTag = "android:switcher:" + mPager.getId() + ":" + position;
    return getChildFragmentManager().findFragmentByTag(fragmentTag);
}

If using FragmentStatePagerAdapter:

It doesn't sets tags on fragments. With FragmentStatePagerAdapter it seems we can get by, using its instantiateItem(ViewGroup container, int position) call. It returns reference to fragment at position position. If FragmentStatePagerAdapter already holds reference to fragment in question, instantiateItem just returns reference to that fragment, and doesn't call getItem() to instantiate it again. In this case our method will be:

public Fragment getCurrentPagerFragment(int position) {
    FragmentStatePagerAdapter a = (FragmentStatePagerAdapter) mPager.getAdapter();
    return (Fragment) a.instantiateItem(pager, position);
}

Finally:

Now use above method to get current fragment. In your case:

mTabHost.setOnTabChangedListener(new TabHost.OnTabChangeListener() {
    public void onTabChanged(String tag) {
        currentTab = mTabHost.getCurrentTab();
        currentPage = mPager.getCurrentItem();
        Fragment currentFragment = getCurrentPagerFragment(currentPage);
        switch (currentPage) {
            case 0:
                ((FragmentA) currentFragment).getData(currentTab);
                break;
            case 1:
                ((FragmentB) currentFragment).getData(currentTab);
                break;
            case 2:
                ((FragmentC) currentFragment).getData(currentTab);
                break;
        }
    }
});
like image 187
erakitin Avatar answered Oct 06 '22 01:10

erakitin