How to get the instance of the currently visible fragment in ViewPager:

16,525

Solution 1

The way I've found the currently visible fragment is using setUserVisibleHint and an interface back to the ViewPager. This method is in the fragment class. Override it and use an interface to call back to your ViewPager. If the fragment is visible - i.e. it returns true - then just use a reference to your fragment (either from the list backing your adapter or one you stored as an instance variable) to get whatever you need out of the fragment itself. I've added the code below.

Declare an interface related to whatever you want to do. In my case I was using this to disable the ViewPager default x-direction listener when a Google Map instance was visible so that the scrolling of the map did not trigger the change of fragment.

public interface OnMapFragmentVisibleListener{
    public void mapVisible(boolean visible);
}

In the fragment:

@Override 
public void onAttach(Activity activity){ 
    super.onAttach(activity);
    try{
        mapFragmentVisibilityListener = (OnMapFragmentVisibleListener) activity;
        isAttached = true; //flag for whether this fragment is attached to pager
    } catch (ClassCastException e){
        throw new ClassCastException(activity.toString() + " must implement interface onAttach");
    }


@Override
public void setUserVisibleHint(boolean isVisibleToUser){
    if(isVisibleToUser && isAttached){ //if listener is called before fragment is attached will throw NPE
        mapFragmentVisibilityListener.mapVisible(true);
    }
}

One thing that was not obvious until I tried this out was the addition of the isAttached variable. I also override the fragments onAttach method and set this to true once it is called. Otherwise what happens is your fragment will be visible to the user and try to call the interface to your ViewPager before the interface is initialized.

In my ViewPager I just implement the OnMapFragmentVisibleListener and then add the required method

    @Override
public void mapVisible(boolean visible) {
    if(visible)
    viewPager.allowSwipe(false); //turn off viewPager intercept touch if map visible
}

In my case I used it to turn off the ViewPager swipe function. However, I could have easily called back to a public method in my mapFragment. I happen to have 3 fragments in my ViewPagerthat I keep a reference to.

Update

To find the fragment that is currently displayed, you first need to find out which fragment is being displayed. You can do this a number of ways. The easiest is to pass some descriptor back to your ViewPager / FragmentActivity with the interface you created before.

public interface OnFragmentVisibleListener{
    public void fragmentVisible(boolean true, String tag);
}

The problem with your setup is that you're using a Vector which is really a synchronized List to store your fragment references. As a result, there is no key value by which you can locate your fragments later. Fragments have the ability to be identified by a tag that is created during the fragment transaction when they are added to the fragment activity. However, in a ViewPager setup, you do not add the fragments individually and so can't add tags during the transaction.

My solution has been to back my BasePagerAdapter with a HashMap in which I store each fragment identified by a unique key.

 LinkedHashMap<String, Fragment> tabs = new LinkedHashMap<String, Fragment>();
 tabs.put("Frag1", fragment1);
 tabs.put("Frag2", fragment2);
 tabs.put("Frag3", fragment3);

Then I use this in the adapter:

private class BasePagerAdapter extends FragmentPagerAdapter{

    private LinkedHashMap<String, Fragment> tabs;

    public BasePagerAdapter(LinkedHashMap<String, Fragment> tabMap, FragmentManager fm)
    {
        super(fm);
        this.tabs = tabMap;
    }

     // other basic methods

Because your references are stored to key values, it's easy to find the fragment you're looking for because you can just search by key from the HashMap and return.

If you cannot change the setup to get away from the Vector you need to either (1) keep track of which fragment you add in which order (assuming fragments are not added and removed dynamically) so that you can get it by index or (2) keep separate references to each fragment.

private Fragment x;

//in onCreate
x = new Fragment();

//in interface
if(tag.equals("x"){
    x.doSomething();
}

Another thing that makes this easier is that my fragments are all sub-classed. E.g. each fragment that performs a different function has its own class. As there is only one instance of each class it is easy to find since I do not have any duplicates. If you're using all generic members of the Fragment class you just need to implement some system of tagging that makes one of the approaches above possible.

Solution 2

You can get the Current Visible Fragment using viewpager.getCurrentItem() method in the following way:

Fragment fr = getSupportFragmentManager().findFragmentByTag("android:switcher:" + R.id.pager + ":" + mViewPager.getCurrentItem());

Solution 3

A little bit late, but for those are looking for something more simple, now in 2021.

Since your view pager needs an adapter and your adapter needs a Fragment Manager just use you FragmentManager like this (Kotlin)

supportFragmentManager.fragments.first { it.isVisible && it.isAdded }

Just a reminder, to use this way your binding.viewPager.offscreenPageLimit cannot be > 0

Or you can use the currentItem from ViewPager like this;

supportFragmentManager.fragments[viewPager.currentItem]
Share:
16,525

Related videos on Youtube

Emil Adz
Author by

Emil Adz

Education: B.S. Computer Science, 2012, HIT. Occupation: Mobile Architect &amp; Co-Founder at Digital Cherry Ever seen a dog chase its tail? Now that's an infinite loop ; ) #SOreadytohelp

Updated on September 15, 2022

Comments

  • Emil Adz
    Emil Adz over 1 year

    I'm building an application that shows different fragments in a ViewPager. I add these fragments to the ViewPager like this:

     public void intialiseViewPager() 
     {
        List<Fragment> fragments = new Vector<Fragment>();
        numberOfTabs = application.currentReport.getODTabsList().size();
        for (int i = 0; i < numberOfTabs; i++)      
        {
            ODTab tempTab = application.currentReport.getODTabsList().get(i);
            if (tempTab.getTabType().equals(ODGrid.XML_GRID_ELEMENT))
            {
                GridFragment gridFragment = GridFragment.newInstance(tempTab.getTabId());
                fragments.add(gridFragment);
            }
            else  if (tempTab.getTabType().equals(ODChart.XML_CHART_ELEMENT))
            {
                NewChartFragment chartFragment = NewChartFragment.newInstance(tempTab.getTabId());
                fragments.add(chartFragment);
            }
        }
        Log.d(TAG, "Current report fragments set to adapter: "+fragments.toString());
        mPagerAdapter  = new ViewPagerAdapter(getSupportFragmentManager(), fragments);
        mViewPager = (ViewPager)findViewById(R.id.pager);
        mViewPager.setAdapter(mPagerAdapter);
        mViewPager.setOffscreenPageLimit(0);
        mViewPager.setOnPageChangeListener(this);
    }
    

    As you can see I pass to the fragment a String (tempTab.getTabId()), in this line:

    GridFragment gridFragment = GridFragment.newInstance(tempTab.getTabId());
    

    In the fragment itself I do this to initialize it:

    public static final GridFragment newInstance(String tabId)
    {
        GridFragment f = new GridFragment();
        Bundle bdl = new Bundle(2);
        bdl.putString(TAB_ID, tabId);
        f.setArguments(bdl);
        return f;
    }
    
    @Override
    public void onCreate(Bundle savedInstanceState) 
    {
        String tabId = getArguments().getString(TAB_ID);
        if (application.currentReport != null)
        {
            this.odTab = application.currentReport.getODTabByTabId(tabId);
        }
        else
        {
            startActivity(new Intent(getActivity(), LoginScrActivity.class));
        }
        super.onCreate(savedInstanceState);
    }
    

    All this is performed in order to not override the default empty constructor of the fragment, as I understand that this is not recommended.

    Now I need to get the instance of the odTab object that I put into the currently visible fragment in this line:

    this.odTab = application.currentReport.getODTabByTabId(tabId);
    

    Could someone please explain to me how this could be done? How do I get the currently visible fragment instance so I can pull the odTab object out of it?

    UPDATE: With the suggestions I received here I have added an odTab object instance to the application class that is called currentVisibleTab and I'm setting the instance of the odTab like this:

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) 
    {
        Log.d(TAG, "setUserVisibleHint invoked!");
        super.setUserVisibleHint(isVisibleToUser);
        if (isVisibleToUser) 
        {
            if (odTab != null && getActivity() != null)
            {
                Log.d(TAG, "Currently visable tab: "+odTab.getTabTitle());
                application.currentVisibleTab = odTab;
            }
        }   
    }
    

    UPDATE2: This is my ViewPagerAdapter:

    public class ViewPagerAdapter extends FragmentPagerAdapter 
    {
    private List<Fragment> fragments;
    
    /**
     * @param fm
     * @param fragments
     */
    public ViewPagerAdapter(FragmentManager fm, List<Fragment> fragments) {
        super(fm);
        this.fragments = fragments;
    }
    
    /* (non-Javadoc)
     * @see android.support.v4.app.FragmentPagerAdapter#getItem(int)
     */
    
    @Override
    public Fragment getItem(int position) {
        return this.fragments.get(position);
    }
    
    /* (non-Javadoc)
     * @see android.support.v4.view.PagerAdapter#getCount()
     */
    
    @Override
    public int getCount() {
        return this.fragments.size();
    }
    
    @Override
    public int getItemPosition(Object object) {
        return POSITION_NONE;
    } 
    
    public void removeAllFragments()
    {
        this.fragments.clear();
    }
    
    public void addFragmentsListToAdapter(List<Fragment> fragments)
    {
        this.fragments.addAll(fragments);
    }
    
    public List<Fragment> getFragments()
    {
        return fragments;
    }
    }
    

    In it I have a List of fragments that are shown by the ViewPager. This list is initialized like this:

     List<Fragment> fragments = new Vector<Fragment>();
    

    What I don't understand is how I get a reference to the current fragment that triggered the interface method from this list. And this is not a related question but maybe you know the answer: What is the difference between List and Vector?

    I still check this option out.

  • Emil Adz
    Emil Adz almost 11 years
    Could please provide some of you code snippet of how you did it on the fragment side and on the viewPager side classes?
  • Rarw
    Rarw almost 11 years
    just added - sorry I didn't post before I was not at my desk when I answered.
  • Emil Adz
    Emil Adz almost 11 years
    Thanks, man... great answer in great details, I will test and accept your answer if this will solve my problem, in the meanwhile +1 for your effort.
  • Emil Adz
    Emil Adz almost 11 years
    where should the interface be declared?
  • Rarw
    Rarw almost 11 years
    In the fragment itself or in its own file. You then implement it with the implements key word in your class declartation for your fragment activity so public class MyViewPager extends FragmentActivity implements OnFragmentVisibleListener Sorry if that was not clear.
  • Emil Adz
    Emil Adz almost 11 years
    Ok, I have done all that, I created the interface in my GridFragment, that is called "OnFragmetVisibleListener" a let my ViewPager activity to implement this interface, added the unimplemented methods, so this basically lets me know that the fragment is currently visible, but I didn't understand how do I now get the visible fragment instance?
  • Rarw
    Rarw almost 11 years
    Sure - once you know the fragment is visible you need to access a reference to that fragment which is stored in your FragmentActivity. You can do this a number of ways. I store a reference as an instance variable when I only have a few fragments to keep track of. If theres more, just look in your adapter. You have to have a collection containing the fragments in your ViewPager somewhere. Once you get the callback saying fragment x is visible just get that reference from the collection.
  • Rarw
    Rarw almost 11 years
    Just saw this update now - you accepted does that mean you figured it out? (btw I have no idea the difference between vector and list)
  • Emil Adz
    Emil Adz almost 11 years
    no I still looking for a way to connect between the visible fragment and it's instance in the vector list inside my ViewPagerAdapter. But you helped me a lot so I figured to check this answer as helpful.
  • Rarw
    Rarw almost 11 years
    Updated again see if this helps you out
  • Emil Adz
    Emil Adz almost 11 years
    Thank you very much for this explanation, I guess you are right, I should try to move my ViewPagerAdapter to a key/value based storage to get it's instance in a quick manner. I will check if this is possible. In my case the fragment are populated using a data that comes from server side, I don't know what size it's going to be, but I think I will find a way to figure it out. Thanks again.
  • Rarw
    Rarw almost 11 years
    My fragments are all populated from data downloaded from the server. You can do it! Good luck.
  • Raghunandan
    Raghunandan almost 11 years
    @Rarw i like the interface idea the same idea used to pass values back to activity from fragment.