9

I am trying to have the same view pager + tabs design as the PlayStore 5.1.x. Here is my layout :

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_gravity="center_vertical|center_horizontal"
    android:gravity="center_vertical|center_horizontal"
    android:orientation="vertical">

    <com.astuetz.PagerSlidingTabStrip
        android:id="@+id/tabs"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:background="@drawable/background_tabs" />

    <android.support.v4.view.ViewPager
        android:id="@+id/pager"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

My Adapter:

public class MainPagerAdapter extends FragmentStatePagerAdapter {

    private ArrayList<FakeFragment> fragments;

    public MainPagerAdapter(FragmentManager fm) {
        super(fm);
        // TODO Auto-generated constructor stub
        fragments = new ArrayList<FakeFragment>();
    }

    @Override
    public Fragment getItem(int position) {
        // TODO Auto-generated method stub      
        if(position < getCount()) {
            FakeFragment fragment = FakeFragment.newInstance(position);
            fragments.add(fragment);
        }
        return fragments.get(position);
    }

    @Override
    public int getCount() {
        // TODO Auto-generated method stub
        return Category.values().length;
    }

    @Override
    public CharSequence getPageTitle(int position) {
        // TODO Auto-generated method stub
        return Category.values()[position].getTitle();
    }

    @Override
    public int getItemPosition(Object object) {
        // TODO Auto-generated method stub
        return POSITION_NONE;
    }
}

My tabs and pager are showing correctly ! but i have noticed that the first fragment shown in the view pager is always the same as the second one. Then when I swipe once, twice and swipe back to the first page, I find that the correct fragment is now shown !!

I can't understand why this behaviour, please I need some explanations.

SOLUTION

The issue was due to my FakeFragment.newInstance() method definition.

private static int position;

public static FakeFragment newInstance(int position) {
    // TODO Auto-generated method stub
    FakeFragment.position = position;
    return new FakeFragment();
}

I changed it by using a setArguments(args) to my FakeFragment instance, and then retrieve it in onCreate method. Now all is working nice !

Can somebody explain me why ??

I think that, in this way, value of position will entierly depend on fragment's lifecycle, so will be always the expected position, Right ??

S.Thiongane
  • 6,883
  • 3
  • 37
  • 52
  • 1
    Get rid of `private ArrayList fragments` and just having `getItem()` return a new instance of the fragment. The **complete and entire *point*** behind `FragmentStatePagerAdapter` is to **NOT** hold onto all of the fragments in memory. If that's what you want, then still get rid of the `ArrayList` and switch your adapter to be a `FragmentPagerAdapter`, rather than a `FragmentStatePagerAdapter`. Also, get rid of `getItemPosition()`. [FWIW, here are a series of sample `ViewPager`-using apps](https://github.com/commonsguy/cw-omnibus/tree/master/ViewPager). – CommonsWare Dec 31 '14 at 23:23
  • Thanks @CommonsWare. I chose the `FragmentStatePagerAdapter` because I have 6 pages in my `ViewPager`, on each page I have a `GridView` with many data to show. Thats why I wanted to use this kind of adapter, to recreate a page or refresh it's content only if needed. That's why also I tried to override `getItemPosition`. Is this possible with `FragmentPagerAdapter` ? thanks again for replying ! – S.Thiongane Jan 01 '15 at 15:58
  • 1
    Your implementation should work correctly in both cases, the only thing different may be the speed. If loading data is expensive and you don't need to do it everytime use `FragmentStatePagerAdapter`, use `Fragment.onSaveInstanceState` to store the loaded data set. – Eugen Pechanec Jan 01 '15 at 16:32
  • thanks @EugenPechanec, I will try to do what you suggested. – S.Thiongane Jan 01 '15 at 16:36
  • I solved the issue, but still don't understand it well. Please see my edit and help me understand this ! thanks in advance. – S.Thiongane Jan 07 '15 at 23:28
  • @CommonsWare, Eugen ... need your help one last time :) – S.Thiongane Jan 08 '15 at 08:58
  • I am having the same problem even today with very similar code. An array has [a, b , c ,d ] ..that should be the order of the pages. But my pages show up first time as [b, b, c, d]. On swiping backwards , c, b, a OR d, c, b ,a. Logs show that the 1st adapter is getting Value a and position 0 but 'a' never gets displayed. Still wondering what could be wrong. – lini sax Jun 21 '15 at 18:23
  • Please see my anaylsis and the way I figured out the problem stackoverflow.com/a/30968767/816635 – lini sax Jun 21 '15 at 20:21

2 Answers2

6

1) Do not implement getItemPosition(Object) if you don't handle it. You're not required to implement it and you might break some other functionality by implementing it wrong.

2) The point of getItem(int) is to return a new fragment. Ditch the fragment array as it makes no sense.

3) Make the adpater class static (It promotes reusability, the adapter should not depend on the parent class to get its data set, right?) and pass the Categorys as a constructor parameter. Store it in a variable and make new fragments according to this data set. You'll also probably want to pass a Category[position] as a parameter to the fragment constructor instead of just position.

Eugen Pechanec
  • 37,669
  • 7
  • 103
  • 124
  • Thanks @Eugen, please can your give more information about the 3) : `the adapter should not depend on the parent class to get its data set` ? – S.Thiongane Jan 07 '15 at 08:07
  • 1
    It's best practice to declare nested classes `static`. This removes their implicit connection to their parent class (notice that you can't access the parent's field variables). In some cases this might prevent memory leaks. Only use non-static inner classes if you know what you're doing. Try reading [this](http://stackoverflow.com/a/70358/2444099). – Eugen Pechanec Jan 07 '15 at 16:30
  • I have accepted your answer and given you the bounties because your answer and comment led me to other usefull information. – S.Thiongane Jan 07 '15 at 23:23
  • I'm glad I could help. Good luck with your app. – Eugen Pechanec Jan 08 '15 at 16:33
  • please see the post http://stackoverflow.com/questions/33012056/view-pager-first-and-second-fragment-data-is-not-updating-when-data-changed-in-a – kartheeki j Oct 08 '15 at 09:45
3

The getItem() implementation is the problem.

@Override
public Fragment getItem(final int position) {
    return FakeFragment.newInstance(position);
}

You should never alter the data in this get method: do not call add() in it. I doubt the Adapter would know at this point that you added an element.

shkschneider
  • 17,833
  • 13
  • 59
  • 112