20

I want to get the last fragment in the backstack, or the current displayed it's the same for me, in the tab b_1. As you can see in the following image, I have a ViewPager, and another one inner tab b. Thus there are four current fragments displayed.

Question: How can I get the Fragment 2 instance?

I have seen another solutions, but none works for this scenario.

Annotation: The fragment to return is not necessary the hosted in the ViewPager. I can have opened two more fragments in a tab.

scenario

With this method I get all the current visible fragments, but not the one specific I want.

public ArrayList<Fragment> getVisibleFragment() {
    List<Fragment> fragments = getSupportFragmentManager().getFragments();
    ArrayList<Fragment> visibleFragments = new ArrayList<>();
    if (fragments != null) {
        for (Fragment fragment : fragments) {
            if (fragment != null && fragment.isVisible())
                visibleFragments.add(fragment);
        }
    }
    return visibleFragments;
}

Some interesting code

activity_main.xml

<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">

        <android.support.design.widget.TabLayout
            android:id="@+id/tabs"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:tabMode="fixed"
            app:tabGravity="fill"/>
    </android.support.design.widget.AppBarLayout>

    <android.support.v4.view.ViewPager
        android:id="@+id/viewpager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"/>

</android.support.design.widget.CoordinatorLayout>

MainActivity.java

public class MainActivity extends AppCompatActivity {

    private static ViewPagerAdapter adapter;
    private static ViewPager viewPager;
    private TabLayout tabLayout;

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        viewPager = (ViewPager) findViewById(R.id.viewpager);
        setupViewPager();

        tabLayout = (TabLayout) findViewById(R.id.tabs);
        tabLayout.setupWithViewPager(viewPager);
        setupTabIcons();
    }

    private void setupViewPager() {
        adapter = new ViewPagerAdapter(getSupportFragmentManager());
        // Wrap with HostFragment to get separate tabbed nagivation.
        adapter.addFrag(HostFragment.newInstance(new Fragment1()), null);
        adapter.addFrag(HostFragment.newInstance(new RootFragment2()), null);
        adapter.addFrag(HostFragment.newInstance(new Fragment4()), null);
        viewPager.setAdapter(adapter);
        viewPager.setOffscreenPageLimit(2);
    }

    public void openNewFragment(Fragment fragment) {
        HostFragment hostFragment = (HostFragment) adapter.getItem(viewPager.getCurrentItem());
        hostFragment.replaceFragment(fragment, true);
    }
}

fragment_host.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/hosted_fragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>

HostFragment.java

/**
 * This class implements separate navigation for a tabbed viewpager.
 *
 * Based on https://medium.com/@nilan/separate-back-navigation-for-
 * a-tabbed-view-pager-in-android-459859f607e4#.u96of4m4x
 */
public class HostFragment extends BackStackFragment {

    private Fragment fragment;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        super.onCreateView(inflater, container, savedInstanceState);
        View view = inflater.inflate(R.layout.fragment_host, container, false);
        if (fragment != null) {
            replaceFragment(fragment, false);
        }
        return view;
    }

    public void replaceFragment(Fragment fragment, boolean addToBackstack) {
        if (addToBackstack) {
            getChildFragmentManager().beginTransaction().replace(R.id.hosted_fragment, fragment).addToBackStack(null).commit();
        } else {
            getChildFragmentManager().beginTransaction().replace(R.id.hosted_fragment, fragment).commit();
        }
    }

    public static HostFragment newInstance(Fragment fragment) {
        HostFragment hostFragment = new HostFragment();
        hostFragment.fragment = fragment;
        return hostFragment;
    }

    public Fragment getFragment() {
        return fragment;
    }
}

fragment2_root.xml

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/fragment2_root"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <android.support.design.widget.TabLayout
        android:id="@+id/tab2_tabs"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:tabMode="fixed"
        app:tabGravity="fill"/>

    <android.support.v4.view.ViewPager
        android:id="@+id/tab2_viewpager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"/>

</LinearLayout>

RootFragment2.java

public class RootFragment2 extends Fragment {

    private ViewPagerAdapter adapter;
    private ViewPager viewPager;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // Inflate the layout for this fragment.
        View root = inflater.inflate(R.layout.fragment2_root, container, false);

        viewPager = (ViewPager) root.findViewById(R.id.tab2_viewpager);
        setupViewPager(viewPager);

        TabLayout tabLayout = (TabLayout) root.findViewById(R.id.tab2_tabs);
        tabLayout.setupWithViewPager(viewPager);

        return root;
    }

    private void setupViewPager(ViewPager viewPager) {
        adapter = new ViewPagerAdapter(getActivity().getSupportFragmentManager());
        // Wrap with HostFragment to get separate tabbed nagivation.
        adapter.addFrag(HostFragment.newInstance(new Fragment2()), null);
        adapter.addFrag(HostFragment.newInstance(new Fragment3()), null);
        viewPager.setAdapter(adapter);
        viewPager.setOffscreenPageLimit(1);
    }

    public ViewPagerAdapter getAdapter() {
        return adapter;
    }

    public ViewPager getViewPager() {
        return viewPager;
    }
}
Santiago Gil
  • 1,292
  • 7
  • 21
  • 52

4 Answers4

46

First define a SparseArray in your ViewPagers' adapters like below. In this array we'll hold the instance of fragments.

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

And Override your Adapters' instantiateItem method.

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

Also Override destroyItem method of your ViewPagers

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

And define a new method to get your ViewPager Fragments instance.

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

And finally set add a PageChangeListener to your ViewPagers:

viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {

    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

    }

    @Override
    public void onPageSelected(int position) {
        // Here's your instance
        YourFragment fragment =(YourFragment)yourPagerAdapter.getRegisteredFragment(position);

    }

    @Override
    public void onPageScrollStateChanged(int state) {

    }
});

I hope this'll help you. Good luck.

Edit: I'm sorry i cannot understand exactly what you're planning to do but if you need to keep sub fragment (b_1, b_2) instance you can define a method to your activity such as

public void setCurrentFragment(Fragment fragment){
      this.currentFragment = fragment;
}

and in your sub view pager's adapter you can call this method like below:

subViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {

    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

    }

    @Override
    public void onPageSelected(int position) {
        // Here's your instance
        YourFragment fragment =(YourFragment)yourSubPagerAdapter.getRegisteredFragment(position);
        ((MyActivity)getActivity).setCurrentFragment(fragment);
    }

    @Override
    public void onPageScrollStateChanged(int state) {

    }
});

With this way you can keep one instance and your top fragment.

savepopulation
  • 11,736
  • 4
  • 55
  • 80
  • **1**. Can you explain what am I doing (and which effects has) if I override `instantiateItem` and `destroyItem`? **2**. Also, should I save `YourFragment fragment` from `onPageSelected` into a global attribute `currentFragment` to have access to it whenever I want? **3**. And, as I have an inner Viewpager, would I have two `currentFragment`s? I mean, one `RootFragment2` in `MainActivity` and one `Fragment2` in `RootFragment2`. – Santiago Gil Sep 01 '16 at 15:15
  • 1- You'll just keep instances of your fragments which you'll use in your view pagers. When you create a new Fragment in your ViewPager adapter you add it's reference to a sparse array and get it when you need. 2- Yes you can do that. 3- Yes you can use 2 current fragments or you can keep a single instance this depends on you. – savepopulation Sep 01 '16 at 15:26
  • Okey, I have implemented it, but doesn't work as desired. I save just a `fragment`; this means, the `onPageSelected` of the main and inner VP save the `fragment` into a global attribute. When `Fragment2` is being displayed, `currentFragment` is `RootFragment2`. – Santiago Gil Sep 01 '16 at 15:45
  • Ok, it was because last page selected was to go to `tab_b`. However, it is being displayed `tab_b1` although it has not been selected (by default, there is always an open page in a viewpager). – Santiago Gil Sep 01 '16 at 15:53
  • @SantiGil how is it going? does my solution works for you? – savepopulation Sep 02 '16 at 06:39
  • Your solution works, but only for a main VP. When you have inner VPs, it fails, because when `tab_b` is selected, `currentFragment` is `RootFragment2` (this doesn't keep in mind the inner ViewPager). This happens although I init manual the first time with the correct value. I solved this in another way, let me post the answer so you can see it. – Santiago Gil Sep 02 '16 at 08:10
  • Yes, that is what I did. The two VPs, main and inner, set this value when page selected. However, when `tab_b` is selected, `tab_b1` and `tab_b2` haven't been selected but one of them is being displayed. Thus `currentFragment` is the root which holds the inner viewpager, because last page selected was `tab_b`. – Santiago Gil Sep 02 '16 at 08:22
  • i think there's something wrong with your implementation. – savepopulation Sep 02 '16 at 08:28
  • What about holding fragment references? maybe you should use WeakReferences array – Lester Oct 23 '16 at 17:29
  • we remove fragment reference from sparse array onDestroyItem method. so i think no need to use wekreferences array. – savepopulation Apr 09 '18 at 19:41
  • Is there any possibility of memory leaks here? How many Fragment references will be held in SparseArray at one time? – Rishab Jaiswal Nov 30 '18 at 06:18
  • there're no possible memory leaks here because when we destroy item we remove it from sparse array. fragment count will be held in SparseArray at one time depends on your viewpager's offScreenPageLimit. – savepopulation Nov 30 '18 at 10:20
3

After some comments with DEADMC, I decided to implement it with extra memory, saving in a list the open fragments.

I explain my solution. I hve two types of special fragments, host and root. Host Fragments are those which are the init of a tab. Root Fragments are those which hold a ViewPager. In this scenario, tab_b has a Root Fragment with an inner ViewPager.

Hierarchy

For each tab, this is, for each HostFragment, I have added a list fragments to save the fragments opened in this tab. Also a method getCurrentFragment to get the last displayed in this tab.

public class HostFragment extends BackStackFragment {

    private ArrayList<Fragment> fragments = new ArrayList<>();

    public Fragment getCurrentFragment() {
        return fragments.get(fragments.size() - 1);
    }

Also a method to remove the last fragment displayed in this tab is needed, to maintain a true state. Back button needs to be redirect to this method.

    public void removeCurrentFragment() {
        getChildFragmentManager().popBackStack();
        fragments.remove(fragments.size() - 1);
    }

Also previous methods need to be changed, to insert the new fragments opened into the list.

    public void replaceFragment(Fragment fragment, boolean addToBackstack) {
        // NEW: Add new fragment to the list.
        fragments.add(fragment);
        if (addToBackstack) {
            getChildFragmentManager().beginTransaction().replace(R.id.hosted_fragment, fragment).addToBackStack(null).commit();
        } else {
            getChildFragmentManager().beginTransaction().replace(R.id.hosted_fragment, fragment).commit();
        }
    }

    public static HostFragment newInstance(Fragment fragment) {
        HostFragment hostFragment = new HostFragment();
        // NEW: Add first fragment to the list.
        hostFragment.fragments.add(fragment);
        return hostFragment;
    }
} // HostFragment.

RootFragment is an interface implemented by those fragments which holds a ViewPager.

public interface RootFragment {

    /**
     * Opens a new Fragment in the current page of the ViewPager held by this Fragment.
     *
     * @param fragment - new Fragment to be opened.
     */
    void openNewFragment(Fragment fragment);

    /**
     * Returns the fragment displayed in the current tab of the ViewPager held by this Fragment.
     */
    Fragment getCurrentFragment();
}

And then, the implementation would be:

MainActivity is not a Fragment, but I implement RootFragment interface for meaning.

public class MainActivity extends AppCompatActivity implements RootFragment {

    private void setupViewPager() {
        adapter = new ViewPagerAdapter(getSupportFragmentManager());
        // Wrap with HostFragment to get separate tabbed nagivation.
        adapter.addFrag(HostFragment.newInstance(new Fragment1()), null);
        adapter.addFrag(new RootFragment2(), null); // This is a Root, not a Host.
        adapter.addFrag(HostFragment.newInstance(new Fragment4()), null);
        viewPager.setAdapter(adapter);
        // 2 because TabLayout has 3 tabs.
        viewPager.setOffscreenPageLimit(2);
    }

    @Override
    public void openNewFragment(Fragment fragment) {
        Fragment hosted = adapter.getItem(viewPager.getCurrentItem());
        // Replace the fragment of current tab to [fragment].
        if (hosted instanceof HostFragment) {
            ((HostFragment) hosted).replaceFragment(fragment, true);
        // Spread action to next ViewPager.
        } else {
            ((RootFragment) hosted).openNewFragment(fragment);
        }
    }

    @Override
    public Fragment getCurrentFragment() {
        Fragment hosted = adapter.getItem(viewPager.getCurrentItem());
        // Return current tab's fragment.
        if (hosted instanceof HostFragment) {
            return ((HostFragment) hosted).getCurrentFragment();
        // Spread action to next ViewPager.
        } else {
            return ((RootFragment) hosted).getCurrentFragment();
        }
    }
}

and RootFragment2

public class RootFragment2 extends Fragment implements RootFragment {

    private void setupViewPager(ViewPager viewPager) {
        adapter = new ViewPagerAdapter(getActivity().getSupportFragmentManager());
        // Wrap with HostFragment to get separate tabbed nagivation.
        adapter.addFrag(HostFragment.newInstance(new Fragment2()), null);
        adapter.addFrag(HostFragment.newInstance(new Fragment3()), null);
        viewPager.setAdapter(adapter);
        // 1 because TabLayout has 2 tabs.
        viewPager.setOffscreenPageLimit(1);
    }

    @Override
    public void openNewFragment(Fragment fragment) {
        ((HostFragment) adapter.getItem(viewPager.getCurrentItem())).replaceFragment(fragment, true);
    }

    @Override
    public Fragment getCurrentFragment() {
        return ((HostFragment) adapter.getItem(viewPager.getCurrentItem())).getCurrentFragment();
    }
}

And then, I just need to call:

((MainActivity) getActivity()).getCurrentFragment();

Santiago Gil
  • 1,292
  • 7
  • 21
  • 52
0

You can create something like CustomFragment class which contains getClassName method and extends from Fragment class. Then extend all your fragments such as RootFragment2 from CustomFragment class instead of just Fragment.

So you can get fragment like this:

public CustomFragment getNeededFragment(String fragmentName) {
    List<Fragment> fragments = getSupportFragmentManager().getFragments();
    CustomFragment result = null;
    if (fragments != null) {
        for (Fragment fragment : fragments) {
            if (fragment != null && fragment.isVisible()) {
                try {
                    CustomFragment customFragment = (CustomFragment) customFragment;
                    if (customFragment.getClassName().equals(fragmentName)))
                    result = customFragment;
                } catch (ClassCastException e) {
                }
            }
        }
    }
    return result ;
}
Andrey Danilov
  • 6,194
  • 5
  • 32
  • 56
  • I had in mind this solution, but as I have commented `The fragment to return is not necessary the hosted in the ViewPager. I can have opened two more fragments in a tab.` I mean, I don't know the `fragmentName` to search for it, because there can be 5-6 fragments in the stack, and I don't know which one is the current displayed. – Santiago Gil Sep 01 '16 at 11:15
  • I have also a similar solution, but I think there has to be a better one because I don't consider it smart. The solution is: each fragment that can be used in a tab, implements a interface `Tab_x_Fragment` where `x` is the number of the tab where it can be displayed. Thus, I just have to get the fragment which is `instanceof Tab_b1_Fragment` from all the current displayed fragments. – Santiago Gil Sep 01 '16 at 11:19
  • Almost, you can keep it in another lists in MainActivity and pass it by special method. So you have current state of every tab already in main Activity, For example keep them in hashMap with key as tabName – Andrey Danilov Sep 01 '16 at 11:22
  • Yes, I also though about that. But I need then special memory, and I'm sure I can get this done using Android features. – Santiago Gil Sep 01 '16 at 11:29
  • You spend special memory only for pointer for your fragments. It requires only 4 bytes for every fragment. I dont think it will really slow down your app. – Andrey Danilov Sep 01 '16 at 11:36
  • Haha I know! But I mean, it is not only special memory, but also duplicated. I have all that information in the backstack. – Santiago Gil Sep 01 '16 at 12:04
0

I would do something like that:

1. Create a interface like this.

public interface ReturnMyself {
    Fragment returnMyself();
}

2. All fragments inside ViewPagers should implements this one.

3. Add OnPageChangeListener to your main VP. So You will always know current position.

4. Add OnPageChangeListener your inner(s) VP so you will know which one is there on screen.

5. Add method to your adapter (both main and inner) which returns your fragment from list passed to it.

public ReturnMyself getReturnMyselfAtPosition()

6. All Fragments should return this in returnMyself()

7. Fragment that has inner fragments should return in returnMyself something like.

Fragment returnMyself() {
    return this.myInnerFragmentAdapter.getReturnMyselfAtPosition().returnMyself(); 
}

8. From main fragment/activity you just call.

this.adapter.getReturnMyselfAtPosition().returnMyself();

And you got your current fragment.

Santiago Gil
  • 1,292
  • 7
  • 21
  • 52
wojciech_maciejewski
  • 1,277
  • 1
  • 12
  • 28
  • What does exactly `getReturnMyselfAtPosition()` do? Do you refer to the `getItem(int position)` method of `FragmentStatePagerAdapter`? – Santiago Gil Sep 01 '16 at 13:21
  • It could be. But getRetunMyselfAtPosition should return ReturnMyself instead of fragment, so you don't need to cast it :-) – wojciech_maciejewski Sep 01 '16 at 13:39
  • But this solution returns me the Fragment hosted in the ViewPager tab. As I said, `The fragment to return is not necessary the hosted in the ViewPager. I can have opened two more fragments in a tab.` – Santiago Gil Sep 01 '16 at 13:57
  • thats why, as i wrote in point 7, the fragment that has inner fragmnets, should not return himself, but one of his child fragment. – wojciech_maciejewski Sep 01 '16 at 16:01
  • Yes, but with that you only manage to return the hosted fragment in the inner ViewPager, instead of the `RootFragment2`. For example if `Fragment2` opens a `Fragment5` inside `tab_b1`, this would return `Fragment2` as current displayed because is the hosted in the inner ViewPager. – Santiago Gil Sep 01 '16 at 16:04
  • No it won't. Sorry that you don't understand that – wojciech_maciejewski Sep 02 '16 at 11:41
  • It returned me the `HostFragment` which hosts the tab (see my answer), and not the fragment in the `hosted_fragment` container of this `HostFragment`. – Santiago Gil Sep 02 '16 at 11:50