226

Below is my code which has 3 Fragment classes each embedded with each of the 3 tabs on ViewPager. I have a menu option. As shown in the onOptionsItemSelected(), by selecting an option, I need to update the fragment that is currently visible. To update that I have to call a method which is in the fragment class. Can someone please suggest how to call that method?

public class MainActivity  extends ActionBarActivity {

     ViewPager ViewPager;
     TabsAdapter TabsAdapter;

     @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);

            ViewPager = new ViewPager(this);
            ViewPager.setId(R.id.pager);
            setContentView(ViewPager);

            final ActionBar bar = getSupportActionBar();

            bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);

            //Attaching the Tabs to the fragment classes and setting the tab title.
            TabsAdapter = new TabsAdapter(this, ViewPager);
            TabsAdapter.addTab(bar.newTab().setText("FragmentClass1"),
                    FragmentClass1.class, null);
            TabsAdapter.addTab(bar.newTab().setText("FragmentClass2"),
              FragmentClass2.class, null);
            TabsAdapter.addTab(bar.newTab().setText("FragmentClass3"),
              FragmentClass3.class, null);


            if (savedInstanceState != null) {
                bar.setSelectedNavigationItem(savedInstanceState.getInt("tab", 0));
            }

        }

        @Override
        public boolean onOptionsItemSelected(MenuItem item) {

            switch (item.getItemId()) {

            case R.id.addText:

           **// Here I need to call the method which exists in the currently visible Fragment class**

                    return true;

            }

            return super.onOptionsItemSelected(item);
        }


     @Override
     protected void onSaveInstanceState(Bundle outState) {
      super.onSaveInstanceState(outState);
            outState.putInt("tab", getSupportActionBar().getSelectedNavigationIndex());

     }

     public static class TabsAdapter extends FragmentPagerAdapter
      implements ActionBar.TabListener, ViewPager.OnPageChangeListener {

      private final Context mContext;
            private final ActionBar mActionBar;
            private final ViewPager mViewPager;
            private final ArrayList<TabInfo> mTabs = new ArrayList<TabInfo>();

            static final class TabInfo {
                private final Class<?> clss;
                private final Bundle args;

                TabInfo(Class<?> _class, Bundle _args) {
                    clss = _class;
                    args = _args;
                }
            }

      public TabsAdapter(ActionBarActivity activity, ViewPager pager) {
       super(activity.getSupportFragmentManager());
                mContext = activity;
                mActionBar = activity.getSupportActionBar();
                mViewPager = pager;
                mViewPager.setAdapter(this);
                mViewPager.setOnPageChangeListener(this);
            }

      public void addTab(ActionBar.Tab tab, Class<?> clss, Bundle args) {
                TabInfo info = new TabInfo(clss, args);
                tab.setTag(info);
                tab.setTabListener(this);
                mTabs.add(info);
                mActionBar.addTab(tab);
                notifyDataSetChanged();

            }

      @Override
      public void onPageScrollStateChanged(int state) {
       // TODO Auto-generated method stub

      }

      @Override
      public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
       // TODO Auto-generated method stub

      }

      @Override
      public void onPageSelected(int position) {
       // TODO Auto-generated method stub
       mActionBar.setSelectedNavigationItem(position);
      }

      @Override
      public void onTabReselected(Tab tab, FragmentTransaction ft) {
       // TODO Auto-generated method stub

      }

      @Override
      public void onTabSelected(Tab tab, FragmentTransaction ft) {
       Object tag = tab.getTag();
                for (int i=0; i<mTabs.size(); i++) {
                    if (mTabs.get(i) == tag) {
                        mViewPager.setCurrentItem(i);

                    }
                }

                tabPosition = tab.getPosition();
      }

      @Override
      public void onTabUnselected(Tab tab, FragmentTransaction ft) {
       // TODO Auto-generated method stub

      }

      @Override
      public Fragment getItem(int position) {
       TabInfo info = mTabs.get(position);
                return Fragment.instantiate(mContext, info.clss.getName(), info.args);
      }

      @Override
      public int getCount() {
       return mTabs.size();
      }

     }

    }

Suppose below is the fragment class with the method updateList() I want to call:

 public class FragmentClass1{

    ArrayList<String> originalData;


    @Override
         public View onCreateView(LayoutInflater inflater, ViewGroup container,
           Bundle savedInstanceState) {

          View fragmentView = inflater.inflate(R.layout.frag1, container, false);

          originalData = getOriginalDataFromDB();

          return fragmentView;

         }


    public void updateList(String text)
    {
       originalData.add(text);
       //Here I could do other UI part that need to added
    }
}
rick
  • 4,395
  • 10
  • 24
  • 40
  • which method of fragment you want to call? – Biraj Zalavadia Sep 04 '13 at 08:49
  • @BirajZalavadia My own defined method, which has a parameter in it and uses the instance variables of that fragment class which have already been initialized while creating a view even. – rick Sep 04 '13 at 08:51
  • 1
    can you give the Class name of Your fragments which are in viewpager? – Biraj Zalavadia Sep 04 '13 at 08:54
  • some solution http://stackoverflow.com/questions/7379165/update-data-in-listfragment-as-part-of-viewpager – An-droid Sep 04 '13 at 08:55
  • The simplest way of accessing the visible fragments would be through http://stackoverflow.com/questions/7379165/update-data-in-listfragment-as-part-of-viewpager?answertab=votes#tab-top , look at the top two answers. – user Sep 04 '13 at 08:55
  • @BirajZalavadia I updated my question. – rick Sep 04 '13 at 08:58
  • @Yume117 I did see this, but I am unable to get where to use that. Do I need to again find the viewpager by Id though I have reference. – rick Sep 04 '13 at 09:00
  • @Luksprog I saw this. But I have 3 classes here. Do I need to again find the viewpager ? Even I am getting nullpointerexception when I use this as the instance variable, in the above code it's arraylist. Because it seems the object is again being created instead of getting the already associated reference of the fragment. – rick Sep 04 '13 at 09:00
  • This shouldn't be a problem. If you have three types of fragment you know which type belongs to which position in the `ViewPager`. If `FragmentClass1` is the fragment on the first position then if the `ViewPager.getCurrentItem()` returns 0 it means you need to cast it to `FragmentClass1` and call the method. – user Sep 04 '13 at 09:07
  • First you should change the name of your viewpager. Generally do not name the variable the same as there type, ViewPager should not be named ViewPager. – An-droid Sep 04 '13 at 09:16
  • take a look here http://stackoverflow.com/questions/18567762/need-to-handle-click-from-non-activity-java-class/18568155#18568155 – An-droid Sep 04 '13 at 09:19
  • @Luksprog I did it, but I am getting the same NPE problem for the instance variables of that fragment class. In the question above, for OriginalData arraylist :( – rick Sep 04 '13 at 09:55
  • @rick, does your fragment save application state? i see you're using onSaveInstanceState(Bundle outState) method, because i am having issues saving state..so i was forced to use setOffscreenPageLimit, your reply will be appreciated. thanks – Tosin Onikute May 28 '15 at 13:47
  • This is the simplest and most reliable solution: http://stackoverflow.com/a/28379798/396005 – Bron Davies Jan 22 '16 at 06:35
  • See this answer http://stackoverflow.com/a/42529702/3496570 – AndroidGeek Mar 01 '17 at 11:02
  • getSupportFragmentManager().getFragments().get(viewPager.getCurrentItem()); Works just fine – Gaurav Pangam Aug 08 '18 at 12:24

31 Answers31

208

by selecting an option, I need to update the fragment that is currently visible.

A simple way of doing this is using a trick related to the FragmentPagerAdapter implementation:

case R.id.addText:
     Fragment page = getSupportFragmentManager().findFragmentByTag("android:switcher:" + R.id.pager + ":" + ViewPager.getCurrentItem());
     // based on the current position you can then cast the page to the correct 
     // class and call the method:
     if (ViewPager.getCurrentItem() == 0 && page != null) {
          ((FragmentClass1)page).updateList("new item");     
     } 
return true;

Please rethink your variable naming convention, using as the variable name the name of the class is very confusing(so no ViewPager ViewPager, use ViewPager mPager for example).

user
  • 85,380
  • 17
  • 189
  • 186
  • 24
    Is that fragment tag future-proof, if the API changes? – Maarten Jan 28 '14 at 22:18
  • 7
    @Maarten No, it's just a common hack that has been used since the adapter appeared(and its source code was available), it hasn't changed yet. If you want something more reliable you'll need to use other options, most notable overriding the `instantiateItem()` method and getting references to the proper fragments yourself. – user Jan 29 '14 at 06:48
  • 5
    After hours of frustration and asking the pages directly from the `mAdapter.getItem(position)`, awesome... At least now I know that the other 8512 trials didn't work. Thanks – Diolor Apr 07 '14 at 20:24
  • 2
    It is very hard to understand why the FragmentViewPager does not offer a method for accessing the current fragment. Since it is such a basic functionality, is there anyone who knows why they omitted that? – Antonio Sesto Mar 29 '15 at 08:01
  • @AntonioSesto That would be a question only the platform engineers could answer. My assumption would be they initially wanted to keep the API very simple and then abandoned the idea as users got over the issue. – user Mar 29 '15 at 09:15
  • 1
    Well, allowing the access to the current fragment would not be so complicated, but they force us to re-write code that they have hidden in the class and that can potentially break with future updates. – Antonio Sesto Mar 29 '15 at 10:09
  • 2
    This solution doesn't seem to workout for me....I'm receiving the page =null in my case....Can you please help me out.. – Harsha Apr 10 '15 at 13:28
  • @Harsha Please post a new question with your problem. What I wrote should work unless you're doing something fishy with the id of the ViewPager. – user Apr 10 '15 at 15:39
  • 30
    Note this does not work if you use FragmentStatePagerAdapter rather than FragmentPagerAdapter. – Shawn Lauzon Jun 29 '15 at 19:40
  • 3
    @ShawnLauzon The FragmentStatePagerAdapter has a different implementation, in its case you call its instantiateItem() method to get the visible fragments. – user Jun 30 '15 at 03:35
  • 3
    @Luksprog Yes, you're right. The method names they chose sure are weird. "Instantiate" often instantiates nothing :/ – Shawn Lauzon Jul 01 '15 at 00:42
  • @ShawnLauzon Thanks! using FragmentPagerAdapter rather than FragmentStatePagerAdapter did it for me! – Gmeister4 Jan 04 '16 at 12:45
  • Although i really don't like this fix I will choose this one and use it in my app. It is nasty in the sense that it uses platform knowledge (i.e. the tags used by FragmentPagerAdapter) to provide a solution, but it is a nasty solution to a nasty problem, which sounds quite symmetric. And programmers love symmetry. – baske Jan 28 '16 at 12:26
  • what is this condition `ViewPager.getCurrentItem() == 0`? Shouldn't be `ViewPager.getCurrentItem() != 0`? – VSB Aug 16 '17 at 14:48
  • @VSB The FragmentClass1 is used in the adapter in position 0 so we will cast to that fragment only when the current visible item in the adapter is position 0. If we were to use ViewPager.getCurrentItem() != 0 you'll end up with a ClassCastException if the current visible fragment isn't anything besides position 0. – user Aug 16 '17 at 16:11
  • Note that this implementation does not work for `FragmentStatePagerAdapter`. I had to switch to `FragmentPagerAdapter`. For `FragmentStatePagerAdapter` the `findFragmentByTag("android:switcher:${R.id.myViewPagerId}:${myViewPager.currentItem}")` usually returns `null`. – Michal Vician May 11 '18 at 09:25
  • 1
    @Luksprog I wanna do the same thing in Kotlin. I referred to your answer but got page as null. – Nir Patel Aug 06 '18 at 11:43
  • 1
    you should never rely on undocumented features of internal code. they are undocumented for a reason. – morgwai Jun 10 '19 at 19:26
161
    public class MyPagerAdapter extends FragmentPagerAdapter {
        private Fragment mCurrentFragment;

        public Fragment getCurrentFragment() {
            return mCurrentFragment;
        }
//...    
        @Override
        public void setPrimaryItem(ViewGroup container, int position, Object object) {
            if (getCurrentFragment() != object) {
                mCurrentFragment = ((Fragment) object);
            }
            super.setPrimaryItem(container, position, object);
        }
    }
Mindphaser
  • 2,139
  • 1
  • 15
  • 5
  • 33
    Google should provide FragmentPageAdapter.getCurrentPrimaryItem() returning mCurrentPrimaryItem. – eugene Jul 29 '14 at 00:04
  • 1
    I use this technique as well.I also define a PagerFragment with methods `onPageSelected` / `onPageDeselected` and have all my pager fragments extend it. Then `setPrimaryitem` can call these methods appropriately when the user swipes to a new page. This means you may implement a lazier strategy for loading of data in pages that aren't even shown. You should however bear in mind that during page transition it looks better if the next page is already populated with data, so lazy loading all data in `onPageSelected` may not be ideal. – JHH Dec 18 '15 at 09:59
  • 3
    Why the `if` statement? Fragment just calls up to the `equals` method of object, which is shallow comparison. So why not just always call `mCurrentFragment = ((Fragment) object);` ? – Overclover May 16 '17 at 14:42
  • tried and it works fine, just cast the getCurrentFragment() to the specific Fragment it should – PhilipJ Mar 03 '20 at 06:03
116

First of all keep track of all the "active" fragment pages. In this case, you keep track of the fragment pages in the FragmentStatePagerAdapter, which is used by the ViewPager.

@Override
public Fragment getItem(int index) {
    Fragment myFragment = MyFragment.newInstance();
    mPageReferenceMap.put(index, myFragment);
    return myFragment;
}

To avoid keeping a reference to "inactive" fragment pages, you need to implement the FragmentStatePagerAdapter's destroyItem(...) method:

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

and when you need to access the currently visible page, you then call:

int index = mViewPager.getCurrentItem();
MyAdapter adapter = ((MyAdapter)mViewPager.getAdapter());
MyFragment fragment = adapter.getFragment(index);

Where the MyAdapter's getFragment(int) method looks like this:

public MyFragment getFragment(int key) {
    return mPageReferenceMap.get(key);
}

Hope it may help!

Zon
  • 12,838
  • 4
  • 69
  • 82
FraZer
  • 1,637
  • 1
  • 9
  • 17
  • Can you please explain clearly? Where do I need to keep this FragmentStatePagerAdapter class in my code? BTW I have 3 fragment classes, not single MyFragmentClass. – rick Sep 04 '13 at 09:36
  • 7
    Even better if you update mPageReferenceMap in instantiateItem instead of getItem because it also works after orientation change. '@Override public Object instantiateItem(ViewGroup container, int position) { Fragment fragment = (Fragment)Super.instantiateItem(container, position); mPageReferenceMap.put(position, fragment); return fragment; }' – pmellaaho Feb 13 '14 at 07:25
  • What type is mPageReferenceMap? – DoruAdryan Oct 15 '14 at 17:31
  • Yes, what is mPageReferenceMap? – real 19 Feb 07 '15 at 07:31
  • mPageReferenceMap is a Map. You can initialize it using *Map mPageReferenceMap = new HashMap" -- or your favorite Map<> implementation. – LeoFarage Jun 18 '15 at 16:13
  • 11
    This answer is really bad! You shouldn't keep the cache of a ViewPager manually since it handle it by itself. The ViewPager decide when fragment should be allocated or not, what to pause and when, keeping a reference on every fragment will add some useless overhead and is not optimized for the memory. – Livio Nov 06 '15 at 15:17
  • Thanks! I guess this solution is not recommendable, but it's the only one that I think works for me. I needed to get the current fragment (using `ViewPager.OnPageChangeListener`) to update something there, but I also needed to update views of the other "alive" fragments. – Ferran Maylinch Dec 31 '15 at 18:02
  • 3
    I tried this approach but unfortunately it doesn't work when the activity is recreated. On Android N and later, you can see this when using the multiwindow feature. When your app is open, long hold the 'recents' button (the square one). The activity and fragment are recreated from savedInstanceState, the adapter is recreated (so the mPageReferenceMap is now empty), but getItem is not called again. The map stays empty so adapter.getFragment(index) will crash. – Martin Konicek Jan 11 '18 at 11:30
  • Yeah this is unreliable across Activity recreations. Use this answer instead: https://stackoverflow.com/a/29269509/1866373 – Daniel Wilson Jan 06 '20 at 18:23
105

This is the only way I don't get NullPointerException for the instance variables of that particular fragment classes. This might be helpful for others who stuck at the same thing. In the onOptionsItemSelected(), I coded the below way:

if(viewPager.getCurrentItem() == 0) {
    FragmentClass1 frag1 = (FragmentClass1)viewPager
                            .getAdapter()
                            .instantiateItem(viewPager, viewPager.getCurrentItem());
    frag1.updateList(text); 
} else if(viewPager.getCurrentItem() == 1) {
    FragmentClass2 frag2 = (FragRecentApps)viewPager
                            .getAdapter()
                            .instantiateItem(viewPager, viewPager.getCurrentItem());
    frag2.updateList(text);
}
ZooMagic
  • 497
  • 6
  • 12
rick
  • 4,395
  • 10
  • 24
  • 40
  • 4
    I think this is the most correct answer - align with the design of `FragmentStatePagerAdapter` without overriding any code. In fact, it should be as simple as a function just return `viewPager.getAdapter().instantiateItem(viewPager, viewPager.getCurrentItem())`. – John Pang Dec 30 '16 at 17:44
41

FragmentStatePagerAdapter has public method with the name instantiateItem that return your fragment based on specified parameter values, this method has two parameters ViewGroup (ViewPager) and position.

public Object instantiateItem(ViewGroup container, int position);

Used this method to get specified position's fragment,

Fragment fragment = (Fragment) adaper.instantiateItem(mViewPager, position);
Alexander Farber
  • 18,345
  • 68
  • 208
  • 375
Krunal Shah
  • 1,388
  • 1
  • 16
  • 29
  • 5
    But this doesn't return the current already instantiated fragment, but a new one instead, no? Thus name "instantiate"... – Ixx Dec 11 '18 at 11:41
  • 1
    @Ixx No it does not create a new fragment. I think method name is incorrect here. – Bugs Happen Mar 06 '19 at 11:23
15

I know its too late but I have really simple ways of doing it,

// for fragment at 0 possition

((mFragment) viewPager.getAdapter().instantiateItem(viewPager, 0)).yourMethod();
Pratik Mhatre
  • 643
  • 6
  • 12
12
getSupportFragmentManager().getFragments().get(viewPager.getCurrentItem());

Cast the instance retreived from above line to the fragment you want to work on with. Works perfectly fine.

viewPager

is the pager instance managing the fragments.

Gaurav Pangam
  • 282
  • 3
  • 7
11

There are a lot of answers here that don't really address the basic fact that there's really NO WAY to do this predictably, and in a way that doesn't result you shooting yourself in the foot at some point in the future.

FragmentStatePagerAdapter is the only class that knows how to reliably access the fragments that are tracked by the FragmentManager - any attempt to try and guess the fragment's id or tag is not reliable, long-term. And attempts to track the instances manually will likely not work well when state is saved/restored, because FragmentStatePagerAdapter may well not call the callbacks when it restores the state.

About the only thing that I've been able to make work is copying the code for FragmentStatePagerAdapter and adding a method that returns the fragment, given a position (mFragments.get(pos)). Note that this method assumes that the fragment is actually available (i.e. it was visible at some point).

If you're particularly adventurous, you can use reflection to access the elements of the private mFragments list, but then we're back to square one (the name of the list is not guaranteed to stay the same).

Melllvar
  • 1,939
  • 3
  • 23
  • 46
  • 1
    to deal with the "not work well when state is saved/restored", I override the method saveState(). So that the pager does not save any fragment when the activity is paused. it works. – AntoineP Aug 14 '15 at 16:41
  • 2
    That just adds to the patchwork, without fixing the underlying issue. I'm not arguing that it doesn't work - just that it's not a good implementation. – Melllvar Oct 19 '15 at 02:02
7

by selecting an option, I need to update the fragment that is currently visible.

To get a reference to currently visible fragment, assume you have a reference to ViewPager as mPager. Then following steps will get a reference to currentFragment:

  1. PageAdapter adapter = mPager.getAdapter();
  2. int fragmentIndex = mPager.getCurrentItem();
  3. FragmentStatePagerAdapter fspa = (FragmentStatePagerAdapter)adapter;
  4. Fragment currentFragment = fspa.getItem(fragmentIndex);

The only cast used line 3 is valid usually. FragmentStatePagerAdapter is an useful adapter for a ViewPager.

Robert
  • 8,521
  • 12
  • 58
  • 108
user4665465
  • 81
  • 2
  • 3
7

Best way to do this, just call CallingFragmentName fragment = (CallingFragmentName) viewPager .getAdapter() .instantiateItem(viewPager, viewPager.getCurrentItem()); It will re-instantiate your calling Fragment, so that it will not throw null pointer exception and call any method of that fragment.

Ritesh Adulkar
  • 621
  • 10
  • 16
6

Current Fragment:

This works if you created a project with the fragments tabbar template.

Fragment f = mSectionsPagerAdapter.getItem(mViewPager.getCurrentItem());

Note that this works with the default tabbed activity template implementation.

hasan
  • 22,029
  • 10
  • 58
  • 94
  • 33
    This depends on your implementation of getItem() - it may return a new instance of the fragment, which is probably not what you want. – Tom Feb 18 '16 at 00:36
  • This is how u do it. if you still have the default configuration when you create a tabor with fragments project. none had this solution. so I thought it would help. – hasan Feb 18 '16 at 19:51
4

I have used the following:

 int index = vpPager.getCurrentItem();
 MyPagerAdapter adapter = ((MyPagerAdapter)vpPager.getAdapter());
 MyFragment suraVersesFragment = (MyFragment)adapter.getRegisteredFragment(index);
BlaShadow
  • 8,375
  • 7
  • 32
  • 58
real 19
  • 731
  • 8
  • 31
  • 1
    This relies on a custom pager implementation from the answer found here: http://stackoverflow.com/questions/8785221/retrieve-a-fragment-from-a-viewpager – Tom Redman Feb 23 '15 at 23:29
  • 1
    This is exactly what I was trying to do but without knowing which methods to override, would have wasted hours - thanks! – Bron Davies Jan 22 '16 at 06:34
3

When we use the viewPager, a good way to access the fragment instance in activity is instantiateItem(viewpager,index). //index- index of fragment of which you want instance.

for example I am accessing the fragment instance of 1 index-

Fragment fragment = (Fragment) viewPageradapter.instantiateItem(viewPager, 1);

if (fragment != null && fragment instanceof MyFragment) {      
 ((MyFragment) fragment).callYourFunction();

}
Royz
  • 145
  • 15
2

FragmentStatePagerAdapter has a private instance variable called mCurrentPrimaryItem of type Fragment. One can only wonder why Android devs did not supplied it with a getter. This variable is instantiated in setPrimaryItem() method. So, override this method in such a way for you to get the reference to this variable. I simply ended up with declaring my own mCurrentPrimaryItem and copying the contents of setPrimaryItem() to my override.

In your implementation of FragmentStatePagerAdapter:

private Fragment mCurrentPrimaryItem = null;

@Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
    Fragment fragment = (Fragment)object;
    if (fragment != mCurrentPrimaryItem) {
        if (mCurrentPrimaryItem != null) {
            mCurrentPrimaryItem.setMenuVisibility(false);
            mCurrentPrimaryItem.setUserVisibleHint(false);
        }
        if (fragment != null) {
            fragment.setMenuVisibility(true);
            fragment.setUserVisibleHint(true);
        }
        mCurrentPrimaryItem = fragment;
    }
}

public TasksListFragment getCurrentFragment() {
    return (YourFragment) mCurrentPrimaryItem;
}
Sevastyan Savanyuk
  • 4,976
  • 2
  • 16
  • 29
2

You can define the PagerAdapter like this then you will able to get any Fragment in ViewPager.

private class PagerAdapter extends FragmentPagerAdapter {
    private final List<Fragment> mFragmentList = new ArrayList<>();

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

    @Override
    public Fragment getItem(int position) {
        return mFragmentList.get(position);
    }

    @Override
    public int getCount() {
        return mFragmentList.size();
    }

    public void addFragment(Fragment fragment) {
        mFragmentList.add(fragment);
    }
}

To get the current Fragment

Fragment currentFragment = mPagerAdapter.getItem(mViewPager.getCurrentItem());
Linh
  • 43,513
  • 18
  • 206
  • 227
2

In my previous implementation I stored a list of child Fragments to be able to access them later, but this turned out to be a wrong implementation causing huge memory leaks.

I end up using instantiateItem(...) method to get current Fragment:

val currentFragment = adapter?.instantiateItem(viewPager, viewPager.currentItem)

Or to get any other Fragment on position:

val position = 0
val myFirstFragment: MyFragment? = (adapter?.instantiateItem(viewPager, position) as? MyFragment)

From documentation:

Create the page for the given position. The adapter is responsible for adding the view to the container given here, although it only must ensure this is done by the time it returns from finishUpdate(ViewGroup).

Micer
  • 7,430
  • 3
  • 68
  • 61
1

I had the same issue and solved it using this code.

MyFragment fragment = (MyFragment) thisActivity.getFragmentManager().findFragmentById(R.id.container);

Just replace the name MyFragment with the name of your fragment and add the id of your fragment container.

Bilal Soomro
  • 621
  • 1
  • 7
  • 10
1

This is more future-proof than the accepted answer:

public class MyFragmentPagerAdapter extends FragmentPagerAdapter {

    /* ------------------------------------------------------------------------------------------ */
    // region Private attributes :

    private Context _context;
    private FragmentManager _fragmentManager;
    private Map<Integer, String> _fragmentsTags = new HashMap<>();

    // endregion
    /* ------------------------------------------------------------------------------------------ */



    /* ------------------------------------------------------------------------------------------ */
    // region Constructor :

    public MyFragmentPagerAdapter(Context context, FragmentManager fragmentManager) {

        super(fragmentManager);

        _context = context;
        _fragmentManager = fragmentManager;
    }

    // endregion
    /* ------------------------------------------------------------------------------------------ */



    /* ------------------------------------------------------------------------------------------ */
    // region FragmentPagerAdapter methods :

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

    @Override
    public Fragment getItem(int position) {

        if(_fragmentsTags.containsKey(position)) {

            return _fragmentManager.findFragmentByTag(_fragmentsTags.get(position));
        }
        else {

            switch (position) {

                case 0 : { return Fragment.instantiate(_context, Tab1Fragment.class.getName()); }
                case 1 : { return Fragment.instantiate(_context, Tab2Fragment.class.getName()); }
            }
        }

        return null;
    }

    @Override
    public Object instantiateItem(ViewGroup container, int position) {

        // Instantiate the fragment and get its tag :
        Fragment result = (Fragment) super.instantiateItem(container, position);
        _fragmentsTags.put(position, result.getTag());

        return result;
    }

    // endregion
    /* ------------------------------------------------------------------------------------------ */
}
Tim Autin
  • 5,663
  • 5
  • 40
  • 65
1

The scenario in question is better served by each Fragment adding its own menu items and directly handling onOptionsItemSelected(), as described in official documentation. It is better to avoid undocumented tricks.

Y2i
  • 3,455
  • 1
  • 26
  • 29
  • This answer could use more details for how to actually do that. How does adding a menu item solve the `ViewPager` problem? – Suragch Dec 06 '16 at 06:21
  • @Suragch ViewPager problem exists if menu events are handled within Activity that hosts fragments and does not exist if the events are handled within the fragment itself. The link shows how a fragment can add its own menu items. – Y2i Nov 03 '17 at 00:28
1

After reading all comments and answers I am going to explain an optimal solution for this problem. The best option is @rik's solution, so my improvement is base on his.

Instead of having to ask each FragmentClass like

if(FragmentClass1){
   ...
if(FragmentClass2){
   ...
}

Create your own interface, and maker your child fragments implement it, something like

public interface MyChildFragment {
    void updateView(int position);
}

Then, you can do something like this to initiate and update your inner fragments.

Fragment childFragment = (Fragment) mViewPagerDetailsAdapter.instantiateItem(mViewPager,mViewPager.getCurrentItem());

if (childFragment != null) {
   ((MyChildFragment) childFragment).updateView();
}

P.S. Be careful where you put that code, if you call insatiateItem before the system actually create it the savedInstanceState of your child fragment will be null therefor

public void onCreate(@Nullable Bundle savedInstanceState){
      super(savedInstanceState)
}

Will crash your app.

Good luck

Pedro Varela
  • 2,099
  • 1
  • 21
  • 31
1

Based on what he answered @chahat jain :

"When we use the viewPager, a good way to access the fragment instance in activity is instantiateItem(viewpager,index). //index- index of fragment of which you want instance."

If you want to do that in kotlin

val fragment =  mv_viewpager.adapter!!.instantiateItem(mv_viewpager, 0) as Fragment
                if ( fragment is YourFragmentFragment)
                 {
                    //DO somthign 
                 }

0 to the fragment instance of 0

//=========================================================================// //#############################Example of uses #################################// //=========================================================================//

Here is a complete example to get a losest vision about

here is my veiewPager in the .xml file

   ...
    <android.support.v4.view.ViewPager
                android:id="@+id/mv_viewpager"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_margin="5dp"/>
    ...

And the home activity where i insert the tab

...
import kotlinx.android.synthetic.main.movie_tab.*

class HomeActivity : AppCompatActivity() {

    lateinit var  adapter:HomeTabPagerAdapter

    override fun onCreate(savedInstanceState: Bundle?) {
       ...
    }



    override fun onCreateOptionsMenu(menu: Menu) :Boolean{
            ...

        mSearchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
          ...

            override fun onQueryTextChange(newText: String): Boolean {

                if (mv_viewpager.currentItem  ==0)
                {
                    val fragment =  mv_viewpager.adapter!!.instantiateItem(mv_viewpager, 0) as Fragment
                    if ( fragment is ListMoviesFragment)
                        fragment.onQueryTextChange(newText)
                }
                else
                {
                    val fragment =  mv_viewpager.adapter!!.instantiateItem(mv_viewpager, 1) as Fragment
                    if ( fragment is ListShowFragment)
                        fragment.onQueryTextChange(newText)
                }
                return true
            }
        })
        return super.onCreateOptionsMenu(menu)
    }
        ...

}
DINA TAKLIT
  • 4,946
  • 7
  • 42
  • 50
0

In my Activity I have:

int currentPage = 0;//start at the first tab
private SparseArray<Fragment> fragments;//list off fragments
viewPager.setOnPageChangeListener(new OnPageChangeListener() {

@Override
public void onPageSelected(int pos) {
        currentPage = pos;//update current page
}

@Override
public void onPageScrolled(int arg0, float arg1, int arg2) {}
@Override
public void onPageScrollStateChanged(int arg0) {}
});



@Override
public void onAttachFragment(Fragment fragment) {
    super.onAttachFragment(fragment);
    if(fragment instanceof Fragment1)
        fragments.put(0, fragment);
    if(fragment instanceof Fragment2)
        fragments.put(2, fragment);
    if(fragment instanceof Fragment3)
        fragments.put(3, fragment);
    if(fragment instanceof Fragment4)
        fragments.put(4, fragment);
}

Then I have the following method for getting the current fragment

public Fragment getCurrentFragment() {
    return fragments.get(currentPage);
}
J. Musyoka
  • 91
  • 1
  • 3
0

Override setPrimaryItem from your FragmentPagerAdapter: the object is the visible fragment:

 @Override
        public void setPrimaryItem(ViewGroup container, int position, Object object) {
            if (mCurrentFragment != object) {
                mCurrentFragment = (LeggiCapitoloFragment) object;
            }
            super.setPrimaryItem(container, position, object);
        }
Flavio Barisi
  • 422
  • 6
  • 9
0

Simply get the current item from pager and then ask your adapter to the fragment of that position.

 int currentItem = viewPager.getCurrentItem();
    Fragment item = mPagerAdapter.getItem(currentItem);
    if (null != item && item.isVisible()) {
       //do whatever want to do with fragment after doing type checking
        return;
    }
Pankaj kumar
  • 1,227
  • 13
  • 13
0

To get current fragment - get position in ViewPager at public void onPageSelected(final int position), and then

public PlaceholderFragment getFragmentByPosition(Integer pos){
    for(Fragment f:getChildFragmentManager().getFragments()){
        if(f.getId()==R.viewpager && f.getArguments().getInt("SECTNUM") - 1 == pos) {
            return (PlaceholderFragment) f;
        }
    }
    return null;
}

SECTNUM - position argument assigned in public static PlaceholderFragment newInstance(int sectionNumber); of Fragment

getChildFragmentManager() or getFragmentManager() - depends on how created SectionsPagerAdapter

Evgeny
  • 1
  • 1
0

You can implement a BroadcastReceiver in the Fragment and send an Intent from anywhere. The fragment's receiver can listen for the specific action and invoke the instance's method.

One caveat is making sure the View component is already instantiated and (and for some operations, such as scrolling a list, the ListView must already be rendered).

nichole
  • 111
  • 1
  • 6
0

This is the simplest hack:

fun getCurrentFragment(): Fragment? {
    return if (count == 0) null
    else instantiateItem(view_pager, view_pager.currentItem) as? Fragment
}

(kotlin code)

Just call instantiateItem(viewPager, viewPager.getCurrentItem() and cast it to Fragment. Your item would already be instantiated. To be sure you can add a check for getCount.

Works with both FragmentPagerAdapter and FragmentStatePagerAdapter!

vedant
  • 15,440
  • 4
  • 60
  • 76
0

If your pager is inside a Fragment then use this:

private fun getPagerCurrentFragment(): Fragment? {
    return childFragmentManager.findFragmentByTag("android:switcher:${R.id.myViewPagerId}:${myViewPager.currentItem}")
}

Where R.id.myViewPagerId is the id of your ViewPager inside the xml Layout.

vovahost
  • 25,072
  • 12
  • 95
  • 99
0

You can declare an Array of fragment as register fragments

class DashboardPagerAdapter(fm: FragmentManager?) : FragmentStatePagerAdapter(fm!!) {
    // CURRENT FRAGMENT
    val registeredFragments = SparseArray<Fragment>()

    override fun instantiateItem(container: ViewGroup, position: Int): Any {
        val fragment = super.instantiateItem(container, position) as Fragment
        registeredFragments.put(position, fragment)
        return fragment
    }

    override fun getItem(position: Int): Fragment {
        return when (position) {
            0 -> HomeFragment.newInstance()
            1 -> ConverterDashboardFragment.newInstance()
            2 -> CartFragment.newInstance()
            3 -> CustomerSupportFragment.newInstance()
            4 -> ProfileFragment.newInstance()
            else -> ProfileFragment.newInstance()
        }
    }

    override fun getCount(): Int {
        return 5
    }

}

Then you can use it as

adapter?.let {
    val cartFragment = it.registeredFragments[2] as CartFragment?
    cartFragment?.myCartApi(true)
}
Hamza Khan
  • 1,106
  • 7
  • 15
  • If you decide to go that way, you should also implement destroyItem() and remove the destroyed instance from registeredFragments, other wise you'll have a memory leak. – dephinera Mar 19 '20 at 14:04
  • That's good advice. You are welcome to make an edit in the answer. – Hamza Khan Mar 25 '20 at 19:05
  • https://stackoverflow.com/questions/55728719/get-current-fragment-with-viewpager2 check this out – Atif AbbAsi Mar 28 '20 at 11:33
0

I tried the following:

 int index = mViewPager.getCurrentItem();
 List<Fragment> fragments = getSupportFragmentManager().getFragments();
 View rootView = fragments.get(index).getView();
ricky roy
  • 96
  • 5
-1

Because FragmentStateAdapter will add new Fragment to FragmentManager will refer to Fragment in FragmentManager instead of creating new when screen rotates, so you just need to get in FragmentManager good for performance

public MyFragment getActiveFragment() {
        List<Fragment> fragments = getSupportFragmentManager().getFragments();
        for (Fragment fragment : fragments){
            if(fragment instanceof  MyFragment){
                return (MyFragment) fragment;
            }
        }
        return null;
    }
Van Tung
  • 39
  • 1
  • 3