Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

insert and remove fragments into viewpager properly

I'm making an adroid application with viewpager and fragments. I want to make an option to add or remove fragment pages to the pager dynamically. I have a custom FragmentPagerAdapter:

public class MyAdapter extends FragmentPagerAdapter implements TitleProvider{

    protected final List<PageFragment> fragments;

    /**
    * @param fm
    * @param fragments
    */
    public MyAdapter(FragmentManager fm, List<PageFragment> fragments) {

        super(fm);
        this.fragments = fragments;
    }

    public void addItem(PageFragment f){
        fragments.add(f);
        notifyDataSetChanged();
    }

    public void addItem(int pos, PageFragment f){
        for(int i=0;i<fragments.size();i++){
            if(i>=pos){
                getSupportFragmentManager().beginTransaction().remove(getSupportFragmentManager().findFragmentByTag(getFragmentTag(i))).commit();
            }
        }
        fragments.add(pos,f);
        notifyDataSetChanged();
        pager.setAdapter(this);
        pager.setCurrentItem(pos);

    }

    public void removeItem(int pos){
        getSupportFragmentManager().beginTransaction().remove(getSupportFragmentManager().findFragmentByTag(getFragmentTag(pos))).commit();
        for(int i=0;i<fragments.size();i++){
            if(i>pos){
                getSupportFragmentManager().beginTransaction().remove(getSupportFragmentManager().findFragmentByTag(getFragmentTag(i))).commit();
            }
        }
        fragments.remove(pos);

        notifyDataSetChanged();
        pager.setAdapter(this);
        if(pos<fragments.size()){
            pager.setCurrentItem(pos);
        }else{
            pager.setCurrentItem(pos-1);
        }
    }



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

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

    @Override
    public int getItemPosition(Object object) {
        return POSITION_NONE;
    }

    private String getFragmentTag(int pos){
        return "android:switcher:"+R.id.pager+":"+pos;
    }

    public String getTitle(int position) {
        String name =  fragments.get(position).getName();
        if(name.equals(""))
            return "- "+position+" -";
        return name;
    }
}

I can remove any fragment except the 0. when I try to remove it I got a NullPointerException on the notifyDataSetChanged(); or on the pager.setAdapter(this); if i comment out the notify.

I also got a NullPointerException when I try to insert a new page. When I add the new page to the end of the list it's working fine. I even tried to readd the fragments in the insert with this after the fragments.add(pos,f)

    for(int i=0;i<fragments.size();i++){
            if(i>=pos){
                getSupportFragmentManager().beginTransaction().add(fragments.get(i).toFragment(),getFragmentTag(i-1)).commit();
            }
        }

if I use getFragmentTag(i-1) I got nullpointer again. With using just i I got illegalstateexception because can not modify fragment's tag. With beginTransaction().add(pager.getId,fragments.get(i).toFragment()) it is still nullpointer...

My question is: what am I doing wrong, and how can it be done properly? (and maybe: from where notyfyDataSetChanged() get the data when causes nullpointerexception?)

like image 243
Blackguard Avatar asked Apr 11 '12 21:04

Blackguard


1 Answers

This is what I used to get around the problem with fragments being tagged when instantiated and the fact that previously tagged fragments cannot change position within the ViewPager when trying to add or remove pages, i.e. getting the exception:

java.lang.IllegalStateException: Can't change tag of fragment MyFragment{4055f558 id=0x7f050034 android:switcher:2131034164:3}: was android:switcher:2131034164:3 now android:switcher:2131034164:4

This adapter is created with a list of all pages upfront and you can then use setEnabled(int position, boolean enabled) to disable or enable certain pages which hides or shows these pages in the ViewPager.

It works by maintaining an internal list of all fragments but exposing to the ViewPager and mapping the positions of enabled fragments only.

public class DynamicFragmentPagerAdapter extends FragmentPagerAdapter {

    public final ArrayList<Fragment> screens = new ArrayList<Fragment>();

    private Context context;
    private List<AtomicBoolean> flags = new ArrayList<AtomicBoolean>();

    public DynamicFragmentPagerAdapter(FragmentManager fm, Context context, List<Class<?>> screens) {
        super(fm);
        this.context = context;

        for(Class<?> screen : screens)
            addScreen(screen, null);

        notifyDataSetChanged();
    }

    public DynamicFragmentPagerAdapter(FragmentManager fm, Context context, Map<Class<?>, Bundle> screens) {
        super(fm);
        this.context = context;

        for(Class<?> screen : screens.keySet())
            addScreen(screen, screens.get(screen));

        notifyDataSetChanged();
    }

    private void addScreen(Class<?> clazz, Bundle args) {
        screens.add(Fragment.instantiate(context, clazz.getName(), args));
        flags.add(new AtomicBoolean(true));
    }

    public boolean isEnabled(int position) {
        return flags.get(position).get();
    }

    public void setEnabled(int position, boolean enabled) {
        AtomicBoolean flag = flags.get(position);
        if(flag.get() != enabled) {
            flag.set(enabled);
            notifyDataSetChanged();
        }
    }

    @Override
    public int getCount() {
        int n = 0;
        for(AtomicBoolean flag : flags) {
            if(flag.get())
                n++;
        }
        return n;
    }

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

    @Override
    public int getItemPosition(Object object) {
        return POSITION_NONE; // To make notifyDataSetChanged() do something
    }

    @Override
    public void notifyDataSetChanged() {
        try {
            super.notifyDataSetChanged();
        } catch (Exception e) {
            Log.w("", e);
        }
    }

    private List<Fragment> getEnabledScreens() {
        List<Fragment> res = new ArrayList<Fragment>();
        for(int n = 0; n < screens.size(); n++) {
            if(flags.get(n).get())
                res.add(screens.get(n));
        }
        return res;
    }

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        // We map the requested position to the position as per our screens array list
        Fragment fragment = getEnabledScreens().get(position);
        int internalPosition = screens.indexOf(fragment);
        return super.instantiateItem(container, internalPosition);
    }

    @Override
    public CharSequence getPageTitle(int position) {
        Fragment fragment = getEnabledScreens().get(position);
        if(fragment instanceof TitledFragment)
            return ((TitledFragment)fragment).getTitle(context);
        return super.getPageTitle(position);
    }

    public static interface TitledFragment {
        public String getTitle(Context context);
    }
}
like image 80
Oliver Jonas Avatar answered Nov 03 '22 01:11

Oliver Jonas