Search code examples
androidandroid-fragmentsandroid-viewpagerfragmentpageradapternotifydatasetchanged

Android ViewPager - Adding/Removing Fragments not working properly


I have 5 fragments:

  • TimerFragment
  • CounterFragment
  • GameFragment
  • NoteFragment
  • SettingsFragment

The SettingsFragment has 4 switches, which control the adding/removing of the other 4 fragments. Turning a switch off would remove the fragment and turning it on would add it.

The problem is that when I try to remove any one of the first 4 fragments, the ViewPager removes the SettingsFragment. I can only see the change when I close and restart my app.

Similarly, when I add a removed Fragment back into the list, it adds a new SettingsFragment. Once again, I have to close and restart my app to see the proper changes.

It's odd and there's no error to help me figure out what's causing it. I'd like to know why this is happening and how to solve it. Thank You.

Here is my code:

Here, I add the fragments:

frags = new String[]{".TimerFragment", ".CounterFragment", ".GameFragment",
            ".NoteFragment", ".SettingsFragment"};
    final Bundle args = new Bundle();
    boolean[] showArray = new boolean[4];
    for (int i = 0; i < frags.length; i++) {
        if (frags[i].equals(".SettingsFragment")) {
            args.putBooleanArray("showing", showArray);
            fragments.add(frags[i]);
        } else {
            boolean showing = sharedPrefs.getBoolean("Tooly" + frags[i] + ".Show",
                    true);
            showArray[i] = showing;
            if (showing) fragments.add(frags[i]);
        }
        fragIds[i] = View.generateViewId();
    }

Here is my FragmentPagerAdapter

vpTools.setAdapter(new FragmentPagerAdapter(getSupportFragmentManager()) {

        @Override
        public Fragment getItem(int position) {
            try {
                Fragment fragment = (Fragment) Class.forName(getPackageName() +
                        fragments.get(position)).newInstance();
                if (fragments.get(position).equals(".SettingsFragment"))
                    fragment.setArguments(args);
                return fragment;
            } catch (InstantiationException | IllegalAccessException |
                    ClassNotFoundException e) {
                e.printStackTrace();
            }
            return null;
        }

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

        @Override
        public long getItemId(int position) {
            return fragIds[position];
        }

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

And here is the code where I add and remove the fragments:

@SuppressLint("ApplySharedPref")
@Override
public void onSettingChanged(int pos, boolean checked) throws Exception {
    if (checked && !frags[pos].equals(fragments.get(pos))) fragments.add(pos, frags[pos]);
    else if (!checked) fragments.remove(pos);
    vpTools.getAdapter().notifyDataSetChanged();
    sharedPrefs.edit().putBoolean("Tooly" + frags[pos] + ".Show", checked).commit();
}

If you're wondering why I have a list of Strings and am instantiating new Fragments from the PagerAdapter itself, it's because I'm trying to avoid an IllegalStateException which occurs when you try to add the same instance of a fragment more than once. Here are a few questions for that:

IllegalStateException: Can't change the tag of fragments...

Also this one with the same error...

I also have an array of unique ids for each of the fragments, which are returned every time getItemId() is called. This is to avoid the exception as well.


Solution

  • The problem was that my set of fragment Ids were the same every time. The key was to return a NEW unique id every single time. Here is the updated code for getItemId():

        @Override
        public long getItemId(int position) {
            return View.generateViewId();
        }
    

    The generateViewId() method generates and returns a new unique, non-clashing view id every time.