Book Image

Creating Dynamic UIs with Android Fragments - Second Edition

By : Jim Wilson
Book Image

Creating Dynamic UIs with Android Fragments - Second Edition

By: Jim Wilson

Overview of this book

Today’s users expect mobile apps to be dynamic and highly interactive, with rich navigation features. These same apps must look fantastic whether running on a medium-resolution smartphone or high-resolution tablet. Fragments provide the toolset we need to meet these user expectations by enabling us to build our applications out of adaptable components that take advantage of the rich capabilities of each individual device and automatically adapt to their differences. This book looks at the impact fragments have on Android UI design and their role in both simplifying many common UI challenges and in providing best practices for incorporating rich UI behaviors. We look closely at the roll of fragment transactions and how to work with the Android back stack. Leveraging this understanding, we explore several specialized fragment-related classes such as ListFragment and DialogFragment. We then go on to discuss how to implement rich navigation features such as swipe-based screen browsing, and the role of fragments when developing applications that take advantage of the latest aspects of Material Design. You will learn everything you need to provide dynamic, multi-screen UIs within a single activity, and the rich UI features demanded by today’s mobile users.
Table of Contents (13 chapters)

The need for a new approach to UI creation


Chances are that the first class you learned to use when you became an Android developer was the Activity class. After all, the Activity class provided your app with a user interface. By organizing your user interface components on an activity, the activity became the canvas on which you were painting your application masterpiece.

In the early days of Android, building an application's user interface directly within an activity worked reasonably well. A majority of early applications had a relatively simple user interface, and the number of different Android device form factors was small. In most cases, with the help of a few layout resources, a single activity worked fine across different device form factors.

Today, Android devices come in a wide variety of form factors with incredible variation in their sizes and shapes. When you combine this with the highly interactive user interfaces of modern Android applications, the creation of a single activity that effectively manages the user interface across such divergent form factors becomes extremely difficult.

A possible solution is to define one activity to provide the user experience for a subset of device form factors—for example, smartphones. Then, we can define another activity for a different subset of form factors, such as tablets. The problem with this approach is that activities tend to have a lot of responsibilities beyond simply rendering the user interface. With multiple activities performing essentially the same tasks, we must either duplicate the logic within each of the activities or increase the complexity of our program by finding ways to share the logic across activities, such as creating potentially complex inheritance relationships. The approach of using different activities for different form factors also substantially increases the number of activities in the program, easily doubling or tripling the number of activities required. In addition, the advent of Google's material design specification further increases the complexity of the code contained within each activity.

We need a better solution, one that allows us to modularize our application's user interface into sections that we can arrange as needed within an activity; fragments are the solution.

Android fragments allow us to partition the user interface into functional groupings of user interface components and logic. An activity can load and arrange the fragments as needed for a given device form factor. The fragments take care of the form factor details, while the activity manages the overall user interface issues. Fragments can also play an important role in grouping user interface components in ways that simplify the application of material design. We'll take a look at the role of fragments in material design in Chapter 6, Fragments and Material Design.

The broad platform support of fragments

The Fragment class was added to Android at API Level 11 (Android 3.0). This was the first version of Android that officially supported tablets. The addition of tablet support exacerbated an already difficult problem; developing Android applications was becoming increasingly difficult because of the wide variety of Android device form factors.

Fortunately, fragments provide a solution to this problem. With fragments, we can much more easily create applications that support a variety of form factors because we can partition our user interfaces into effective groupings of components and their associated logic.

As of the writing of this book, over 95% of Android phones in use support fragments natively. If you happen to be working on a project where you're required to support the less than 5% of devices that do not support fragments natively—those devices with an API level below 11—you can still take advantage of fragments through v4 of the Android Support Library. The details of working with the Fragment class in v4 of Android Support Library are outside the scope of this book; however, you can find information on working with the Fragment class in v4 of the Android Support Library at http://developer.android.com/tools/support-library/index.html.

How fragments simplify common Android tasks

Fragments not only simplify the way we create our application user interfaces, but also simplify many of the built-in Android user interface tasks. User interface concepts such as tabbed displays, list displays, and dialog boxes have all historically had distinctly different approaches even though they are each variations on a common concept. Each is a way of combining user interface components and logic into a functional group. Fragments formalize this concept and therefore allow us to take a consistent approach to these formerly disparate tasks. We will talk about each of these issues in detail as well as some of the specialized fragment classes, such as DialogFragment and ListFragment, later in this book.

The relationship between fragments and activities

Fragments do not replace activities but rather supplement them. A fragment always exists within an activity. An activity instance can contain any number of fragments, but a given fragment instance can only exist within a single activity. A fragment is closely tied to the activity on which it exists, and the lifetime of this fragment is tightly coupled with the lifetime of the containing activity. We'll talk much more about the close relationship between the lifetime of a fragment and the containing activity in Chapter 3, Fragment Life Cycle and Specialization.

One thing we don't want to do is make the common mistake of overusing fragments. Often when someone learns about fragments, they make the assumption that every activity must contain fragments; this is simply not the case.

As we go through this book, we'll discuss the features and capabilities of fragments and a variety of scenarios in which they work well. We always want to keep these in mind as we build our applications. In those situations where fragments add value, we definitely want to use them. However, it is equally important that we avoid complicating our applications by using fragments in cases where they do not provide any value.

Making the shift to fragments

Although fragments are a very powerful tool, they do something very simple fundamentally. Fragments group user interface components and their associated logic. Creating the portion of your user interface associated with a fragment is very much like doing so for an activity. In most cases, the view hierarchy for a particular fragment is created from a layout resource; although, just as with activities, the view hierarchy can be programmatically generated.

Creating a layout resource for a fragment follows the same rules and techniques as doing so for an activity. The key difference is that we're looking for opportunities to partition our user interface layout into manageable subsections when working with fragments.

The easiest way to get started working with fragments is for us to walk through converting a traditional activity-oriented user interface to use fragments.

The old thinking – activity-oriented

To get started, let's first look at the appearance and structure of the application we will convert. This application contains a single activity that, when run, looks similar to the following screenshot:

The activity displays a list of five book titles in the upper portion of the activity. When the user selects one of these books title, the description of this book appears in the lower portion of the activity.

Defining the activity appearance

The appearance of an activity is defined in a layout resource file named activity_main.xml that contains the following layout description:

<LinearLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:orientation="vertical"
  android:layout_width="match_parent"
  android:layout_height="match_parent">

  <!-- List of Book Titles -->
  <ScrollView
    android:layout_width="match_parent"
    android:layout_height="0dp"
    android:id="@+id/scrollTitles"
    android:layout_weight="1">
    <RadioGroup
      android:id="@+id/bookSelectGroup"
      android:layout_height="wrap_content"
      android:layout_width="wrap_content">
      <RadioButton
        android:id="@+id/dynamicUiBook"
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        android:text="@string/dynamicUiTitle"
        android:checked="true" />
      <RadioButton
        android:id="@+id/android4NewBook"
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        android:text="@string/android4NewTitle" />

      <!-- Other RadioButtons elided for clarify -->
    </RadioGroup>
  </ScrollView>

  <!-- Description of selected book -->
  <ScrollView
    android:layout_width="match_parent"
    android:layout_height="0dp"
    android:id="@+id/scrollDescription"
    android:layout_weight="1">
    <TextView
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:textAppearance="?android:attr/textAppearanceMedium"
      android:text="@string/dynamicUiDescription"
      android:id="@+id/textView"
      android:paddingLeft="@dimen/activity_horizontal_margin"
      android:paddingRight="@dimen/activity_horizontal_margin"
      android:gravity="fill_horizontal"/>
  </ScrollView>
</LinearLayout>

Tip

You can download the example code files for this book from your account at http://www.packtpub.com. If you purchased this book elsewhere, you can visit http://www.packtpub.com/support and register to have the files e-mailed directly to you.

You can download the code files by following these steps:

  • Log in or register to our website using your e-mail address and password.

  • Hover the mouse pointer on the SUPPORT tab at the top.

  • Click on Code Downloads & Errata.

  • Enter the name of the book in the Search box.

  • Select the book for which you're looking to download the code files.

  • Choose from the drop-down menu where you purchased this book from.

  • Click on Code Download.

Once the file is downloaded, please make sure that you unzip or extract the folder using the latest version of:

  • WinRAR / 7-Zip for Windows

  • Zipeg / iZip / UnRarX for Mac

  • 7-Zip / PeaZip for Linux

This layout resource is reasonably simple and is explained as follows:

  • The overall layout is defined within a vertically-oriented LinearLayout element containing two ScrollView elements

  • Both of the ScrollView elements have a layout_weight value of 1 that causes the top-level LinearLayout element to divide the screen equally between the two ScrollView elements

  • The top ScrollView element with the id value of scrollTitles wraps a RadioGroup element containing a series of the RadioButton elements, one for each book

  • The bottom ScrollView element with the id value of scrollDescription contains a TextView element that displays the selected book's description

Displaying the activity UI

The application's activity class is MainActivity. To display the activity's user interface, we will override the onCreate method and call the setContentView method, passing the R.layout.activity_main layout resource ID via the following code:

protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  // load the activity_main layout resource
  setContentView(R.layout.activity_main);
}

The new thinking: fragment-oriented

The activity-oriented user interface we currently have would be fine if all Android devices had the same form factor. As we've discussed, this is not the case.

We need to partition the application user interface so that we can switch to a fragment-oriented approach. With proper partitioning, we can be ready to make some simple enhancements to our application to help it adapt to device differences.

Let's look at some simple changes we can make that will partition our user interface.

Creating the fragment layout resources

The first step in moving to a fragment-oriented user interface is to identify the natural partitions in the existing user interface. In the case of this application, the natural partitions are reasonably easy to identify. The list of book titles is one good candidate, and the book description is the other. We'll make each of them a separate fragment.

Defining the layout as a reusable list

For the list of book titles, we have the option of defining the fragment to contain either the ScrollView element that's nearest to the top (which has an id value of scrollTitles) or just the RadioGroup element within this ScrollView element. When creating a fragment, we want to structure it in such a way that the fragment is most easily reused. Although the RadioGroup element is all we need to display the list of titles, it seems likely that we'll always want the user to be able to scroll the list of titles if necessary. With this being the case, it makes sense to include the ScrollView element in this fragment.

Note

If you're using Android Studio, you can use the New Fragment menu option to create the fragment class and layout resource in a single step by selecting the Create layout XML checkbox on the New Android Activity dialog.

For now, you want to uncheck the New Android Activity dialog's Include fragment factory methods and Include interface callbacks checkboxes. Unchecking these checkboxes will significantly simplify the code generated.

We'll talk about these and many other fragment-related features of Android Studio in detail throughout the rest of this book.

To create a fragment for the book list, we will define a new layout resource file called fragment_book_list.xml. We will copy the top ScrollView element and its contents from the activity_main.xml resource file to the fragment_book_list.xml resource file. The resulting fragment_book_list.xml resource file is as follows:

<!-- List of Book Titles -->
<ScrollView
  android:layout_width="match_parent"
  android:layout_height="0dp"
  android:id="@+id/scrollTitles"
  android:layout_weight="1">
  <RadioGroup
    android:id="@+id/bookSelectGroup "
    android:layout_height="wrap_content"
    android:layout_width="wrap_content">
  <RadioButton
    android:id="@+id/dynamicUiBook"
    android:layout_height="wrap_content"
    android:layout_width="wrap_content"
    android:text="@string/dynamicUiTitle"
    android:checked="true"/>
  <RadioButton
    android:id="@+id/android4NewBook"
    android:layout_height="wrap_content"
    android:layout_width="wrap_content"
    android:text="@string/android4NewTitle"/>

    <!-- Other RadioButtons elided for clarify -->
  </RadioGroup>
</ScrollView>

This gives us a layout resource consistent with the book title portion of the user interface as it appeared in the activity layout resource. This is a good start.

Minimizing assumptions

An effective fragment-oriented user interface is constructed with layout resources that minimize assumptions about where and how the fragment is used. The fewer assumptions we make about a fragment's use, the more reusable the fragment becomes.

The layout in the fragment_book_list.xml resource file as we now have it is very limiting because it includes significant assumptions. For example, the root ScrollView element includes a layout_height attribute with a value of 0. This assumes that the fragment will be placed within a layout that calculates the height of the fragment.

A layout_height attribute value of 0 prevents the ScrollView element from properly rendering when we use the fragment within any of the many layouts that require the ScrollView element to specify a meaningful height. A layout_height attribute value of 0 prevents the fragment from properly rendering even when doing something as simple as placing the fragment within a horizontally oriented LinearLayout element. The layout_weight attribute has similar issues.

In general, a good practice is to design the fragment to fully occupy whatever space it is placed within. This gives the layout in which the fragment has the most control over the placement and sizing of the fragment.

To do this, we'll remove the layout_weight attribute from the ScrollView element and change the layout_height attribute value to match_parent. As the ScrollView element is now the root node of the layout resource, we also need to add the android namespace prefix declaration.

The following code snippet shows the updated ScrollView element:

<ScrollView
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:id="@+id/scrollTitles">
  <!—RadioGroup and RadioButton elements elided for clarity -->
</ScrollView>

With the updated ScrollView element, the fragment layout can now adapt to almost any layout it's referenced within.

Encapsulating the display layout

For the book description, we'll define a layout resource file called fragment_book_desc.xml. The fragment layout includes the contents of the activity layout resource's bottom ScrollView element (which has an id value of scrollDescription). Just as in the book list fragment, we'll remove the layout_weight attribute, set the layout_height attribute to match_parent, and add the android namespace prefix declaration.

The fragment_book_desc.xml layout resource file appears as follows:

<!-- Description of selected book -->
<ScrollView
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:id="@+id/scrollDescription">
  <TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:textAppearance="?android:attr/textAppearanceMedium"
    android:text="@string/dynamicUiDescription"
    android:id="@+id/textView"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:gravity="fill_horizontal"/>
</ScrollView>

Creating the Fragment class

Similar to when creating an activity, we need more than a simple layout definition for our fragment; we also need a class.

Wrapping the list in a fragment

All fragment classes must extend the android.app.Fragment class either directly or indirectly.

We'll call the class for the fragment that manages the book list—that is, BookListFragment. The class will directly extend the Fragment class as follows:

Import android.app.Fragment;
public class BookListFragment extends Fragment { … }

During the creation of a fragment, the Android framework calls a number of methods on this fragment. One of the most important of these is the onCreateView method. The onCreateView method is responsible for returning the view hierarchy represented by the fragment. The Android framework attaches this returned view hierarchy for the fragment to the appropriate place in the activity's overall view hierarchy.

In a case like the BookListFragment class where the Fragment class inherits directly from the Fragment class, we must override the onCreateView method and perform the work necessary to construct the view hierarchy.

The onCreateView method receives three parameters. We'll focus on just the first two for now:

  • inflater: This is a reference to a LayoutInflater instance that can read and expand layout resources within the context of the containing activity

  • container: This is a reference to the ViewGroup instance within the activity's layout where the fragment's view hierarchy is to be attached

The LayoutInflater class provides a method called inflate that handles the details of converting a layout resource into the corresponding view hierarchy and returns a reference to the root view of this hierarchy. Using the LayoutInflater.inflate method, we can implement our BookListFragment class' onCreateView method to construct and return the view hierarchy corresponding to the R.layout.fragment_book_list layout resource, as shown in the following code:

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
  View viewHierarchy =
  inflater.inflate(R.layout.fragment_book_list,
    container, false);
  return viewHierarchy;
}

You'll notice in the preceding code we include the container reference and a Boolean value of false in the call to the inflate method. The container reference provides the necessary layout parameters for the inflate method to properly format the new view hierarchy. The parameter value of false indicates that container is to be used only for the layout parameters. If this value were true, the inflate method would also attach the new view hierarchy to the container view group. We do not want to attach the new view hierarchy to the container view group in the onCreateView method because the activity will handle that.

Providing the description fragment

For the book description fragment, we'll define a class called BookDescFragment. This class is identical to the BookListFragment class, except that the BookDescFragment class uses the R.layout.fragment_book_desc layout resource as follows:

public class BookDescFragment extends Fragment {
  @Override
  public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View viewHierarchy = inflater.inflate(R.layout.fragment_book_desc, container, false);
    return viewHierarchy;
  }
}

Converting an activity to use fragments

With the fragments defined, we can now update the activity to use them. To get started, we'll remove all the book titles and description layout information from the activity_main.xml layout resource file. The file now contains just the top-level LinearLayout element and comments to show where the book titles and description belong. The code is given as follows:

<LinearLayout
  android:orientation="vertical"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  xmlns:android="http://schemas.android.com/apk/res/android">

  <!--  List of Book Titles  -->

  <!--  Description of selected book  -->

</LinearLayout>

Using the fragment element, we can add a fragment to the layout by referencing the fragment's class name with the name attribute. For example, we will reference the book list fragment's class, BookListFragment, as follows:

<fragment
  android:name="com.jwhh.fragments.BookListFragment"
  android:id="@+id/fragmentTitles"/>

We want our activity user interface to appear the same, using fragments as it did before we converted it to use fragments. To do this, we will add the same layout_width, layout_height, and layout_weight attribute values to the fragment elements as were on the ScrollView elements in the original layout.

With this, the complete layout resource file for the activity, activity_main.xml, now looks similar to the following code:

<LinearLayout
  android:orientation="vertical"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  xmlns:android="http://schemas.android.com/apk/res/android">

  <!-- List of Book Titles -->
  <fragment
    android:layout_width="match_parent"
    android:layout_height="0dp"
    android:layout_weight="1"
    android:name="com.jwhh.fragments.BookListFragment"
    android:id="@+id/fragmentTitles"/>

  <!-- Description of selected book -->
  <fragment
    android:layout_width="match_parent"
    android:layout_height="0dp"
    android:layout_weight="1"
    android:name="com.jwhh.fragments.BookDescFragment"
    android:id="@+id/fragmentDescription"/>
</LinearLayout>

Note

If you are working with Android Studio, you might find a tools:layout attribute on the fragment element. This attribute is used by Android Studio to provide a preview of the layout within the graphical designer. It has no effect on your application's appearance when the application is run.

When the application is run, the user interface appears exactly as it did when it was defined entirely within the activity.