Android ViewPager with Fragments: Tutorial and Sample Project

Posted by admin at 10:30 AM on Oct 19, 2016

Share:


This is a common paradigm in app development: I have a list of data objects representing, for example, recent shipments, and I want to display them in gallery format, so that I can swipe between them. Fortunately, Google anticipated the need for this paradigm and provided for us the tools to easily create the associated UI.

That class is ViewPager, and its associated data manager, PagerAdapter. If you have worked with Android ListViews before, you have seen something along the same lines as PagerAdapter: in Android terms, an Adapter is a class which translates data and turns it into UI elements. While an Adapter for a class such as ListView operates on Views, PagerAdapter’s concrete implementations operate on that much-maligned piece of Android UI, the Fragment. (Generally speaking, I am less harsh on Fragments than most of my colleagues in the Android development world, but that is a topic for another day.) FragmentPagerAdapter, the concrete implementation we will use today, generates Fragments for a list of data objects. We will run through the project now, taking note of some interesting features. You can download the source code here.

First: layout/activity_main.xml:

<android.support.v4.view.ViewPager
   android:id="@+id/pager"
   android:layout_width="match_parent"
   android:layout_height="match_parent">


We have defined a ViewPager to occupy the full content area. As a view, ViewPager may be placed anywhere in the view hierarchy in an activity.

Second: MainActivity.java. Note that it extends FragmentActivity, a necessity for an activity hosting a ViewPager and a FragmentPagerAdapter. We have a few things to cover here. First, our adapter:

private class DemoFragmentAdapter extends FragmentPagerAdapter {
   public DemoFragmentAdapter(FragmentManager fm) {
       super(fm); // super tracks this
   }

   @Override
   public Fragment getItem(int position) {
       return DemoFragment.newInstance(mDemoData.get(position));
   }

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


We will cover DemoFragment and DemoData in a bit: DemoData in particular is interesting, since it is an example of an implementation of Parcelable, a useful interface which allows an object to be inserted into a Bundle (in this case, as an argument to DemoFragment). The FragmentPagerAdapter class which we are extending handles all of the bookkeeping for us: it caches Fragments and tracks when they need to be replaced if the backing data has changed. All we need to do is provide the size of the data store, and implement a method to create a Fragment for each option.

Next, we will take a quick look at some of the code in MainActivity’s onCreate method.

mAdapter = new DemoFragmentAdapter(getSupportFragmentManager());
mPager = (ViewPager) findViewById(R.id.pager);

mPager.setAdapter(mAdapter);

mDemoData.add(new DemoData("Item1", "First item", 1.5));
mDemoData.add(new DemoData("Item2", "Second item", 2.5));
mDemoData.add(new DemoData("Item3", "Third item", 5.2));
mDemoData.add(new DemoData("Item4", "Fourth item", 5.1));
mDemoData.add(new DemoData("Item5", "Fifth item", 3.8));
mAdapter.notifyDataSetChanged();


Simple enough: create a DemoFragmentAdapter, attach it to the pager, and add some demo data. The last line, the call to the notifyDataSetChanged method, is the only potential stumbling block here: whenever the backing data changes, you must call mAdapter.notifyDataSetChanged(). If the ViewPager sees that the backing data has changed and that the adapter has not been notified, it will throw runtime exceptions.

DemoFragment is mostly straightforward, but the calls to OnFragmentInteractionListener.onFragmentResumed and .onFragmentCreated show a simple pattern for alerting the host activity to changes in the displayed data, if the host activity needs to respond to such changes.

Finally, DemoData:

public class DemoData implements Parcelable  {
   public final String name;
   public final String desc;
   public final double weight;

   public DemoData(String name, String desc, double weight) {
       this.name = name;
       this.desc = desc;
       this.weight = weight;
   }

   protected DemoData(Parcel in) {
       name = in.readString();
       desc = in.readString();
       weight = in.readDouble();
   }

   public static final Creator<DemoData> CREATOR = new Creator<DemoData>() {
       @Override
       public DemoData createFromParcel(Parcel in) {
           return new DemoData(in);
       }

       @Override
       public DemoData[] newArray(int size) {
           return new DemoData[size];
       }
   };

   @Override
   public int describeContents() {
       return 0;
   }

   @Override
   public void writeToParcel(Parcel parcel, int i) {
       parcel.writeString(name);
       parcel.writeString(desc);
       parcel.writeDouble(weight);
   }
}


The bulk of this class is Parcelable boilerplate, which Android Studio will generate for you as an autofix action if you have left Parcelable methods unimplemented. You need four items: first, one to construct an object from a Parcel, which sets properties on the object. Parcelables have no concept of indexing or key-value pairing: you read objects from the Parcel in the same order as you write them.

Second, you need a Creator<YourType>. This is used internally by Parcelable, and this implementation is the canonical form. You never need anything further in the Creator.

Third, you need the describeContents method, which returns 0 in all common use cases. (See Google’s documentation for more.)

Finally, you need the writeToParcel method, which writes primitives and Parcelables to the Parcel for this object. Notice that, since you can write Parcelables and arrays of Parcelables to a Parcel, it is possible (if not always trivial) to write complicated objects to a Parcel. You can also deliver data to the fragment using a method on the fragment callback, if parceling your data is infeasible. For instance, this might take the shape of storing a database ID in the Parcel, then making a database query or web API call to obtain the data required to fully populate the fragment.

This concludes our exploration of the ViewPager and the techniques required to use fragments to back it. As you can see, it is a powerful tool to develop a seamlessly-paginated view on collections of like data.


Loading Conversation