Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

FragmentPagerAdapter is not removing items (Fragments) correctly

I have implemented the FragmentPagerAdapter and and using a List<Fragment> to hold all fragments for my ViewPager to display. On addItem() I simply add an instantiated Fragment and then call notifyDataSetChanged(). I am not sure if this is necessary or not.

My problem simply... start with fragment 1

[Fragment 1]  

add new Fragment 2

[Fragment 1] [Fragment 2] 

remove Fragment 2

[Fragment 1] 

add new Fragment 3

[Fragment 1] [Fragment 2] 

When adding new fragments everything seems great. Once I remove a fragment and then add a newly instantiated fragment the old fragment is still displayed. When i go a .getClass.getName() it is giving me Fragment 3's name however I still see Fragment 2.

I believe this might be an issue with instantiateItem() or such but I thought the adapter was to handle that for us. Any suggestions would be great.

adapter code...

public class MyPagerAdapter extends FragmentPagerAdapter { public final ArrayList<Fragment> screens2 = new ArrayList<Fragment>();   private Context context;  public MyPagerAdapter(FragmentManager fm, Context context) {     super(fm);     this.context = context; }  public void removeType(String name){     for(Fragment f: screens2){         if(f.getClass().getName().contains(name)){ screens2.remove(f); return; }     }     this.notifyDataSetChanged(); }  public boolean addSt(String tag, Class<?> clss, Bundle args){     if(clss==null) return false;     if(!clss.getName().contains("St")) return false;      if(!args.containsKey("cId")) return false;     boolean has = false;     boolean hasAlready = false;     for(Fragment tab: screens2){         if(tab.getClass().getName().contains("St")){             has = true;             if(tab.getArguments().containsKey("cId"))                 if(tab.getArguments().getLong("cId") == args.getLong("cId")){                     hasAlready = true;                 }             if(!hasAlready){                 // exists but is different so replace                 screens2.remove(tab);                 this.addScreen(tag, clss, args, C.PAGE_ST);                 // if returned true then it notifies dataset                 return true;             }         }         hasAlready = false;     }      if(!has){          // no st yet exist in adapter         this.addScreen(tag, clss, args, C.PAGE_ST);         return true;     }      return false; }  public boolean removeCCreate(){     this.removeType("Create");       return false; }  @Override public int getItemPosition(Object object) {     return POSITION_NONE; //To make notifyDataSetChanged() do something   }  public void addCCreate(){     this.removeCCreate();     Log.w("addding c", " ");     this.addScreen("Create C",  CreateCFragment.class, null, C.PAGE_CREATE_C); }  public void addScreen(String tag, Class<?> clss, Bundle args, int type){     if(clss!=null){         screens2.add(Fragment.instantiate(context, clss.getName(), args));     } }  @Override public int getCount() {     return screens2.size(); }   @Override public Fragment getItem(int position) {     return screens2.get(position);  }  } 

I realize the code uses some "ghetto" means of determining the fragment type however I wrote this code strictly for testing functionality. Any help or ideas would be great as it seems that not many people ventured into the world of FragmentPagerAdapters.

like image 616
Maurycy Avatar asked Jan 30 '12 08:01

Maurycy


2 Answers

I got same problem,and my solution was overring the method "destroyItem" as following.

@Override public void destroyItem(ViewGroup container, int position, Object object) {     FragmentManager manager = ((Fragment)object).getFragmentManager();     FragmentTransaction trans = manager.beginTransaction();     trans.remove((Fragment)object);     trans.commit(); } 

It's work for me,does anybody have another solutions?

Updated:

I found those code made Fragment removed unnecessary,so I added a condition to avoid it.

@Override public void destroyItem(ViewGroup container, int position, Object object) {     if (position >= getCount()) {         FragmentManager manager = ((Fragment) object).getFragmentManager();         FragmentTransaction trans = manager.beginTransaction();         trans.remove((Fragment) object);         trans.commit();     } } 
like image 116
Tericky Shih Avatar answered Sep 23 '22 18:09

Tericky Shih


Updated this post and included my solution (if someone can improve let me know)

Ok i've now solved my problem in a hackish way, but yeah it's working ;). If someone can improve my solution please let me know. For my new solution i now use a CustomFragmentStatePagerAdapter but it doesn't save the state like it should and stores all the Fragments in a list. This can cause a memory problem if the user has more than 50 fragments, like the normal FragmentPagerAdapter does. It would be great if someone can add the State-thing back to my solution without removing my fixes. Thanks.

So here's my CustomFragmentStatePagerAdapter.java

package com.tundem.webLab.Adapter;  import java.util.ArrayList;  import android.os.Bundle; import android.os.Parcelable; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentTransaction; import android.support.v4.view.PagerAdapter; import android.util.Log; import android.view.View; import android.view.ViewGroup;  public abstract class CustomFragmentStatePagerAdapter extends PagerAdapter {     private static final String TAG = "FragmentStatePagerAdapter";     private static final boolean DEBUG = false;      private final FragmentManager mFragmentManager;     private FragmentTransaction mCurTransaction = null;      public ArrayList<Fragment.SavedState> mSavedState = new ArrayList<Fragment.SavedState>();     public ArrayList<Fragment> mFragments = new ArrayList<Fragment>();     private Fragment mCurrentPrimaryItem = null;      public CustomFragmentStatePagerAdapter(FragmentManager fm) {         mFragmentManager = fm;     }      /**      * Return the Fragment associated with a specified position.      */     public abstract Fragment getItem(int position);      @Override     public void startUpdate(ViewGroup container) {}      @Override     public Object instantiateItem(ViewGroup container, int position) {         // If we already have this item instantiated, there is nothing         // to do. This can happen when we are restoring the entire pager         // from its saved state, where the fragment manager has already         // taken care of restoring the fragments we previously had instantiated.          // DONE Remove of the add process of the old stuff         /* if (mFragments.size() > position) { Fragment f = mFragments.get(position); if (f != null) { return f; } } */          if (mCurTransaction == null) {             mCurTransaction = mFragmentManager.beginTransaction();         }          Fragment fragment = getItem(position);         if (DEBUG)             Log.v(TAG, "Adding item #" + position + ": f=" + fragment);         if (mSavedState.size() > position) {             Fragment.SavedState fss = mSavedState.get(position);             if (fss != null) {                 try // DONE: Try Catch                 {                     fragment.setInitialSavedState(fss);                 } catch (Exception ex) {                     // Schon aktiv (kA was das heißt xD)                 }             }         }         while (mFragments.size() <= position) {             mFragments.add(null);         }         fragment.setMenuVisibility(false);         mFragments.set(position, fragment);         mCurTransaction.add(container.getId(), fragment);          return fragment;     }      @Override     public void destroyItem(ViewGroup container, int position, Object object) {         Fragment fragment = (Fragment) object;          if (mCurTransaction == null) {             mCurTransaction = mFragmentManager.beginTransaction();         }         mCurTransaction.remove(fragment);          /*if (mCurTransaction == null) { mCurTransaction = mFragmentManager.beginTransaction(); } if (DEBUG) Log.v(TAG, "Removing item #" + position + ": f=" + object + " v=" + ((Fragment)          * object).getView()); while (mSavedState.size() <= position) { mSavedState.add(null); } mSavedState.set(position, mFragmentManager.saveFragmentInstanceState(fragment));          * mFragments.set(position, null); mCurTransaction.remove(fragment); */     }      @Override     public void setPrimaryItem(ViewGroup container, int position, Object object) {         Fragment fragment = (Fragment) object;         if (fragment != mCurrentPrimaryItem) {             if (mCurrentPrimaryItem != null) {                 mCurrentPrimaryItem.setMenuVisibility(false);             }             if (fragment != null) {                 fragment.setMenuVisibility(true);             }             mCurrentPrimaryItem = fragment;         }     }      @Override     public void finishUpdate(ViewGroup container) {         if (mCurTransaction != null) {             mCurTransaction.commitAllowingStateLoss();             mCurTransaction = null;             mFragmentManager.executePendingTransactions();         }     }      @Override     public boolean isViewFromObject(View view, Object object) {         return ((Fragment) object).getView() == view;     }      @Override     public Parcelable saveState() {         Bundle state = null;         if (mSavedState.size() > 0) {             state = new Bundle();             Fragment.SavedState[] fss = new Fragment.SavedState[mSavedState.size()];             mSavedState.toArray(fss);             state.putParcelableArray("states", fss);         }         for (int i = 0; i < mFragments.size(); i++) {             Fragment f = mFragments.get(i);             if (f != null) {                 if (state == null) {                     state = new Bundle();                 }                 String key = "f" + i;                 mFragmentManager.putFragment(state, key, f);             }         }         return state;     }      @Override     public void restoreState(Parcelable state, ClassLoader loader) {         if (state != null) {             Bundle bundle = (Bundle) state;             bundle.setClassLoader(loader);             Parcelable[] fss = bundle.getParcelableArray("states");             mSavedState.clear();             mFragments.clear();             if (fss != null) {                 for (int i = 0; i < fss.length; i++) {                     mSavedState.add((Fragment.SavedState) fss[i]);                 }             }             Iterable<String> keys = bundle.keySet();             for (String key : keys) {                 if (key.startsWith("f")) {                     int index = Integer.parseInt(key.substring(1));                     Fragment f = mFragmentManager.getFragment(bundle, key);                     if (f != null) {                         while (mFragments.size() <= index) {                             mFragments.add(null);                         }                         f.setMenuVisibility(false);                         mFragments.set(index, f);                     } else {                         Log.w(TAG, "Bad fragment at key " + key);                     }                 }             }         }     } } 

Here's my normal FragmentAdapter.java

package com.tundem.webLab.Adapter;  import java.util.LinkedList; import java.util.List;  import android.support.v4.app.FragmentManager;  import com.tundem.webLab.fragments.BaseFragment; import com.viewpagerindicator.TitleProvider;  public class FragmentAdapter extends CustomFragmentStatePagerAdapter implements TitleProvider {     public List<BaseFragment> fragments = new LinkedList<BaseFragment>();      private int actPage;      public FragmentAdapter(FragmentManager fm) {         super(fm);     }      public void setActPage(int actPage) {         this.actPage = actPage;     }      public void addItem(BaseFragment fragment) {         // TODO if exists don't open / change to that tab         fragments.add(fragment);     }      public BaseFragment getActFragment() {         return getItem(getActPage());     }      public int getActPage() {         return actPage;     }      @Override     public BaseFragment getItem(int position) {         if (position < getCount()) {             return fragments.get(position);         } else             return null;     }      @Override     public int getCount() {         return fragments.size();     }      @Override     public String getTitle(int position) {         return fragments.get(position).getTitle();     }      @Override     public int getItemPosition(Object object) {         return POSITION_NONE;     } } 

And this is the way i delete a Fragment. (I know it's a bit more than only .remove() ). Be free to improve my solution, you can also add this code somewhere in the adapter so yeah. It's up to the user who tries to implement this. I use this in my TabHelper.java (A class which handles all tab operations like delete, add, ...)

    int act = Cfg.mPager.getCurrentItem();     Cfg.mPager.removeAllViews();     Cfg.mAdapter.mFragments.remove(act);     try {         Cfg.mAdapter.mSavedState.remove(act);     } catch (Exception ex) {/* Already removed */}     try {         Cfg.mAdapter.fragments.remove(act);     } catch (Exception ex) {/* Already removed */}      Cfg.mAdapter.notifyDataSetChanged();     Cfg.mIndicator.notifyDataSetChanged(); 

Description of the Cfg. thing. I save the reference to those objects in a cfg, class so i can always use them without the need of a special Factory.java ...

Yeah. i hope i was able to help. Feel free to improve this, but let me know so i can improve my code too.

Thanks.

If i missed any code let me know.


My old answer also works but only if you have different Fragments. FileFragment, WebFragment, ... Not if you use one of those fragmenttypes twice.

I got it pseudo working for now. It's a really dirty solution and i'm still searching for a better one. Please help.

I changed the code, where i delete a tab to this one:

   public static void deleteActTab()         {                //We set this on the indicator, NOT the pager             int act = Cfg.mPager.getCurrentItem();             Cfg.mAdapter.removeItem(act);             List<BaseFragment> frags = new LinkedList<BaseFragment>();             frags = Cfg.mAdapter.fragments;              Cfg.mPager = (ViewPager)Cfg.act.findViewById(R.id.pager);             Cfg.mPager.setAdapter(Cfg.mAdapter);             Cfg.mIndicator.setViewPager(Cfg.mPager);              Cfg.mAdapter.fragments = frags;              if(act > 0)             {                 Cfg.mPager.setCurrentItem(act-1);                 Cfg.mIndicator.setCurrentItem(act-1);             }              Cfg.mIndicator.notifyDataSetChanged();         } 

If someone can improve this code let me know. If someone can tell us the real answer for that problem. please add it here. There are many many people who experience this issue. I added a reputation of 50 for the one who solve it. I can also give a donation for the one who solve it.

Thanks

like image 26
mikepenz Avatar answered Sep 20 '22 18:09

mikepenz