Showing specified page when view pager is first created
Asked Answered
P

1

2

I have a fragment that contains a ViewPager. This ViewPager is backed by a PagerAdapter that uses a Cursor. The cursor is managed by LoaderCallbacks. I'm using the v4 support libraries here.

What I want is to create the fragment and have the view pager showing a specified page, rather than starting at page 0.

I know that ViewPager has a setCurrentItem() method, but the data may not yet be loaded when the ViewPager is created. What I need is to listen to the adapter for data set changes, and if that is the first such change, then call setCurrentItem() on ViewPager.

However the PagerAdapter class does not export the registerDataSetObserver() method; it has package rather than public access (at least in the v4 support library).

What I have done, and it seems very much like a hack to me, is the following:

class ItemPagerFragment extends SherlockFragment implements LoaderCallbacks<Cursor> {

    private CursorPagerAdapter mAdapter;
    private ViewPager mPager;
    private int mInitialPageToShow;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mAdapter = new CursorPagerAdapter() {
            @Override public void notifyDataSetChanged() {
                super.notifyDataSetChanged();
                setInitialPageIfRequired();
            }
        }; 
        getActivity().getSupportLoaderManager().initLoader(LOADER_ID, null, this);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup group, Bundle saved) {
        View view = inflater.inflate(R.layout.items_pager, group, false);
        mPager = (ViewPager) view.findViewById(R.id.pager);
        mPager.setAdapter(mAdapter);
        setInitialPageIfRequired();
        return view;
    }

    private boolean initialPageSet = false;

    private synchronized void setInitialPageIfRequired() {
        // Set the current page of the pager if (a) this is the 
        // first time attempting to set the page and (b) the 
        // pager exists and (c) the adapter has data.
        if (!initialPageSet && mPager != null && mAdapter.getCount() > 0) {
            mPager.setCurrentItem(mInitialPageToShow);
            initialPageSet = true;
        }
    }

}

There's a race condition between loading data into the adapter (in the onCreate() method), and creating the ViewPager in the onCreateView() method. So the current page can be set either when (a) the pager exists but data is loaded for the first time or (b) the data is loaded before the pager is created.

I've tried to handle both cases above but I'm thinking there must be an alternative approach that is more robust (and hopefully simpler) using observers.

Perfectible answered 11/12, 2012 at 6:46 Comment(5)
Can you post the code for CursorPagerAdapter?Vollmer
@Luksprog The CursorPagerAdapter class extends FragmentPagerAdapter and handles getCount() and getItem() using a cursor. It also has a swapCursor() method (cf CursorAdapter class) that is called by the LoaderCallbacks methods (not shown in my code snippets above). I've put the CursorPagerAdapter code on pastebin here: pastebin.com/XTx5HfKSPerfectible
If I would be in your place I would start the Fragments from the ViewPager with what data I have at that moment(if I don't have any just show a blank something, or loading) and in the loader's onLoadFinished callback I would put the data in a field in the master Fragment and let the ViewPager fragment know that data is available for them and that they should update.Vollmer
@Luksprog thanks for the comment. In effect, what you mention is already happening. The onLoadFinished callback calls the adapter's swapCursormethod, which will call notifyDataSetChanged. This is handled in the code snippet above. There's no guarantee that the pager will exist by the time the adapter is notified of the data set change, however.Perfectible
If that is what you are afraid of then why don't you start the loader in the onActivityCreated?Vollmer
B
3

Wait to initLoader until after the pager exists, check the boolean flag and do setCurrentItem only the first time onLoadFinished is called:

class ItemPagerFragment extends Fragment implements LoaderManager.LoaderCallbacks<Cursor> {

    private CursorPagerAdapter mAdapter;
    private ViewPager mPager;
    private int mInitialPageToShow;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }


    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup group, Bundle saved) {
        View view = inflater.inflate(R.layout.items_pager, group, false);
        mPager = (ViewPager) view.findViewById(R.id.pager);
        mAdapter = new CursorPagerAdapter(getFragmentManager());
        mPager.setAdapter(mAdapter);
        return view;
    }

    @Override
    public void onViewCreated(View view, Bundle saved) {
        super.onViewCreated(view, saved);
        ...
        getLoaderManager().initLoader(LOADER_ID, null, this);
    }

    @Override
    public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) {
        mAdapter.swapCursor(cursor);
        setInitialPageIfRequired();
    }

    private boolean initialPageSet = false;

    private synchronized void setInitialPageIfRequired() {
        // Set the current page of the pager if (a) this is the 
        // first time attempting to set the page and (b) the 
        // pager exists and (c) the adapter has data.
        if (!initialPageSet && mPager != null && mAdapter.getCount() > 0) {
            mPager.setCurrentItem(mInitialPageToShow);
            initialPageSet = true;
        }
    }
}

Note: this snippet assumes v13 support library, but it should be the same for v4.

Barozzi answered 11/6, 2014 at 23:15 Comment(1)
What happens on a config change? Would it be better to save/retrieve mInitialPageToShow in the savedInstanceState bundle?Arrangement

© 2022 - 2024 — McMap. All rights reserved.