Fragments protect against the unexpected

The true test of user interface flexibility is in how well the user interface design and implementation hold up when encountering an unexpected change request. A well-designed fragment-based user interface allows us to create incredible dynamic user interfaces that can evolve and change with minimal impact on the code. As an example, let's make what could potentially be a major design change on our application.

Currently, the application always shows the book list and description on the same activity. The only difference is whether the fragments are positioned vertically or horizontally relative to one another. Imagine we receive feedback from our users that they don't like the way the app appears when viewed on a portrait-oriented handset. When viewed on a portrait-oriented handset, they would like the list and description to appear on separate activities. In all other cases, they want the app to continue to show the list and description side-by-side.

We first create a duplicate copy of the activity_main.xml resource file in the layout resource folder named activity_book_desc.xml. To do this in Android Studio, perform the following steps:

Remove the fragment element for BookListFragment from the activity_book_desc.xml file so it now shows only BookDescFragment as in the following code:

In the activity_main.xml resource file, remove BookDescFragment so that it now appears as follows:

We now have layout resources for each of the activities. Remember that these changes will not affect the appearance of the app in scenarios that use the activity_main_wide.xml resource file.

To display the book description, we add a simple activity named BookDescActivity that uses the activity_book_desc.xml layout resource. The activity relies on an "Intent extra" to pass the book index. Since BookDescFragment contains all the logic necessary to display a book description, we can simply get a reference to BookDescFragment and set the book index just as we did in the MainActivity class as shown here:

The MainActivity class has some extra work to do now because the specific fragments contained within it will vary. When running on a device with a screen that is at least 600 dp wide or when running on a device in the large screen group, the MainActivity class always contains an instance of BookDescFragment. On the other hand, when running on other devices, the presence of BookDescFragment will depend upon the device's current orientation. We could add code to the MainActivity class to test for all of these various scenarios or we could take a simpler approach, that is, check whether the activity contains an instance of the BookDescFragment class.

Using this approach, we have the MainActivity class' onSelectedBookChanged method to check the validity of BookDescFragment returned by FragmentManager. If FragmentManager returns a valid reference, the method can call setBook on BookDescFragment just as it has been. If the returned reference is not valid, the onSelectedBookChanged method calls startActivity with an Intent instance containing the information to display BookDescActivity that includes bookIndex as an extra as shown in the following code:

Notice the if statement that checks the validity of bookDescFragment. In most cases, a simple check for whether the reference is null is all we need. The one exception is the case of when the app is run on a handset device on which the user has viewed the app in landscape orientation and then rotated the device to portrait. In this situation, the BookDescFragment instance is not visible but the activity's FragmentManager instance may be caching a reference to an invisible instance remaining from the landscape layout. For this reason, we check both for a null reference and for visibility. We'll discuss the details of fragment lifecycle, creation, and caching over the next two chapters.

We now have adaptability built into our app. The scenarios that use the activity_main_wide.xml resource file look as they always did. On portrait-oriented handset devices, our app provides the user interface with two separate activities: one for the book list and one for the book description. The application now appears on portrait-oriented handset devices as shown here:

Making the MainActivity class adaptive