I\'ve got this activity, which holds a fragment. This fragment layout consists of a view pager with several fragments (two, actually).
When the view pager is created, it
My answer is kind of similar to Joshua Hunt's one, but by commiting the transaction in finishUpdate
method you get much better performance. One transaction instead of two per update.
Here is the code:
private class SuchPagerAdapter extends PagerAdapter{
private final FragmentManager mFragmentManager;
private SparseArray<Fragment> mFragments;
private FragmentTransaction mCurTransaction;
private SuchPagerAdapter(FragmentManager fragmentManager) {
mFragmentManager = fragmentManager;
mFragments = new SparseArray<>();
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
Fragment fragment = getItem(position);
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
mCurTransaction.add(container.getId(),fragment,"fragment:"+position);
return fragment;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
mCurTransaction.detach(mFragments.get(position));
mFragments.remove(position);
}
@Override
public boolean isViewFromObject(View view, Object fragment) {
return ((Fragment) fragment).getView() == view;
}
public Fragment getItem(int position) {
return YoursVeryFragment.instantiate();
}
@Override
public void finishUpdate(ViewGroup container) {
if (mCurTransaction != null) {
mCurTransaction.commitAllowingStateLoss();
mCurTransaction = null;
mFragmentManager.executePendingTransactions();
}
}
@Override
public int getCount() {
return countOfPages;
}
}
Why solutions above so complex? Looks like overkill. I solve it just with replacing old reference to new in class extended from FragmentPagerAdapter
@Override
public Object instantiateItem(ViewGroup container, int position) {
frags[position] = (Fragment) super.instantiateItem(container, position);
return frags[position];
}
All adapter's code looks like this
public class RelationsFragmentsAdapter extends FragmentPagerAdapter {
private final String titles[] = new String[3];
private final Fragment frags[] = new Fragment[titles.length];
public RelationsFragmentsAdapter(FragmentManager fm) {
super(fm);
frags[0] = new FriendsFragment();
frags[1] = new FriendsRequestFragment();
frags[2] = new FriendsDeclinedFragment();
Resources resources = AppController.getAppContext().getResources();
titles[0] = resources.getString(R.string.my_friends);
titles[1] = resources.getString(R.string.my_new_friends);
titles[2] = resources.getString(R.string.followers);
}
@Override
public CharSequence getPageTitle(int position) {
return titles[position];
}
@Override
public Fragment getItem(int position) {
return frags[position];
}
@Override
public int getCount() {
return frags.length;
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
frags[position] = (Fragment) super.instantiateItem(container, position);
return frags[position];
}
}
I had the same issue - I assume you're subclassing FragmentPagerAdapter
for your pager adapter (as getItem()
is specific to FragmentPagerAdapter
).
My solution was to instead subclass PagerAdapter
and handle the fragment creation/deletion yourself (reimplementing some of the FragmentPagerAdapter
code):
public class ListPagerAdapter extends PagerAdapter {
FragmentManager fragmentManager;
Fragment[] fragments;
public ListPagerAdapter(FragmentManager fm){
fragmentManager = fm;
fragments = new Fragment[5];
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
assert(0 <= position && position < fragments.length);
FragmentTransaction trans = fragmentManager.beginTransaction();
trans.remove(fragments[position]);
trans.commit();
fragments[position] = null;
}
@Override
public Fragment instantiateItem(ViewGroup container, int position){
Fragment fragment = getItem(position);
FragmentTransaction trans = fragmentManager.beginTransaction();
trans.add(container.getId(),fragment,"fragment:"+position);
trans.commit();
return fragment;
}
@Override
public int getCount() {
return fragments.length;
}
@Override
public boolean isViewFromObject(View view, Object fragment) {
return ((Fragment) fragment).getView() == view;
}
public Fragment getItem(int position){
assert(0 <= position && position < fragments.length);
if(fragments[position] == null){
fragments[position] = ; //make your fragment here
}
return fragments[position];
}
}
Hope this helps.
With reference to simekadam's solution, mFragments isn't being populated in instantiateItem and needs
mFragments.put(position, fragment);
inside or you'll end up with this error: Trying to remove fragment from view gives me NullPointerException on mNextAnim.
My code :
public class SampleAdapter extends FragmentStatePagerAdapter {
private Fragment mFragmentAtPos2;
private FragmentManager mFragmentManager;
private Fragment[] mFragments = new Fragment[3];
public SampleAdapter(FragmentManager mgr) {
super(mgr);
mFragmentManager = mgr;
Bundle hack = new Bundle();
try {
for (int i = 0; i < mFragments.length; i++) {
hack.putInt("hack", i);
mFragments[i] = mFragmentManager.getFragment(hack, "hack");
}
} catch (Exception e) {
// No need to fail here, likely because it's the first creation and mActive is empty
}
}
public void switchFrag(Fragment frag) {
if (frag == null) {
Dbg.e(TAG, "- switch(frag) frag is NULL");
return;
} else Dbg.v(TAG, "- switch(frag) - frag is " + frag.getClass());
// We have to check for mFragmentAtPos2 null in case of first time (only Mytrips fragment being instatiante).
if (mFragmentAtPos2!= null)
mFragmentManager.beginTransaction()
.remove(mFragmentAtPos2)
.commit();
mFragmentAtPos2 = frag;
notifyDataSetChanged();
}
@Override
public int getCount() {
return(3);
}
@Override
public int getItemPosition(Object object) {
Dbg.v(TAG,"getItemPosition : "+object.getClass());
if (object instanceof MyTripsFragment
|| object instanceof FindingDriverFragment
|| object instanceof BookingAcceptedFragment
|| object instanceof RideStartedFragment
|| object instanceof RideEndedFragment
|| object instanceof ContactUsFragment
)
return POSITION_NONE;
else return POSITION_UNCHANGED;
}
@Override
public Fragment getItem(int position) {
Dbg.v("SampleAdapter", "getItem called on: "+position);
switch (position) {
case 0:
if (snapbookFrag==null) {
snapbookFrag = new SnapBookingFragment();
Dbg.e(TAG, "snapbookFrag created");
}
return snapbookFrag;
case 1:
if(bookingFormFrag==null) {
bookingFormFrag = new BookingFormFragment();
Dbg.e(TAG, "bookingFormFrag created");
}
return bookingFormFrag;
case 2:
if (mFragmentAtPos2 == null) {
myTripsFrag = new MyTripsFragment();
mFragmentAtPos2 = myTripsFrag;
return mFragmentAtPos2;
}
return mFragmentAtPos2;
default:
return(new SnapBookingFragment());
}
}
}
The problem is that getItem()
in FragmentPageAdapter
has a wrong name. It should have been named createItem()
.
Because the way it works the getItem()
is for create fragments and it's not safe to call it to query/find a fragment.
My recomendation is to make a copy of current FragmentPagerAdapter and change it that way:
Add:
public abstract Fragment createFragment(int position);
And change getItem to:
public Fragment getItem(int position) {
if(containerId!=null) {
final long itemId = getItemId(position);
String name = makeFragmentName(containerId, itemId);
return mFragmentManager.findFragmentByTag(name);
} else {
return null;
}
}
Finally add that to instantiateItem:
if(containerId==null)
containerId = container.getId();
else if(containerId!=container.getId())
throw new RuntimeException("Container id not expected to change");
Complete code at this gist
I think that this implementation is safer and easier to use and also has same performance from the original adapter from Google engineers.