2

I'm having some problems when dealing with Fragment using ViewPager.

What I'm having:
An activity (say, MainActivity) that contains a ViewPager to display some Fragment(s). Some of them contains a callback interface, which will be called to do somethings in the MainActivity.
The MainActivity has a FragmentPagerAdapter class, which is used as the adapter of the ViewPager. And a List<Fragment> in FragmentPagerAdapter to store some Fragment that will be displayed on the ViewPager.

What I'm expecting:
First launch, the Fragment called the callback interface's methods when I hit a button in it and MainActivity did somethings inside that. It worked great.
After a screen rotation, I expected it to work the same as the first launch BUT
NullPointerException: attempt to invoke a method on a null reference object (particularly, the Fragment's interface) hit me in my face.

What I know:
- The getItem(int position) won't be called again once the Fragment is created. Instead the instantiateItem(ViewGroup container, int position) will be called.
- FragmentManager will store some Fragment in mActive.mValues
- ViewPager and fragments — what's the right way to store fragment's state? (I did reference to this and some other same topics on StackOverflow too.)

What I have tried and saw:
- Override instantiateItem(ViewGroup container, int position)
- Debugged for 1 day. I saw that when I pass getSupportFragmentManager() in MainActivity's onCreate() method to FragmentPagerAdapter's super constructor, in the first launch, it has an "address in memory", assume it was '1a1a1a1'. The mActive.mValues of FragmentManager saved some Fragment' "address in memory" which are identical to the List<Fragment> containing them (assume it was 'qwertyu'). Which meaned it was right.
But when I rotated the screen, passing the getSupportFragmentManager() again, the "address in memory" was completely different, assume '9f9f9f9'. And FragmentManager's mActive.mValues contained a different set of Fragment' "address in memory" too (assume 'abcdeff'), although the number of Fragment in it was equal to the number of Fragment that was saved on the first launch (before rotation).
I have added a Fragment to the List<Fragment> with a new "address in memory" (assume 'abababa'), has the callback interface. But when I hit the button in it, it was the Fragment that was in the FragmentManager's mActive.mValues after the rotation (with "address in memory" is 'abcdeff' as I assumed above), and that one didn't have the callback interface (due to not being set in MainActivity first). And caused the NullPointerException as mentioned above.

My questions now is:
- First of all, how to get rid of this problem!? It would be better to keep using FragmentPagerAdapter instead of another class. But I will consider using other class too.
- Second, can you explain why FragmentManager saved the Fragment instance before rotation. But after rotation, it creates a completely different Fragment instance but still uses it instead of the Fragment that was saved in the List<Fragment>?

Here is the code (I think I didn't use the instantiateItem(ViewGroup container, int position) method in the right way so it still caused the problem).

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //Attach the SectionsPagerAdapter to the ViewPager
        SectionsPagerAdapter pagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager());
        ViewPager viewPager = findViewById(R.id.viewPager);
        viewPager.setAdapter(pagerAdapter);
    }


    //
    //
    //Adapter class
    private class SectionsPagerAdapter extends FragmentPagerAdapter {
        private static final int PAGE_HOME = 0;
        private int tabCount = 1;
        private List<Fragment> fragmentList = new ArrayList<>();
        private List<String> fragmentTitleList = new ArrayList<>();
        //private FragmentManager fragmentManager;

        SectionsPagerAdapter(FragmentManager fm) {
            super(fm);
            //fragmentManager = fm;

            //Default HomeFragment
            HomeFragment homeFragment = new HomeFragment();
            //Callback interface
            homeFragment.setOnCategoryFragmentChangedListener(new HomeFragment.OnCategoryFragmentChangedListener() {
                //This method will be called when a button in HomeFragment is clicked
                @Override
                public void onAddNewCategory(String categoryName) {
                    addNewCategory(categoryName);
                }
            });
            fragmentList.add(homeFragment);
            fragmentTitleList.add("Home");
        }

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

        @Override
        public int getCount() {
            return tabCount;
        }

        @Override
        public CharSequence getPageTitle(int position) {
            return fragmentTitleList.get(position);
        }

        @Override
        public Object instantiateItem(ViewGroup container, int position) {
            fragmentList.set(position, (Fragment) super.instantiateItem(container, position));
            return fragmentList.get(position);
        }

        private void addNewCategory(String categoryName) {
            CategoryFragment fragment = new CategoryFragment();

            tabCount += 1;
            fragmentList.add(fragment);
            fragmentTitleList.add(categoryName);
            notifyDataSetChanged();
        }
    }
}

Please help. I'm being insane for 2 days now...!

Chocolatto
  • 63
  • 7

2 Answers2

0

I believe android restarts the activity during orientation change thus making multiple instances of FragmentPagerAdapter and multiple set of instances of List.

I don't completely understand your question but I suspect instantiateItem doesn't do anything anyway. Doesn't the Fragment getItem(int pos) work without overriding instantiateItem()?

Vaughn Armada
  • 439
  • 4
  • 5
  • Yes, `getItem(int pos)` did work without overriding `instantiateItem()`. But as I said, after screen rotation, `getItem(int pos)` won't be called again on the Fragment(s) that were already created in the first launch. In the code, new HomeFragment instance will be created and added to the `List`; but the ViewPager didn't display that instance (which was added to the `List`), instead it display the instance in `mActive.mValues` of `FragmentManager`, thus caused the NPE. – Chocolatto Feb 21 '19 at 16:57
  • My question is about: "How to display Fragment(s) that are NOT from FragmentManager's `mActive.mValues` (but in the new List) after screen rotation? (Additional question: And should I do it like that, by recreating the `List<>` then use it? Is there a better way like recycling or reuse the already created Fragment?") – Chocolatto Feb 21 '19 at 17:03
0

Oh well, right after i felt in sleep, I found the solution. It's true that I didn't use the instantiateItem() method in the right way. After debugging again, I found that the instantiateItem() method get call whenever I swipe (or choose if using TabLayout as well) to another Fragment, and even get call before getItem(int pos), no matter what it's the first launch or after rotation. Which is why I think we should set things up for the Fragment in the instantiateItem() method.
So here is how I use the instantiateItem() method now:

        @Override
        public Object instantiateItem(ViewGroup container, int position) {
            fragmentList.set(position, (Fragment) super.instantiateItem(container, position));
            Fragment fragment = fragmentList.get(position);
            if (position == PAGE_HOME) {
                ((HomeFragment) fragment).setOnCategoryFragmentChangedListener(new HomeFragment.OnCategoryFragmentChangedListener() {
                    @Override
                    public void onAddNewCategory(String categoryName) {
                        addNewCategory(categoryName);
                    }
                });
            }
            return fragment;
        }

If anyone have a better solution, please just tell me if you don't mind. I will consider about it.

Chocolatto
  • 63
  • 7