Christopher Hackl
Christopher Hackl

Reputation: 193

How to add Fragment to first position in FragmentPagerAdapter

I am using FragmentPagerAdapter and a ViewPager to add custom Fragments EDIT: from my MainActivity (also sending a bunch of extra data based on a JSON response via bundle) and using swiping motions to move to the next Fragments in the List.

public class MyPagerAdapter extends FragmentPagerAdapter implements Serializable {

	public List<Fragment> fragments;
	public FragmentManager fm;

	public MyPagerAdapter(FragmentManager fm) {
		super(fm);
		this.fm = fm;
		this.fragments = new ArrayList<Fragment>();	
	}
	
	@Override
	public Fragment getItem(int position) {
		return fragments.get(position);
	}


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

}

Everything is working fine as long as I'm adding new Fragments by using

MyPagerAdapter pageAdapter = new MyPagerAdapter(getSupportFragmentManager());

pager = (ViewPager)findViewById(R.id.myViewPager);

pageAdapter.fragments.add(new CustomFragment());

pager.setAdapter(pageAdapter);

But I can't find a proper way to add Fragments to the beginning of the List and swipe back.

I've tried both

pageAdapter.fragments.add(0, new CustomFragment());

as well as changing the FragmentPagerAdapters List to LinkedList and using

pageAdapter.fragments.addFirst(new CustomFragment());

and then refreshing the adapter by using

pageAdapter.notifyDataSetChanged();

and i keep getting the following exception:

java.lang.IllegalStateException: Can't change tag of fragment CustomFragment{2ead9520 #10 id=0x7f0a0002 android:switcher:2131361794:10}: was android:switcher:2131361794:10 now android:switcher:2131361794:11

Upvotes: 3

Views: 3529

Answers (4)

BitByteDog
BitByteDog

Reputation: 3484

The key methods no one has talked about yet is public int getItemPosition(Object) which is used to remap fragments to pages after they move and public long getItemId(int position) which must be overridden by a pager adapter that reorders fragments. The default implementation uses the position of the page as the id, so reordering confuses the FragmentPagerAdapter.

(I am leaving out the Serializable interface as it is irrelevant for the purposes of answering the question - How to reorder fragments in a FragmentPagerAdapter).

public class MyPagerAdapter extends FragmentPagerAdapter {

    public List<Fragment> fragments;
    public FragmentManager fm;

    public MyPagerAdapter(FragmentManager fm) {
        super(fm);
        this.fm = fm;
        this.fragments = new ArrayList<Fragment>(); 
    }

    void addFragmentAtPosition(int position, Fragment f) {
        if(position == fragments.size())
            fragments.add(f);
        else
            fragments.add(position, f);
        notifyDataSetChanged();
    }

    void removeFragmentAtPosition(int position) {
        Fragment f = fragments.remove(position);
        if(f != null)
            fm.beginTransaction().remove(f).commit();
        notifyDataSetChanged();
    }

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

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

    @Override
    public int getItemPosition(Object object){
        /*
         * called when the fragments are reordered to get the
         * changes.
         */
        int idx = fragments.indexOf(object);
        return idx < 0 ? POSITION_NONE : idx;
    }

    @Override
    public long getItemId(int position) {
        /*
         * map to a position independent ID, because this
         * adapter reorders fragments
         */
        return System.identityHashCode(fragments.get(position));
    }
}

The key additions are the overrides of public int getItemPosition(Object) and public long getItemId(int). These allow the FragmentPagerAdapter to reposition the existing fragments and to identify the existing active fragments in the FragmentManager cache correctly.

Upvotes: 3

Endy Silveira
Endy Silveira

Reputation: 211

Don't know if you still need the answer, but I was trying to do something similar and just found the solution :)

You are receiving that exception because of your getItem function. You are returning the fragment in the position that the function receives, and this position is always the last position of the array because that would correspond to the last added fragment in a "typical" usage.

In your case, you want to add a new Fragment in the first position, so your getItem will return twice the same fragment and throw the exception.

To avoid this you need to create a public function into your Adapter and the index of the fragment you are adding, and then return this specific fragment.

PS.: I'm developing only in Kotlin for about 6 months now, so it can have some typos.

public int newFragmentIndex = 0;
public List<Fragment> fragments;

...

public void addFragmentAt(int index, Fragment fragment) {
    newFragmentIndex = index;
    pageAdapter.fragments.add(index, fragment);
    notifyDataSetChanged();
}

public void addFragment(Fragment fragment) {
    newFragmentIndex = fragments.size();
    pageAdapter.fragments.add(fragment);
    notifyDataSetChanged();
}

...

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


To call this from your Activity, change your
pageAdapter.fragments.add(new CustomFragment());
to
pageAdapter.fragments.addFragment(new CustomFragment());

And
pageAdapter.fragments.add(0, new CustomFragment());
to
pageAdapter.fragments.addFragmentAt(0, new CustomFragment());


Hope this helps you with your problem!

Upvotes: 0

MineConsulting SRL
MineConsulting SRL

Reputation: 2340

I would suggest you don't keep a list of references to fragments since it is not necessary and you risk to create memory leaks. What i would do is create the fragment only when required like this :

@Override
public Fragment getItem(int position) {
    Fragment fragment = new MyFragment();
    return fragment;
}

To solve your problem you should create the fragment based on your needs, for example if you have fragments of different class instances like for example one instance of MyFragment another one of YourFragment and so on, just keep a list which says which kind of fragment occupy that position. For example:

myListMap = new HashMap<Integer, Integer>();
myListMap.put(position, type);

and then create the fragment on the fly:

@Override
public Fragment getItem(int position) {
    Fragment fragment = null;
    int type = ...find fragment type in that position ....
    if(type == MYFRAGMENTTYPE) {
        fragment = new MyFragment();
    }
    return fragment;
}

Upvotes: 1

Nasir
Nasir

Reputation: 2122

You should not create and add fragments this way. Instead just instantiate the fragments in getItem and the adapter will take care of using them. just do this:

@Override
public Fragment getItem(int position) {
    Fragment fragment = new CustomFragment();
    fragments.add(fragment)
    return fragment
}

Upvotes: 1

Related Questions