33

I have an activity with 3 fragments (A, B, C). Fragment A consists of a ViewPager with 2 ListFragments. The user can tap on an item in any of the listfragments and by doing so, goes to fragment B.

In fragment A I do:

@Override
public void onAttach(Activity activity) {
    super.onAttach(activity);
    pagerAdapter = new PagerAdapter(getActivity().getSupportFragmentManager());
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.fragmentA, container, false);

    vpPager = (ViewPager)view.findViewById(R.id.vpPager);
    vpPager.setOffscreenPageLimit(2);
    vpPager.setAdapter(pagerAdapter);
    vpPager.addOnPageChangeListener(this);

    return view;
}

And the PagerAdapter is as follows:

private class PagerAdapter extends FragmentPagerAdapter {
    private final ListFragment1 lf1 = ListFragment1 .newInstance();
    private final ListFragment2 lf2 = ListFragment2 .newInstance();

    public PagerAdapter(android.support.v4.app.FragmentManager fm) {
        super(fm);
    }

    @Override
    public android.support.v4.app.Fragment getItem(int position) {
        switch (position) {
            case 0:  return lf1;
            case 1:  return lf2;
            default: return null;
        }
    }
}

The first time the activity is shown, the viewpager list fragments are displayed correctly.

The 2 viewpager fragments load data from a db, and I do this only once (when the fragments are created).

The user can tap on an item and fragment B is displayed. If the user presses Back, fragment A is shown. However the list fragments are not shown (already an instance of them still exists).

Could it be that the view has been destroyed, even though instances exist?

What is wrong here? Is there a better approach?

EDIT

If I use newInstance in the pager adapter, I get an IllegalStateException: not attached to activity. This is because I start an async task as follows:

@Override
public void onPageSelected(int position) {
    Fragment fragment = pagerAdapter.getItem(position);
    if (fragment instanceof IPagedFragment) {
        ((IPagedFragment) fragment).onShown();
    }
}

And onShown is:

@Override
public void onShown() {
    myTask= new MyTask();
    myTask.execute((Void)null);
}

When can I start the task so that I can be 100% sure that the fragment is attached to the activity and that the view has been created (I need to get listview, etc. from the layout).

Ivan-Mark Debono
  • 15,500
  • 29
  • 132
  • 263

9 Answers9

114

You have to use ChildFragmentManager like below.

@Override
public void onAttach(Activity activity) {
    super.onAttach(activity);
    pagerAdapter = new PagerAdapter(getChildFragmentManager()); //here used child fragment manager 
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.fragmentA, container, false);

    vpPager = (ViewPager)view.findViewById(R.id.vpPager);
    vpPager.setOffscreenPageLimit(2);
    vpPager.setAdapter(pagerAdapter);
    vpPager.addOnPageChangeListener(this);

    return view;
}

It works like charm in my code with viewpager and fragment.

Darshan Mistry
  • 3,294
  • 1
  • 19
  • 29
35

Just now I solved it after struggling for whole day, by using getChildFragmentManager() pass this as a parameter to the pagerAdapter. and it will work.

while using pagerAdapter in fragment use :

PagerAdapter adapter = new PagerAdapter(getChildFragmentManager());

and in case of activity use getFragmentManager()

PagerAdapter adapter = new PagerAdapter(getFragmentManager());
daniel.keresztes
  • 875
  • 2
  • 15
  • 25
Gautam Kumar
  • 1,385
  • 14
  • 17
17

You're creating ListFragment1 and ListFragment2 using the Activity FragmentManager, while you should use the Fragment FragmentManager. So, modify the pagerAdapter = new PagerAdapter(getActivity().getSupportFragmentManager()); with pagerAdapter = new PagerAdapter(getChildFragmentManager());. In this way, the fragments of the view pager will be 'bound' to the fragment hosting the viewpager. Moreover, you should not keep any reference to fragments inside the viewpager: it's something that Android already manage. Try with:

private class PagerAdapter extends FragmentPagerAdapter {

    public PagerAdapter(android.support.v4.app.FragmentManager fm) {
        super(fm);
    }

    @Override
    public android.support.v4.app.Fragment getItem(int position) {
        switch (position) {
            case 0:  return ListFragment1.newInstance();
            case 1:  return ListFragment2.newInstance();
            default: return null;
        }
    }
}

By the way, the vpPager.setOffscreenPageLimit(2); is unuseful since you have just 2 pages and this is a method that I've never used even when I have many fragments to manage, since it requires memory.

About your update: remove any logic related to ViewPager handling the fragment. If you need to start an AsyncTask within your Fragment, you can do it using one of the methods of Fragment lifecycle: onResume(), onCreateView() and so on.

class IPagedFragment extends Fragment {

    public void onResume() {
        super.onResume();
        myTask= new MyTask();
        myTask.execute((Void)null);
    }
}

and please, remove the private final ListFragment1 lf1 = ListFragment1 .newInstance();. Trust me, it's not a good idea since you have a strong reference to your Fragments.

I've built a simple project that you can use as reference implementation. You can download the source code from my dropbox.

Mimmo Grottoli
  • 5,758
  • 2
  • 17
  • 27
  • I start an an async task in the viewpager fragments, and I get an `IllegalStateException: not attached to activity` – Ivan-Mark Debono Jul 28 '15 at 15:52
  • That's another issue. Post your asynctask – Mimmo Grottoli Jul 28 '15 at 16:18
  • I've updated my answer. By the way, the general rule you have to keep in mind is that every class has its own rules and competences. The datas you get from AsyncTask are used to build some view inside the Fragment? Then let the Fragment create the AsyncTask! – Mimmo Grottoli Jul 29 '15 at 07:39
  • I've also added a simple project to my dropbox public area, to show what is the right way to do what you need to implement. – Mimmo Grottoli Jul 29 '15 at 07:58
2

use getChildFragmentManager() instead of supportFragmentManager()

Bayu
  • 43
  • 3
1

If any of the solutions above doesn't work, you can try a workaround by posting (delayed) to the pager view instance an additional notifyDataSetChanged call of the adapter:

vpPager.post(new Runnable() {
    @Override
    public void run() {
        pagerAdapter.notifyDataSetChanged();
    }
});

or

vpPager.postDelayed(new Runnable() {
    @Override
    public void run() {
        pagerAdapter.notifyDataSetChanged();
    }
}, 100 /* you have to find out the best delay time by trying/adjusting */);
Mehmed Mert
  • 935
  • 1
  • 7
  • 24
0

Try overriding the getItemPosition method in your FragmentPagerAdapter:

@Override
public int getItemPosition(Object object) {
    return PagerAdapter.POSITION_NONE;
}
Vadim Kotov
  • 8,084
  • 8
  • 48
  • 62
Linh Nguyen
  • 1,264
  • 1
  • 10
  • 22
0

If you experience this with Kotlin, it will be like this.

val fragmentAdapter = FragmentPageAdapter(childFragmentManager)
Kimanthi K.
  • 595
  • 6
  • 13
-1

You shouldn't keep references to fragments in your FragmentPagerAdapter. You should always call newInstance in getItem() call, for example:

@Override
public android.support.v4.app.Fragment getItem(int position) {
    switch (position) {
        case 0:  return ListFragment1.newInstance();
        case 1:  return ListFragment2.newInstance();
        default: return null;
    }
}

The data you load from the database should be stored in the fragment itself. The adapter will restore the state of fragments (setOffscreenPageLimit(2)).

You are losing your fragments because the items (fragments) are instantiated by the FragmentManager you provide, and it creates fragments based on tags. So it can happen that it creates a new instance of the fragment you already keep, just with different tag.

See FragmentPagerAdapter source code (check instantiateItem() method): https://android.googlesource.com/platform/frameworks/support/+/refs/heads/master/v13/java/android/support/v13/app/FragmentPagerAdapter.java

Also see this answer: keep instances of fragments inside FragmentPagerAdapter

Community
  • 1
  • 1
Ivan Skoric
  • 1,200
  • 8
  • 18
-3
  1. On PagerAdapter class override the method setPrimaryItem, which is called when there's a change in the pager, i would give it a shot.

I would create something like :

 private class PagerAdapter extends FragmentPagerAdapter {
        private final ListFragment1 lf1 = ListFragment1 .newInstance();
        private final ListFragment2 lf2 = ListFragment2 .newInstance();

        public PagerAdapter(android.support.v4.app.FragmentManager fm) {
            super(fm);
        }

        @Override
        public android.support.v4.app.Fragment getItem(int position) {
            switch (position) {
                case 0:  return lf1;
                case 1:  return lf2;
                default: return null;
            }
        }

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

        @Override
        public void setPrimaryItem(ViewGroup container, int position, Object object) {
            super.setPrimaryItem(container, position, object);
            if (position == 0)
                lf1.updateUI(); //Refresh what you need on this fragment
            else if (position == 1)
                lf2.updateUI();
        }

    }
  1. You're missing getCount() as well.
  2. I'm not sure offscreen has any use, but its probably not an issue. vpPager.setOffscreenPageLimit(2)
  3. One more thing, i would also remove vpPager.addOnPageChangeListener(this), there's no use for this, an it might cause you some issues. Whatever you need to do, you can pull it off without it, by overriding the pagination, you might "ruin" some of the standard pagination(since the super isn't called)
Vadim Kotov
  • 8,084
  • 8
  • 48
  • 62
Dus
  • 4,052
  • 5
  • 30
  • 49
  • I'm very sorry that this kind of solution has been accepted. I don't understand why you consider acceptable to keep your Fragment in memory. This is a bad design pattern, since every Fragment requires memory that the system is not able to free when, for example, you would like to switch to a FragmentStatePagerAdapter. – Mimmo Grottoli Jul 31 '15 at 07:38
  • 3
    I was only following his design and pointed out ways to improve it, you could do it as well, without bashing.. – Dus Aug 02 '15 at 07:10