Ivan-Mark Debono
Ivan-Mark Debono

Reputation: 16280

Viewpager fragments not shown after the first time

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).

Upvotes: 33

Views: 25718

Answers (9)

Kevin Kyei
Kevin Kyei

Reputation: 585

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

val fragmentAdapter = FragmentPageAdapter(childFragmentManager)

Upvotes: 0

Bayu
Bayu

Reputation: 43

use getChildFragmentManager() instead of supportFragmentManager()

Upvotes: 2

Gautam Kumar
Gautam Kumar

Reputation: 1463

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());

Upvotes: 35

Mehmed Mert
Mehmed Mert

Reputation: 1103

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 */);

Upvotes: 1

Linh Nguyen
Linh Nguyen

Reputation: 1264

Try overriding the getItemPosition method in your FragmentPagerAdapter:

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

Upvotes: 0

Dus
Dus

Reputation: 4230

  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)

Upvotes: -3

Darshan Mistry
Darshan Mistry

Reputation: 3372

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.

Upvotes: 113

Mimmo Grottoli
Mimmo Grottoli

Reputation: 5773

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.

Upvotes: 17

Ivan Skoric
Ivan Skoric

Reputation: 1210

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

Upvotes: -1

Related Questions