javaandroidandroid-fragmentsandroid-viewpagerandroid-adapter

Stop FragmentPagerAdapter from creating all its fragments at once


So I have bottom navigation bar with 4 fragments for each tab, and inside each one I call an API request to fetch some data, but the problem is each time I press any tab of the bar, at least two of the fragments gets created and they call their own method and by extension they fires the API request..! I just want the fragment that I select to be instantiated.!

I know the adapter behaves like this to pre-render the fragment to ensure better transaction between tabs and whatnot..! but I really can't afford to call multiple api calls with each select..!

Adapter

public class My_PagerAdapter extends FragmentPagerAdapter {
                       // I've tried FragmentStatePagerAdapter but same thing

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

  @Override
  public Fragment getItem(int position) {

    switch (position) {
      case 0:
         new MyFragment_1();
      case 1:
         new MyFragment_2();
      case 2:
         new MyFragment_3();
      case 3:
         new MyFragment_4();
    }

  }

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

Edit

This how I call the adapter..

ViewPager viewPager = main.findViewById(R.id.vp); 
viewPager.setOffscreenPageLimit(1); 
viewPager.setAdapter(new My_PagerAdapter (getChildFragmentManager()));
navigationTabBar.setModels(models); // just UI stuff for each tab offered by the bottom navigation bar library, 
navigationTabBar.setViewPager(viewPager);

Solution

  • Ok this is exactly an issue that i faced. The solution i have does not stop the viewpager from creating the fragments but it will stop the calls to network api.

    Heres the gist:

    1) Create an interface

    public interface ViewPagerLifeCycleManagerInterface {
      void onResumeAndShowFragment();
      void onPauseAndHideFragment();
      //void refreshFragment();
    }
    

    2) modify your FragmentPagerAdapter to override the onInstantiateItem method

    Here each Fragment will have a weakReference declared inside the Adapter class in order to store a reference to the fragments created

        @Override
    public Object instantiateItem(ViewGroup container, int position){
    
        Fragment createdFragment = (Fragment) super.instantiateItem(container, position);
    
        switch (position){
            case 0:
                xyzWeakReference=null;
                xyzFragmentWeakReference=new WeakReference<>((xyz)createdFragment);
                break;
    
            case 1:
                xyz1WeakReference=null;
                xyz1WeakReference=new WeakReference<>((xyz1WeakReference)createdFragment);
                break;
    
        }
    
        return createdFragment;
    };
    

    3) Inside the FragmentPagerAdapter, add the following method in order to fetch the weak reference of the fragment in picture

        public Fragment getFragmentAtGivenPosition(int i){
        switch (i){
            case 0:
                if(xyzFragmentWeakReference == null){
                    return null;
                }
                return xyzFragmentWeakReference.get();
            case 1:
                if(xyz1FragmentWeakReference == null){
                    return null;
                }
                return xyz1FragmentWeakReference.get();
    
        }
    }
    

    4) Now in the activity where the TabLayout is created and the view pager instantiated, attach a listener to the TabLayout for listening to tab changes

            tabLayout_bookmarks.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
            @Override
            public void onTabSelected(final TabLayout.Tab tab) {
                //let the instantiateItem have some time to be called by the adapter
    
                currentFragmentIndex = tab.getPosition();
    
                new Handler().postDelayed(new Runnable() {
                    @Override
                    public void run() {
    
                        ViewPagerLifeCycleManagerInterface currentFragment = (ViewPagerLifeCycleManagerInterface)btca.getFragmentAtGivenPosition(tab.getPosition());
    
                        if(currentFragment!=null){
                            currentFragment.onResumeAndShowFragment();
                        }else{
                            //Log.d("FragmentCreate","Current fragment is null and fucked up in adapter");
    
                            //if it is null ... that means the adapter hasn't yet called instantiate item ... this internally calls get item any way
                            //.....
    
                            //This shouldn't really hit but in case it does ... keep a handler in order to ensure that everything is created
                            new Handler().postDelayed(new Runnable() {
                                @Override
                                public void run() {
                                    ViewPagerLifeCycleManagerInterface localFragment  = (ViewPagerLifeCycleManagerInterface)btca.getItem(tab.getPosition());
                                    //getItem never returns a null fragment unless supplied a horrendous value for position
                                    //by the time these 50 ms pass, the instantiate item should surely have been called
                                    //else it will be an empty space ... no crash though
                                    localFragment.onResumeAndShowFragment();
                                }
                            },50);
                        }
    
                    }
                },100);
    
            }
    
            @Override
            public void onTabUnselected(final TabLayout.Tab tab) {
                new Handler().postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        ViewPagerLifeCycleManagerInterface currentFragment = (ViewPagerLifeCycleManagerInterface)btca.getFragmentAtGivenPosition(tab.getPosition());
                        if(currentFragment!=null){
                            currentFragment.onPauseAndHideFragment();
                        }else{
                            //Log.d("FragmentCreateTab","the fucking fragment was null");
                            //if it is null ... that means the adapter hasn't yet called instantiate item ... this internally calls get item any way
                            //.....
    
                            //This shouldn't really hit but in case it does ... keep a handler in order to ensure that everything is created
                            new Handler().postDelayed(new Runnable() {
                                @Override
                                public void run() {
                                    ViewPagerLifeCycleManagerInterface localFragment  = (ViewPagerLifeCycleManagerInterface)btca.getItem(tab.getPosition());
                                    //getItem never returns a null fragment unless supplied a horrendous value for position
                                    //by the time these 50 ms pass, the instantiate item should surely have been called
                                    //else it will be an empty space ... no crash though
                                    localFragment.onPauseAndHideFragment();
                                }
                            },50);
    
                        }
    
                    }
                },100);
            }
    
            @Override
            public void onTabReselected(TabLayout.Tab tab) {
                //do nothing
            }
        });
    

    5) In each of the Fragments inside the Viewpager, implement the Interface we created in step 1 and override the methods.

    Create a boolean variable in each fragment amIVisible... This will help decide when the fragment is visible and when it can call the network api

    a) here for the first fragment in viewpager, i.e at 0 index, the network api call has to happen immediately after the view gets created. This fragment is obviously visible by default. This is written inside onCreateView method

            if(dataList!=null && dataList.size()==0) {
            if (savedInstanceState==null) {
                //your api call to load from net
            } else {
                if (savedInstanceState.getBoolean("savedState")) {
                    //If you have saved data in state save, load it here
                } else {
                    //only fire the async if the current fragment is the one visible, else the onResumeAndShowFragment will trigger the same async when it becomes visible
                    if (savedInstanceState.getBoolean("amIVisible")) {
                        //Load data from net
                    }
                }
            }
        }
    

    The other methods are as follows for the first fragment

        @Override
    public void onResumeAndShowFragment() {
        amIVisible=true;
    
    
        if(dataList!=null && dataList.size()==0){
            new Handler().postDelayed(new Runnable() {
                @Override
                public void run() {
                    //Load data from net if data was not found,
                    //This basically means auto refresh when user scrolls back and the fragment had no data
                }
            },400);
        }
    }
    
    @Override
    public void onPauseAndHideFragment() {
        amIVisible=false;
    }
    

    Here i have overriden onSaveInstanceState method and saved the value of amIVisible and savedState is a boolean which indicates if the list has at least 1 item or not.

    b) For the other fragments, Data will be loaded by the following process

            if(savedInstance!=null){
    
            if (savedInstance.getBoolean("savedState")) {
                new Handler().postDelayed(new Runnable() {
                    @Override
                    public void run() {
                       //load data from saved State
                    }
                },100);
    
            } else {
                //only fire the async if the current fragment is the one visible, else the onResumeAndShowFragment will trigger the same async when it becomes visible
    
    
                if (savedInstance.getBoolean("amIVisible")) {
    
                    new Handler().postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            //load data from net
                        }
                    },100);
    
                }
            }
        }
    

    The interface methods are the same for the other fragments.

    This is pretty complicated but does the job. The weak references inside the adapter even allow garbage collection and avoid context leaks.