0

I made this class:

public class My_ViewPagerAdapter extends FragmentStatePagerAdapter {

    private ArrayList<My_Fragment> fragments=new ArrayList<>();
    private ArrayList<String> tabTitles=new ArrayList<>();

    public My_ViewPagerAdapter(FragmentManager fm) {

        super(fm);
    }

    @Override
    public Fragment getItem(int position) {

        return fragments.get(position);
    }

    @Override
    public int getItemPosition(Object object) {

        for (int i=0;i<fragments.size();i++){

            Class<? extends My_Fragment> clazz= fragments.get(i).getClass();

            if (clazz.isInstance(object)){

                return i;
            }
        }
        return POSITION_NONE;
    }

    @Override
    public int getCount() {

        return fragments.size();
    }

    public void addFragment(My_Fragment fragment, String tabTitle){

        fragments.add(fragment);
        tabTitles.add(tabTitle);
        notifyDataSetChanged();
    }

    public void removeFragment(int position){

        fragments.remove(position);
        tabTitles.remove(position);
        notifyDataSetChanged();
    }

    public My_Fragment getFragmentAtTab(int position){

        return fragments.get(position);
    }

    @Override
    public CharSequence getPageTitle(int position) {

        return tabTitles.get(position);
    }
}

I'm adding/removing fragments/tabs dynamically, and i want to save their state through configuration changes.

I implemented onSaveInstanceState():

int tabs=tabLayout.getTabCount();

    for (int i=0;i<tabs;i++){

        outState.putInt("TabsCount",tabs);
        outState.putString("Tab_"+i,getTabTitle(i));
        outState.putString("FragmentClassAtTab_"+i,my_viewPagerAdapter.getFragmentAtTab(i).getClass().getName());
    }

and i implemented onRestoreInstanceState():

for (int i=0;i<tabs;i++){

        try {

            My_Fragment fragment= (My_Fragment) Class.forName(savedInstanceState.getString("FragmentClassAtTab_"+i)).newInstance();
            addTab(fragment ,savedInstanceState.getString("Tab_"+i));
        }
        catch (ClassNotFoundException e) {

            e.printStackTrace();
        }
        catch (InstantiationException e) {

            e.printStackTrace();
        }
        catch (IllegalAccessException e) {

            e.printStackTrace();
        }
    }

addTab method is:

public void addTab(My_Fragment fragment, String title){

    if (fragment!=null) my_viewPagerAdapter.addFragment(fragment,title);
    tabLayout.addTab(tabLayout.newTab().setText(title));
}

THE PROBLEM:

Every fragment i add gets recreated "automatically" when configuration change occurs, so its constructor gets called everytime. onCreateView gets called on these instances and not on the ones recreated by my code.

I need onCreateView to get called on fragments instantiated in my tabs in order to set their listviews.

Fragments created "automatically" get replaced by new fragment instances through my onRestoreInstanceState method, but no onCreateView method gets called on these new instances.

I think i'm doing it wrong and maybe there is a better way to save tabs and fragments throught configuration changes... Any help is appreciated.

Lazarus
  • 147
  • 13
  • Just to understand fully, are the FragmentStatePagerAdapter held by an Activity or Fragment? Are the number of fragments correct after configuration change? And do they have the expected content? – cYrixmorten Oct 05 '16 at 18:55
  • Yes, the adapter gets held and recreated in onCreate() method of my activity. if by saying "number of fragments" you mean fragments.size(), yes it is, i used to remove the fragments by calling "removeFragment" on my adapter but since it gets recreated after configuration change (always in onCreate method) i don't need to remove them manually. – Lazarus Oct 05 '16 at 20:15
  • Think my further input is limited (been away from Android for a while). However, have you looked at this SO post: http://stackoverflow.com/questions/15313598/once-for-all-how-to-correctly-save-instance-state-of-fragments-in-back-stack? Just thought it looks relevant – cYrixmorten Oct 06 '16 at 07:27
  • Thanks for your help, however, i found a tricky solution (check my own answer) which helps me retrieving the fragment on which the view is (re)created.. – Lazarus Oct 06 '16 at 07:34
  • Alright no problem, glad you got it working :) happy coding – cYrixmorten Oct 06 '16 at 07:42

2 Answers2

0

I once created something similar, which in my case looked like this:

public class DisplayPagerAdapter extends FragmentStatePagerAdapter  {

    private static final String TAG = "DisplayPagerAdapter";

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

    private final Context context;
    private final DisplayCoreModule display;
    private final FragmentManager fm;

    private boolean isAddOrRemoving;

    public DisplayPagerAdapter(Context context, FragmentManager fm,
            DisplayCoreModule display) {
        super(fm);
        this.context = context;
        this.display = display;
        this.fm = fm;

        Log.d(TAG, "pages " + display.getPagesCount());
    }


    public void notifySizeChangingDataSetChange() {
        isAddOrRemoving = true;
        notifyDataSetChanged();
        isAddOrRemoving = false;
    }

    @Override
    public int getCount() {
        int count = (display != null && display.getPagesCount() > 0) ? display
                .getPagesCount() : 1;
        return count;
    }

    @Override
    public int getItemPosition(Object object) {
        DisplayFragment frag = (DisplayFragment) object;
        if (!display.containsPageId(frag.getPageId())) {
            // this will update the 'no information' page with id -1
            return POSITION_NONE;
        }
        if (isAddOrRemoving) {
            // recreate all for simplicity
            return POSITION_NONE;
        }
        return POSITION_UNCHANGED;
    }

    @Override
    public Fragment getItem(int position) {
        Log.d(TAG, "getItem " + position);
        return DisplayFragment.newInstance(position);
    }

    @Override
    public CharSequence getPageTitle(int position) {
        if (display != null && display.getPagesCount() > 0) {
            return context.getString(R.string.page) + " " + (position + 1);
        } else {
            return super.getPageTitle(position);
        }
    }

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        Log.d(TAG, "instantiateItem " + position);
        DisplayFragment fragment = (DisplayFragment) super.instantiateItem(
                container, position);
        registeredFragments.put(position, fragment);
        return fragment;
    }

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

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

    public SparseArray<DisplayFragment> getRegisteredFragments() {
        return registeredFragments;
    }

}

It has been a long time since I wrote this, so are not able to go into great detail.

However, the key thing to note (I would think), is that I have an external 'bookkeeper' (the DisplayCoreModule). Whenever the amount of fragments would change in DisplayCoreModule, I simply invoked notifySizeChangingDataSetChange() to make the FragmentStatePagerAdapter refresh content.

Regaring some of your problems, FragmentStatePagerAdapter will not invoke onCreateView of your fragments until they are displayed. Usually this means only three fragments: the one you currently look at and one from either side of that fragment.

Perhaps, come to think of it, the root of your problem is missing getItemPosition() with POSITION_NONE. Seem to recall that it was neccesarry for dynamic content to be updated. To make notifyDataSetChanged have a proper effect.

cYrixmorten
  • 7,110
  • 3
  • 25
  • 33
  • Thank you for your answer! But i think that simply calling "notifydataSetChanged()" method whenever i add/remove tab/fragment is similar to your approach, so the problem still exists. – Lazarus Oct 05 '16 at 18:18
  • I see you point. Just looks a bit as if you are more eagerly managing the fragments for the adapter, whereas my snippet more or less only get a count from which it is asked to create instances. I think there is a difference there. Also added a few more lines to the answer that may be worth looking into. – cYrixmorten Oct 05 '16 at 18:25
  • I added the getItemPosition() overridden method, but nothing helped... Would you suggest to do something else? I really don't know how to make this work. – Lazarus Oct 05 '16 at 18:48
0

HACKY SOLUTION:

I simply modified my addTab method:

public void addTab(My_Fragment fragment, String title){

    boolean createFragment=true;

    if (getTabCount()>0){
        //all tabs must have different names
        for (int i=0;i<getTabCount();i++){

            if (getTabTitle(i).equals(title)==true){

                createFragment=false;
                break;
            }
        }
    }

    if (createFragment && fragment!=null){
        //this ensures that if a fragment of the same type already exists, that should be the one to be added, tabs are made of fragments of different classes, so there will be no problem while checking this.
        for (Fragment f: my_activity.getSupportFragmentManager().getFragments()){

            if (fragment.getClass().isInstance(f)){

                my_viewPagerAdapter.addFragment((My_Fragment)f,title);
                tabLayout.addTab(tabLayout.newTab().setText(title));
                return;
            }
        }
        my_viewPagerAdapter.addFragment(fragment,title);
        tabLayout.addTab(tabLayout.newTab().setText(title));
    }
}
Lazarus
  • 147
  • 13