Creating Dynamic UIs With Android Fragments - Second Edition - Sample Chapter
Creating Dynamic UIs With Android Fragments - Second Edition - Sample Chapter
programming
and
want
to
improve
the
P U B L I S H I N G
Jim Wilson
ee
Sa
m
pl
C o m m u n i t y
E x p e r i e n c e
D i s t i l l e d
$ 29.99 US
19.99 UK
Fr
Jim Wilson
in solutions for the Android, iOS, and Microsoft platforms. He has over 30 years of
software engineering experience, with the past 15 years heavily focused on creating
mobile device and location-based solutions. Jim cofounded multiple software-related
startups and has served in a consulting role at several more. After nearly a decade as
a Microsoft Device Application Development MVP, he now focuses on developing
Android and iOS device applications.
Preface
Long gone are the days of mobile apps with a static UI squished on a tiny screen.
Today's users expect mobile apps to be dynamic and highly interactive. They expect
an app to look fantastic when they look at it on their medium resolution smartphone
and just as fantastic when they switch over to using it on their high-resolution tablet.
Apps need to provide rich navigation features, be adaptive, and be responsive.
Trying to meet these demands using Android's traditional activity-centric UI design
model is difficult at best. As developers, we need more control than that afforded by
activities. We need a new approach, and fragments give us this new approach.
In this book, you'll learn how to use fragments to meet the challenges of creating
dynamic UIs in the modern world of mobile app development.
Preface
Chapter 3, Fragment Life Cycle and Specialization, discusses the relationship of the
life cycle of fragments with that of activities and demonstrates the appropriate
programming actions at the various points in the life cycle. Leveraging this knowledge,
the special purpose fragment classes, ListFragment and DialogFragment, are
introduced to demonstrate their behavior and provide a deeper understanding of
how their behavior in the activity life cycle differs from that of standard fragments.
Chapter 4, Working with Fragment Transactions, explains how to create multiple app
screens within a single activity by dynamically adding and removing fragments
using fragment transactions. Topics covered include thread handling, implementing
back button behavior, and dynamically adapting multifragment UIs to differences in
device characteristics.
Chapter 5, Creating Rich Navigation, brings everything together by building on the
previous chapters to show how to use fragments to enhance the user's experience
through rich navigation features. This chapter demonstrates how to implement a
number of navigation features, including screen browsing with swipe-based paging,
direct screen access with drop-down list navigation, and random screen viewing
with tabs.
Chapter 6, Fragments and Material Design, introduces the next generation of
application development using material design. This chapter demonstrates how
to implement fragments that incorporate a rich visual appearance and animated
transitions using the latest features of Android's material design capabilities.
By the end of this chapter, we will be able to coordinate the setup and teardown of
fragments within their host activities and effectively utilize the ListFragment and
DialogFragment classes.
[ 39 ]
[ 40 ]
Chapter 3
The following figure shows the sequence of life cycle-related callback method calls
that occur on fragments and activities during setup and display:
As you might expect in most cases, the first step in the setup and display of a
fragment occurs in the activity's onCreate method. In most cases, the activity calls
the setContentView method from within the activity's onCreate callback method,
which then loads the layout resource and triggers the activity's association with the
contained fragments.
Note what happens next. Before the fragment is even created, it is attached to the
activity. The fragment is first notified of the attachment and receives a reference to
the activity through the onAttach callback method. The activity is then notified and
receives a reference to the fragment through the onAttachFragment callback method.
Although attaching the fragment to the activity prior to creating the fragment may
seem unexpected, doing so is useful. In many cases, the fragment needs access to the
activity during the creation process because the activity often contains information
that the fragment will display or that is otherwise important to the fragment's
creation process.
Attached to the activity, the fragment then performs general creation work in
the onCreate method and then constructs the contained view hierarchy in the
onCreateView method. We'll talk more about which actions are appropriate to perform
in each method in the Maximizing the available resources section later in this chapter.
[ 41 ]
When an activity contains multiple fragments, Android calls the four methods
Fragment.onAttach, Activity.onAttachFragment, Fragment.onCreate, and
Fragment.onCreateView in succession for one fragment before making any calls
to these methods for the next fragment. This allows each fragment to complete the
process of attachment and creation before the next fragment begins this process.
Once the sequence of calling these four methods is complete for all the fragments, the
remaining setup and display callback methods are called individually in succession
for each fragment.
After the activity completes the execution of its onCreate method, Android then
calls each fragment's onActivityCreated method. The onActivityCreated method
indicates that all views and fragments created by the activity's layout resource are
now fully constructed and can be safely accessed.
At this point, the fragment receives the standard life cycle callbacks on the onStart
and onResume methods just after each of the activity methods of the same name is
called. Any work performed in the fragment's onStart and onResume methods is
very much like the work performed in the corresponding methods within an activity.
For many fragments, the only methods in this part of their life cycle that are
overridden are the onCreate and onCreateView methods, as we noted in the
examples in the previous chapters.
[ 42 ]
Chapter 3
Initially, during hide and teardown, fragments behave just as activities. When the
user switches to another activity, each fragment's onPause, onSaveInstanceState,
and onStop methods are called. For each method, the fragment implementation is
called first, followed by the activity implementation.
After the onStop method is called, fragments begin to behave a little differently than
activities. Consistent with the separation of fragment creation from fragment view
hierarchy creation, fragment view hierarchy destruction is separate from fragment
destruction. Following the call to the activity's onStop method, the fragment's
onDestroyView method is called, indicating that the view hierarchy returned by the
fragment's onCreateView method is being destroyed. The fragment's onDestroy
method is then called, followed by the fragment's onDetach method. At this point,
the fragment has no association with an activity and any calls to the getActivity
method will return null.
[ 43 ]
For activities containing multiple fragments, Android calls the sequence of the
three methods onDestroyView, onDestroy, and onDetach for an individual
fragment before beginning the sequence of calling these three methods for the next
fragment. This groups the process of destroying and detaching each fragment similar
to the way Android groups the process of attaching and creating each fragment.
Once this sequence is completed for all fragments, Android calls the activity's
onDestroy method.
[ 44 ]
Chapter 3
When managing the state of a fragment, you want to be sure to separate work that
is general to the fragment's overall existence from being work-specific to setting up
the view hierarchy. Any expensive initialization work that's general to the fragment's
existence, such as connecting to a data source, complex calculations, or resource
allocations, should occur in the onCreate method rather than the onCreateView
method. This way, if only the fragment's view hierarchy is destroyed and the fragment
remains intact, you avoid unnecessarily repeating expensive initialization work.
ListFragment
One of the simplest fragment derived classes to use, and yet one of the most helpful,
is the ListFragment class. The ListFragment class provides a fragment that
encapsulates ListView and, as the name implies, is useful for displaying lists of data.
[ 45 ]
The ListFragment class wraps an instance of the ListView class, which is accessible
through the getListView method. In most scenarios, we can feel free to interact
with the contained ListView instance directly and take advantage of any features
offered by the ListView class. The one very important exception is when we set
the ListAdapter instance. Both the ListFragment and ListView classes expose
a setListAdapter method, but we must be sure to use the ListFragment version
of the method.
The ListFragment class relies on certain initialization behaviors that occur within
the ListFragment.setListAdapter method; therefore, the process of calling the
setListAdapter method directly on the contained ListView instance bypasses
this initialization behavior and may cause the application to become unstable.
[ 46 ]
Chapter 3
Within the resources root element of the course_arrays.xml resource file, add a
string-array element that includes a name attribute with a value of bookTitles.
Within the string-array element, add an item for each book title that references
the string resource for each title. We want to be sure that we list the book title array
entries in the same order as the book_descriptions array entries because we use
the array index as the ID value for each book when we notify the activity of the user's
book selection. The array resource entries for the book title and description arrays
appear as follows:
<resources>
<!-- Book Titles -->
<string-array name="book_titles">
<item>@string/dynamicUiTitle</item>
<item>@string/android4NewTitle</item>
<item>@string/androidSysDevTitle</item>
<item>@string/androidEngineTitle</item>
<item>@string/androidDbProgTitle</item>
</string-array>
<!-- Book Descriptions -->
<string-array name="book_descriptions">
<item>@string/dynamicUiDescription</item>
<item>@string/android4NewDescription</item>
<item>@string/androidSysDevDescription</item>
<item>@string/androidEngineDescription</item>
<item>@string/androidDbProgDescription</item>
</string-array>
</resources>
With the titles stored as an array resource, we can now easily create a ListFragment
derived class to display the book titles.
[ 47 ]
To create the BookListFragment2 class, we first need to open the New Android
Activity dialog by performing the following steps:
1. Select the Android Studio File menu.
2. Then, select New.
3. Select Fragment.
4. Select Fragment (List).
Now, we will perform the following steps within the New Android Activity dialog:
1. In the Object Kind: field, enter String.
2. In the Fragment class name: field, enter BookListFragment2.
3. Then, unselect the Include fragment factory methods? checkbox.
4. Unselect the Switch to grid view on large screens? checkbox.
The New Android Activity dialog should now look similar to the following
screenshot:
[ 48 ]
Chapter 3
Click on the Finish button to complete the creation of the BookListFragment2 class.
The generated class has the onCreate method stubbed to populate the list with
dummy data. To load in the list of book titles, update the onCreate method, as
shown in the following code:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// TODO: Change Adapter to display your content
String[] bookTitles =
getResources().getStringArray(R.array.book_titles);
setListAdapter(new ArrayAdapter<String>(getActivity(),
android.R.layout.simple_list_item_1,
android.R.id.text1, bookTitles));
}
In the onCreate method, we will first call the base class implementation that is
required by all classes that extend ListFragment. We will then load the bookTitles
array resource. We will call the setListAdapter method by passing an instance
of the ArrayAdapter. The array adapter takes the context as the first parameter,
which we will get by accessing the activity, and then it takes the array as the
third parameter. The second parameter is the ID of the resource used to lay out
each entry in the list. This resource can be a custom resource or one of the built-in
Android resources. In our case, we will use the built-in Android layout resource
android.R.layout.simple_list_item_1, which displays a single string value
for each row within ListView.
Creating a custom layout resource for the ListFragment class is just like
doing so for the ListView class and is discussed in detail in the Android
developer documentation at http://developer.android.com/
reference/android/app/ListFragment.html.
[ 49 ]
With the mListener field, we are able to store a reference to the containing
activity as an OnSelectedBookChangeListener interface reference. The
generated BookListFragment2 class code sets the mListener reference in
the onAttach callback method. As we discussed earlier in this chapter, the
onAttach method is called when the fragment instance is attached to the
containing activity and receives a reference to this activity. Update the onAttach
method to use the OnSelectedBookChangeListener interface rather than the
OnFragmentInteractionListener interface so that the method now appears as
shown in the following code:
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mListener = (OnSelectedBookChangeListener) activity;
}
catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement OnSelectedBookChangeListener");
}
}
[ 50 ]
Chapter 3
The onAttach method simply assigns the activity to the mListener field casting the
activity to the OnSelectedBookChangeListener interface. The method also includes
a try\catch block to display an appropriate error message if the containing activity
does not implement the OnSelectedBookChangeListener interface.
The generated BookListFragment2 class includes an onListItemClick method that
is called when the user makes a selection from the list and receives several selectionrelated parameters, including the zero-based position of the user selection. Update
the onListItemClick method to use the OnSelectedBookChangeListener interface
so that the method appears as shown in the following code:
public void onListItemClick(ListView l, View v,
int position, long id) {
super.onListItemClick(l, v, position, id);
if (null != mListener) {
mListener.onSelectedBookChanged(position);
}
}
After calling the onListItemClick method on the base class, the preceding code
verifies that the mListener field is set. If it has, the onSelectedBookChanged
method is called, passing the position of the user selection. This code will now
inform the activity each time the user makes a selection from the list, just as the
BookListFragment class implementation did when the user selected a radio button.
All the activity classes in our application that use our BookListFragment2 class
already implement the OnSelectionChangeListener interface, so there is no
change required to the activity classes.
[ 51 ]
Any changes that we need to make to the titles can now all be made in the resources
file and require no changes to the user interface code.
[ 52 ]
Chapter 3
DialogFragment
Up until now, we've been looking at fragments as a new way to divide our
application's user interface into subsections of the available display area. Although
fragments are new, the concept of having an aspect of our application user interface
as a subsection of the available display area is not new. Whenever an application
displays a dialog, the application does exactly this.
Historically, the challenge of working with dialogs is that, even though they are
conceptually just another window within an application, we must handle many
of the tasks related to dialogs differently than other aspects of our application
user interface. Doing something as simple as handling a button click requires the
dialog-specific DialogInterface.OnClickListener interface, rather than the
View.OnClickListener interface that we use when handling a click event from
non-dialog-related parts of our user interface code. An even more complicated
issue is that of orientation changes. Dialogs automatically close in response to an
orientation change and therefore can create inconsistent application behavior if a
user changes device orientation while a dialog is visible.
The DialogFragment class eliminates much of the special handling related
to dialogs. With the DialogFragment class, displaying and managing a dialog
becomes much more consistent with other aspects of our application user interface.
Styles
When an application displays an instance of the DialogFragment class, the window
for the DialogFragment instance has up to three parts to it: the layout area, title,
and frame. A DialogFragment instance always contains the layout area, but we can
control whether it includes the title and frame by setting the DialogFragment class'
style using the setStyle method. The DialogFragment class supports four styles
with an instance of the DialogFragment class having exactly one style applied. The
following table shows the four available styles:
Style
STYLE_NORMAL
Has title
Has frame
Accepts input
Yes
Yes
Yes
STYLE_NO_TITLE
No
Yes
Yes
STYLE_NO_FRAME
No
No
Yes
STYLE_NO_INPUT
No
No
No
[ 53 ]
The style affects the remainder of the behavior of the DialogFragment class and
therefore must be set in the onCreate callback method. An attempt to set the
DialogFragment class' style any later in the life cycle is ignored.
If you wish to provide the dialog with a special theme, the theme's resource ID can
also be passed to the setStyle method. To allow Android to select an appropriate
theme based on the style, simply pass 0 as the theme resource ID. The following
code sets the DialogFragment instance to have no title and use the Android-selected
theme for this style, as in the following code:
class MyDialogFragment extends DialogFragment {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setStyle(DialogFragment.STYLE_NO_TITLE, 0);
}
}
Layout
Populating the layout of an instance of the DialogFragment class is similar to that
of a standard fragment derived class. We will simply override the onCreateView
method and inflate the layout resource via the following code:
public View onCreateView(LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
View theView = inflater.inflate(R.layout.fragment_my_dialog,
container, false);
return theView;
}
Creating a layout resource for use with a DialogFragment derived class works
exactly like creating a layout resource for any other fragment derived class. To have
our DialogFragment instance display a line of text and two buttons, we will define
the fragment_my_dialog.xml layout resource as shown in the following XML:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
[ 54 ]
Chapter 3
android:layout_height="match_parent">
<!-- Text -->
<TextView
android:layout_width="fill_parent"
android:layout_height="0px"
android:layout_weight="1"
android:text="@string/dialogSimpleFragmentPrompt"
android:layout_margin="16dp"/>
<!-- Two buttons side-by-side -->
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="0px"
android:orientation="horizontal"
android:layout_weight="3">
<Button
android:id="@+id/btnYes"
android:layout_width="0px"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/text_yes"
android:layout_margin="16dp"/>
<Button
android:id="@+id/btnNo"
android:layout_width="0px"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/text_no"
android:layout_margin="16dp"/>
</LinearLayout>
</LinearLayout>
Displaying DialogFragment
Displaying our DialogFragment derived class is largely just a matter of creating
the class instance and calling the show method. We need to keep in mind, though,
that although our DialogFragment instance appears as a standard dialog when
it displays, it is actually a fragment. As with all fragments, it is managed by the
containing activity's FragmentManager instance. As a result, we need to pass
a reference to the activity's FragmentManager instance as part of the call to the
DialogFragment class' show method, as we do in the following code:
MyDialogFragment theDialog = new MyDialogFragment();
theDialog.show(getFragmentManager(), null);
[ 55 ]
With our DialogFragment derived class' style set to STYLE_NO_TITLE and using
the fragment_my_dialog.xml layout resource file shown earlier, the previous
code displays the following screenshot:
[ 56 ]
Chapter 3
Note that we're setting up the button click handling just as we would if we
were working within any other fragment or even directly within the activity.
We can also handle notifying the activity of the user's interaction with the
DialogFragment derived class consistently with the way we do with other
fragments. Just as we did when notifying the activity of book title selections, our
DialogFragment derived class simply provides an interface to notify the activity
which of the available buttons the user selected, as shown in the following code:
public class MyDialogFragment extends DialogFragment
implements View.OnClickListener {
// Interface Activity implements for notification
public interface OnButtonClickListener {
void onButtonClick(int buttonId);
}
// Other members elided for clarity
}
As long as the activity implements the interface, our DialogFragment derived class
can notify the activity of the button that the user clicked.
In the handler for our button click events, we'll follow the same pattern as we did in
the previous chapter. We will access the containing activity, cast it to the expected
interface, and call the interface method, as shown in the following code:
public void onClick(View view) {
int buttonId = view.getId();
// Notify the Activity of the button selection
OnButtonClickListener parentActivity = (OnButtonClickListener)
getActivity();
parentActivity.onButtonClick(buttonId);
// Close the dialog fragment
dismiss();
}
Note that there is one bit of special handling in the onClick method. Just as with the
traditional Dialog class, we must call the dismiss method on the DialogFragment
derived class when we no longer wish to display it.
[ 57 ]
[ 58 ]
Chapter 3
// Set the dialog aspects of the dialog fragment
Dialog dialog = getDialog();
dialog.setTitle(getString(R.string.myDialogFragmentTitle));
dialog.setCanceledOnTouchOutside(false);
return theView;
}
The code first sets the dialog title and then sets the option to prevent the user from
closing the dialog by tapping on the activity window. For the call to the setTitle
method to work, we will need to change the call to the setStyle method in the
onCreate callback method to set the style to STYLE_NORMAL so that the dialog will
have a title area.
[ 59 ]
Within our class' onCreateDialog method, we will create the AlertDialog instance
using the AlertDialog.Builder class just as if we were going to display the
AlertDialog instance directly. Within the onCreateDialog method, we will set all
the options on the AlertDialog.Builder instance, including the title, message, icon,
and buttons. Note that we never call the AlertDialog.Builder class' show method;
instead, we call its create method. We will then take the reference to the newly
created AlertDialog instance and return it from the onCreateDialog method.
All of these steps are shown in the following code:
public Dialog onCreateDialog(Bundle savedInstanceState) {
// Create the Builder for the AlertDialog
AlertDialog.Builder builder = new
AlertDialog.Builder(getActivity());
// Set the AlertDialog options
builder.setTitle(R.string.alert_dialog_title)
.setMessage(R.string.alert_dialog_message)
.setIcon(R.drawable.ic_launcher)
.setCancelable(false)
.setPositiveButton(R.string.text_yes, this)
.setNegativeButton(R.string.text_no, this);
// Create and return the AlertDialog
AlertDialog alertDialog = builder.create();
return alertDialog;
}
[ 60 ]
Chapter 3
Summary
Understanding the fragment life cycle empowers us to leverage the phases of
creation and destruction of fragments to more efficiently manage fragments and
the data associated with them. By working with this natural life cycle, we can take
advantage of the specialized fragment classes to create a rich user experience while
following a more consistent programming model than was previously available.
In the next chapter, we will build on our understanding of the fragment life cycle to
take more direct control of fragments to dynamically add and remove them within
individual activities.
[ 61 ]
www.PacktPub.com
Stay Connected: