android – FragmentPagerAdapter – how to detect a swipe or a tab click when user goes to a new tab?-ThrowExceptions

Exception or error:

I have a MainActivity that has three fragments in a FragmentPagerAdapter as below. How can I find out when an user goes from 1st fragment to second or from second to third, either with swiping or with a click on the tab? I saw that the getItem() method is not called always as I have declared mViewPager.setOffscreenPageLimit(2);

public class MainThreeTabAdapter extends FragmentPagerAdapter {

    private final String[] CONTENT = new String[]{"News", "Rewards", "Me"};

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

    @Override
    public Fragment getItem(int position) {
        if (position == 0) {
            return NewsFragment.newInstance();
        } else if (position == 1) {
            return RewardsFragment.newInstance();
        } else if (position == 2) {
            return MeFragment.newInstance(true, App.getAccountData().getId());
        } else {
            return null;
        }

    }

    @Override
    public CharSequence getPageTitle(int position) {
        return CONTENT[position % CONTENT.length];
    }

    @Override
    public int getCount() {
        return CONTENT.length;
    }
}

In the MainActivity’s onCreate()

    mainThreeTabAdapter = new MainThreeTabAdapter(getFragmentManager());

    // Set up the ViewPager with the sections adapter.
    // this ensures that 2 tabs on each side of current are kept in memory, which is all we need for our case. Default = 1
    // this is all taken from the Quickreturn facebook sample app
    mViewPager.setOffscreenPageLimit(2);
    mViewPager.setAdapter(mainThreeTabAdapter);
How to solve:

The getItem() method is only called when creating a view. To understand why getItem() isn’t being called, it helps to understand the default behavior of a ViewPager. By default, when you are on a particular page of a ViewPager it also creates the pages that are before and after this particular page. If you were to have 3 fragments named and in this order [a,b,c], and you were on page b, due to the default behavior of the ViewPager fragments a and c would already be created with a call to getItem(int). Because the fragments are already created, you won’t get another call to getItem()

Aside: this behavior can be modified with ViewPager.setOffScreenLimit()

What you actually want to do in order to be notified when a user switches pages is to set a OnPageChangeListener to the ViewPager using ViewPager.addOnPageChangeListener() to be notified when a page is selected.

###

    mViewPager.setAdapter(mSectionsPagerAdapter);
    mViewPager.setOffscreenPageLimit(4);
    mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
        @Override
        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

        }

        @Override
        public void onPageSelected(int position) {
            _position = position;
            switch (position) {
                case 0:
                    _header.setButtonVisible(1, View.GONE);
                    break;
                case 1:
                    _header.setButtonVisible(1, View.GONE);
                    break;
                case 2:
                    _header.setButtonVisible(1, View.VISIBLE);
                    break;
                case 3:
                    _header.setButtonVisible(1, View.VISIBLE);
                    break;
            }
        }

        @Override
        public void onPageScrollStateChanged(int state) {

        }
    });

###

First of all I guess you have following declared fields

ViewPager pager;
TabLayout tabs;
MainThreeTabAdapter adapter;

Next you have to create your adapter instance and set it to pager. Here you can add listener calling addOnPageChangeListener

adapter = new MainThreeTabAdapter(getSupportFragmentManager());
pager.setAdapter(adapter);
pager.addOnPageChangeListener(/*here is your listener instance*/);
tabs.setupWithViewPager(pager);

Always when page switches to new one by swiping or clicking on tab the method onPageSelected(int position) will be called.

If you want to get actual instance of current fragment to interact with it directly, you can create your own FragmentPagerAdapter which should cache created fragments:

public abstract class CachingFragmentPagerAdapter extends FragmentPagerAdapter {

    private final SparseArray<Fragment> registeredFragments = new SparseArray<>();

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

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        Fragment fragment = (Fragment) super.instantiateItem(container, position);
        registeredFragments.put(position, fragment);
        return fragment;
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        registeredFragments.remove(position);
        super.destroyItem(container, position, object);
    }

    public Fragment getRegisteredFragment(int position) {
        return registeredFragments.get(position);
    }

    public SparseArray<Fragment> getRegisteredFragments() {
        return registeredFragments;
    }
}

In this way you have to

  1. extend your MainThreeTabAdapter from CachingFragmentPagerAdapter
  2. add addOnPageChangeListener to ViewPager
  3. find and interact with fragment in onPageSelected method like as following:

    public void onPageSelected(int position) {
        final Fragment fragment = adapter.getRegisteredFragment(position);
        if (fragment instanceof NewsFragment) {
            //...
        } else ...
    }
    

###

If as I assume you are using a TabLayout with your ViewPager you should use addOnTabSelectedListener(). This will handle both an explicit tab click or a swipe.

mTabLayout.setupWithViewPager(mViewPager);

mTabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
        @Override
        public void onTabSelected(TabLayout.Tab tab) {
            Logger.d(TAG, "tabSelected: " + tab.getPosition());
        }

        @Override
        public void onTabUnselected(TabLayout.Tab tab) {
            Logger.d(TAG, "tabUnselected: " + tab.getPosition());
        }

        @Override
        public void onTabReselected(TabLayout.Tab tab) {
            Logger.d(TAG, "tabReselected: " + tab.getPosition());
        }
});

Leave a Reply

Your email address will not be published. Required fields are marked *