Android Programming: The Big Nerd Ranch Guide (2015)

Chapter 7. UI Fragments and the Fragment Manager

In this chapter, you will start building an application named CriminalIntent. CriminalIntent records the details of “office crimes” – things like leaving dirty dishes in the breakroom sink or walking away from an empty shared printer after documents have printed.

With CriminalIntent, you can make a record of a crime including a title, a date, and a photo. You can also identify a suspect from your contacts and lodge a complaint via email, Twitter, Facebook, or another app. After documenting and reporting a crime, you can proceed with your work free of resentment and ready to focus on the business at hand.

CriminalIntent is a complex app that will take thirteen chapters to complete. It will have a list-detail interface: The main screen will display a list of recorded crimes. Users will be able to add new crimes or select an existing crime to view and edit its details (Figure 7.1).

Figure 7.1  CriminalIntent, a list-detail app

CriminalIntent, a list-detail app

The Need for UI Flexibility

You might imagine that a list-detail application consists of two activities: one managing the list and the other managing the detail view. Clicking a crime in the list would start an instance of the detail activity. Pressing the Back button would destroy the detail activity and return you to the list where you could select another crime.

That would work, but what if you wanted more sophisticated presentation and navigation between screens?

·               Imagine that your user is running CriminalIntent on a tablet. Tablets and some larger phones have screens large enough to show the list and detail at the same time – at least in landscape orientation (Figure 7.2).

Figure 7.2  Ideal list-detail interface for phone and tablet

Ideal list-detail interface for phone and tablet

·               Imagine the user is viewing a crime on a phone and wants to see the next crime in the list. It would be better if the user could swipe to see the next crime without having to return to the list. Each swipe should update the detail view with information for the next crime.

What these scenarios have in common is UI flexibility: the ability to compose and recompose an activity’s view at runtime depending on what the user or the device requires.

Activities were not built to provide this flexibility. An activity’s views may change at runtime, but the code to control those views must live inside the activity. As a result, activities are tightly coupled to a particular screen used by the user.

Introducing Fragments

You can get around the letter of the Android law by moving the app’s UI management from the activity to one or more fragments.

fragment is a controller object that an activity can deputize to perform tasks. Most commonly, the task is managing a user interface. The user interface can be an entire screen or just one part of the screen.

A fragment managing a user interface is known as a UI fragment. A UI fragment has a view of its own that is inflated from a layout file. The fragment’s view contains the interesting UI elements that the user wants to see and interact with.

The activity’s view contains a spot where the fragment’s view will be inserted. Or it might have several spots for the views of several fragments.

You can use the fragment(s) associated with the activity to compose and re-compose the screen as your app and users require. The activity’s view technically stays the same throughout its lifetime, and no laws of Android are violated.

Let’s see how this would work in a list-detail application to display the list and detail together. You would compose the activity’s view from a list fragment and a detail fragment. The detail view would show the details of the selected list item.

Selecting another item should display a new detail view. This is easy with fragments; the activity will replace the detail fragment with another detail fragment (Figure 7.3). No activities need to die for this major view change to happen.

Figure 7.3  Detail fragment is swapped out

Detail fragment is swapped out

Using UI fragments separates the UI of your app into building blocks, which is useful for more than just list-detail applications. Working with individual blocks, it is easy to build tab interfaces, tack on animated sidebars, and more.

Achieving this UI flexibility comes at a cost: more complexity, more moving parts, and more code. You will reap the benefits of using fragments in Chapter 11 and Chapter 17. The complexity, however, starts now.

Starting CriminalIntent

In this chapter, you are going to start on the detail part of CriminalIntent. Figure 7.4 shows you what CriminalIntent will look like at the end of this chapter.

It may not seem like a very exciting goal to shoot for. Just keep in mind that this chapter is about laying the foundation for the bigger things that are coming.

Figure 7.4  CriminalIntent at the end of this chapter

CriminalIntent at the end of this chapter

The screen shown in Figure 7.4 will be managed by a UI fragment named CrimeFragment. An instance of CrimeFragment will be hosted by an activity named CrimeActivity.

For now, think of hosting as the activity providing a spot in its view hierarchy where the fragment can place its view (Figure 7.5). A fragment is incapable of getting a view on screen itself. Only when it is placed in an activity’s hierarchy will its view appear.

Figure 7.5  CrimeActivity hosting a CrimeFragment

CrimeActivity hosting a CrimeFragment

CriminalIntent will be a large project, and one way to keep your head wrapped around a project is with an object diagram. Figure 7.6 gives you the big picture of CriminalIntent. You do not have to memorize these objects and their relationships, but it is good to have an idea of where you are heading before you start.

You can see that CrimeFragment will do the sort of work that your activities did in GeoQuiz: create and manage the user interface and interact with the model objects.

Figure 7.6  Object diagram for CriminalIntent (for this chapter)

Object diagram for CriminalIntent (for this chapter)

Three of the classes shown in Figure 7.6 are classes that you will write: CrimeCrimeFragment, and CrimeActivity.

An instance of Crime will represent a single office crime. In this chapter, a crime will have only a title and an ID. The title is a descriptive name, like “Toxic sink dump” or “Someone stole my yogurt!” The ID will uniquely identify an instance of Crime.

For this chapter, you will keep things very simple and use a single instance of CrimeCrimeFragment will have a member variable (mCrime) to hold this isolated incident.

CrimeActivity’s view will consist of a FrameLayout that defines the spot where the CrimeFragment’s view will appear.

CrimeFragment’s view will consist of a LinearLayout and an EditTextCrimeFragment will have a member variable for the EditText (mTitleField) and will set a listener on it to update the model layer when the text changes.

Creating a new project

Enough talk; time to build a new app. Create a new Android application (File → New Project...). Name the application CriminalIntent and name the package com.bignerdranch.android.criminalintent, as shown in Figure 7.7.

Figure 7.7  Creating the CriminalIntent application

Creating the CriminalIntent application

Click Next and specify a minimum SDK of API 16: Android 4.1. Also ensure that only the Phone and Tablet application type is checked.

Click Next again to select the type of Activity to add. Choose Blank Activity and continue along in the wizard.

In the final step of the New Project wizard, name the activity CrimeActivity and click Finish (Figure 7.8).

Figure 7.8  Configuring CrimeActivity

Configuring CrimeActivity

Fragments and the support library

Fragments were introduced in API level 11 along with the first Android tablets and the sudden need for UI flexibility. In the old days of Android development, which you can see in the first edition of this book, many developers supported devices running API level 8 and newer. Luckily, those developers were still able to use fragments because of Android’s support library.

The support library includes a complete implementation of fragments that work all the way back to API level 4. In this book, we will use the support implementation of fragments rather than the implementation built into the Android OS. This is a good idea because the support library is quickly updated when new features are added to the fragments API. To use those new features, you can update your project with the latest version of the support library. Detailed reasoning for this decision is laid out at the end of the chapter in the section called “For the More Curious: Why Support Fragments are Superior”.

Note that when you use a support library class, it is not just used on older versions where no native class is available; it is also used on newer versions instead of the native class.

There are two key classes that we will use from the support library: the Fragment class (android.support.v4.app.Fragment) and the FragmentActivity class (android.support.v4.app.FragmentActivity). Using fragments requires activities that know how to manage fragments. The FragmentActivityclass knows how to manage the support version of fragments.

Figure 7.9 shows you the name of each of these classes and where they live. Since the support library (and android.support.v4.app.Fragment) lives with your application, it is safe to use no matter where your app is running.

Figure 7.9  Where the different fragment classes live

Where the different fragment classes live

Adding dependencies in Android Studio

To use the support library, your project must list it as a dependency. Open the build.gradle file located in your app module. Your project will come with two build.gradle files, one for the project as a whole and one for your app module. We will edit the one located at app/build.gradle.

Listing 7.1  Gradle dependencies (app/build.gradle)

apply plugin: 'com.android.application'

android {

    ...

}

dependencies {

    compile fileTree(dir: 'libs', include: ['*.jar'])

}

In the current dependencies section of your build.gradle file, you should see something similar to Listing 7.1 that specifies that the project depends on all of the jar files in the project’s libs directory.

Gradle also allows for the specification of dependencies that you have not copied into your project. When your app is compiled, Gradle will find, download, and include the dependencies for you. All you have to do is specify an exact string incantation and Gradle will do the rest.

Nobody can remember these incantations, though, so Android Studio maintains a list of common libraries for you. Navigate to the project structure for your project (File → Project Structure...).

Select the app module on the left and the Dependencies tab in the app module. The dependencies for the app module are listed here. (You may have other dependencies already specified here, such as the AppCompat dependency shown in Figure 7.10. If you have other dependencies, do not remove them. You will learn about the AppCompat library in Chapter 13.)

Figure 7.10  App dependencies

App dependencies

You may have additional dependencies specified here, such as the AppCompat dependency. If you have other dependencies, do not remove them. You will learn about the AppCompat library in Chapter 13.

Use the + button and choose Library dependency to add a new dependency (Figure 7.11). Choose the support-v4 library from the list and click OK.

Figure 7.11  A collection of dependencies

A collection of dependencies

Navigate back to the editor window showing app/build.gradle and you should see a new addition, as shown in Listing 7.2.

Listing 7.2  Updated Gradle dependencies (app/build.gradle)

...

dependencies {

    compile fileTree(dir: 'libs', include: ['*.jar'])

    compile 'com.android.support:support-v4:22.1.1'

}

(If you modify this file manually, outside of the project structure window, you will need to sync your project with the Gradle file for your project to reflect any updates that you have made. This sync asks Gradle to update the build based on your changes by either downloading or removing dependencies. Changes within the project structure window will trigger this sync automatically. To manually perform this sync, navigate to Tools → Android → Sync Project with Gradle Files.)

The shaded dependency string in Listing 7.2 uses the following Maven coordinates format: groupId:artifactId:version. (Maven is a dependency management tool. You can learn more about Maven at https://maven.apache.org/.)

The groupId is the unique identifier for a set of libraries available on the Maven repository. Often the library’s base package name is used as the groupId, which is com.android.support in this case.

The artifactId is the name of a specific library within the package. In this case, the name of the library you are referring to is support-v4. There are different libraries available within com.android.support, such as support-v13, appcompat-v7, and gridlayout-v7. Google uses the naming convention basename-vX for their support libraries, where -vX represents the minimum API level the library supports. So, for example, appcompat-v7 is Google’s compatibility library that works on devices running Android API version 7 and higher.

Last but not least, the version represents which revision number of the library. CriminalIntent depends on the 22.1.1 version of the support-v4 library. Version 22.1.1 is the latest version as of this writing, but any version newer than that should work for this project. In fact, it is a good idea to use the latest version of the support library so that you can use newer APIs and receive the latest bug fixes. If Android Studio added a newer version of the library for you, do not roll it back to the version shown above.

Now that the support library is a dependency in the project, it is time to use it. In the package explorer, find and open CrimeActivity.java. Change CrimeActivity’s superclass to FragmentActivity. While you are there, remove the template’s implementation of onCreateOptionsMenu(Menu) andonOptionsItemSelected(MenuItem). (You will be creating an options menu for CriminalIntent from scratch in Chapter 13.)

Listing 7.3  Tweaking template code (CrimeActivity.java)

public class CrimeActivity extends AppCompatActivity FragmentActivity {

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_crime);

    }

    @Override

    public boolean onCreateOptionsMenu(Menu menu) {

        getMenuInflater().inflate(R.menu.crime, menu);

        return true;

    }

    @Override

    public boolean onOptionsItemSelected(MenuItem item) {

        int id = item.getItemId();

        if (id == R.id.action_settings) {

            return true;

        }

        return super.onOptionsItemSelected(item);

    }

}

Before proceeding further with CrimeActivity, let’s create the model layer for CriminalIntent by writing the Crime class.

Creating the Crime class

In the project tool window, right-click the com.bignerdranch.android.criminalintent package and select New → Java Class. Name the class Crime and click OK.

In Crime.java, add the following code:

Listing 7.4  Adding to Crime class (Crime.java)

public class Crime {

    private UUID mId;

    private String mTitle;

    public Crime() {

        // Generate unique identifier

        mId = UUID.randomUUID();

    }

}

Next, you want to generate only a getter for the read-only mId and both a getter and setter for mTitle. Right-click after the constructor and select Generate... → Getter and select the mId variable. Then, generate the getter and setter for mTitle by repeating the process, but selecting Getter and Setter in theGenerate... menu.

Listing 7.5  Generated getters and setter (Crime.java)

public class Crime {

    private UUID mId;

    private String mTitle;

    public Crime() {

        mId = UUID.randomUUID();

    }

    public UUID getId() {

        return mId;

    }

    public String getTitle() {

        return mTitle;

    }

    public void setTitle(String title) {

        mTitle = title;

    }

}

That is all you need for the Crime class and for CriminalIntent’s model layer in this chapter.

At this point, you have created the model layer and an activity that is capable of hosting a support fragment. Now you will get into the details of how the activity performs its duties as host.

Hosting a UI Fragment

To host a UI fragment, an activity must:

·               define a spot in its layout for the fragment’s view

·               manage the lifecycle of the fragment instance

The fragment lifecycle

Figure 7.12 shows the fragment lifecycle. It is similar to the activity lifecycle: it has stopped, paused, and running states, and it has methods you can override to get things done at critical points – many of which correspond to activity lifecycle methods.

Figure 7.12  Fragment lifecycle diagram

Fragment lifecycle diagram

The correspondence is important. Because a fragment works on behalf of an activity, its state should reflect the activity’s state. Thus, it needs corresponding lifecycle methods to handle the activity’s work.

One critical difference between the fragment lifecycle and the activity lifecycle is that fragment lifecycle methods are called by the hosting activity, not the OS. The OS knows nothing about the fragments that an activity is using to manage things. Fragments are the activity’s internal business.

You will see more of the fragment lifecycle methods as you continue building CriminalIntent.

Two approaches to hosting

You have two options when it comes to hosting a UI fragment in an activity:

·               add the fragment to the activity’s layout

·               add the fragment in the activity’s code

The first approach is known as using a layout fragment. It is simple but inflexible. If you add the fragment to the activity’s layout, you hardwire the fragment and its view to the activity’s view and cannot swap out that fragment during the activity’s lifetime.

The second approach, adding the fragment to the activity’s code, is more complex. But it is the only way to have control at runtime over your fragments. You determine when the fragment is added to the activity and what happens to it after that. You can remove the fragment, replace it with another, and then add the first fragment back again.

Thus, to achieve real UI flexibility you must add your fragment in code. This is the approach you will use for CrimeActivity’s hosting of a CrimeFragment. The code details will come later in the chapter. First, you are going to define CrimeActivity’s layout.

Defining a container view

You will be adding a UI fragment in the hosting activity’s code, but you still need to make a spot for the fragment’s view in the activity’s view hierarchy. In CrimeActivity’s layout, this spot will be the FrameLayout shown in Figure 7.13.

Figure 7.13  Fragment-hosting layout for CrimeActivity

Fragment-hosting layout for CrimeActivity

This FrameLayout will be the container view for a CrimeFragment. Notice that the container view is completely generic; it does not name the CrimeFragment class. You can and will use this same layout to host other fragments.

Locate CrimeActivity’s layout at res/layout/activity_crime.xml. Open this file and replace the default layout with the FrameLayout diagrammed in Figure 7.13. Your XML should match that in Listing 7.6.

Listing 7.6  Create fragment container layout (activity_crime.xml)

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"

  android:id="@+id/fragment_container"

  android:layout_width="match_parent"

  android:layout_height="match_parent"

  />

Note that while activity_crime.xml consists solely of a container view for a single fragment, an activity’s layout can be more complex and define multiple container views as well as widgets of its own.

You can preview your layout file or run CriminalIntent to check your code. You will only see an empty FrameLayout because the CrimeActivity is not yet hosting a fragment (Figure 7.14).

Figure 7.14  An empty FrameLayout

An empty FrameLayout

Later, you will write code that puts a fragment’s view inside this FrameLayout. But first you need to create a fragment.

Creating a UI Fragment

The steps to creating a UI fragment are the same as those you followed to create an activity:

·               compose a user interface by defining widgets in a layout file

·               create the class and set its view to be the layout that you defined

·               wire up the widgets inflated from the layout in code

Defining CrimeFragment’s layout

CrimeFragment’s view will display the information contained within an instance of Crime. Eventually, the Crime class and CrimeFragment’s view will include many interesting pieces, but for this chapter you just need a text field to contain the crime’s title.

Figure 7.15 shows the layout for CrimeFragment’s view. It consists of a vertical LinearLayout that contains an EditTextEditText is a widget that presents an area where the user can add or edit text.

Figure 7.15  Initial layout for CrimeFragment

Initial layout for CrimeFragment

To create a layout file, right-click the res/layout folder in the project tool window and select New → Layout resource file. Name this file fragment_crime.xml and enter LinearLayout as the root element. Click OK and Android Studio will generate the file for you.

When the file opens, navigate to the XML. The wizard has added the LinearLayout for you. Using Figure 7.15 as a guide, make the necessary changes to fragment_crime.xml. You can use Listing 7.7 to check your work.

Listing 7.7  Layout file for fragment’s view (fragment_crime.xml)

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

  android:layout_width="match_parent"

  android:layout_height="match_parent"

  android:orientation="vertical"

  >

  <EditText android:id="@+id/crime_title"

    android:layout_width="match_parent"

    android:layout_height="wrap_content"

    android:hint="@string/crime_title_hint"

    />

</LinearLayout>

Open res/values/strings.xml and add a crime_title_hint string resource.

Listing 7.8  Adding a string (res/values/strings.xml)

<?xml version="1.0" encoding="utf-8"?>

<resources>

    <string name="app_name">CriminalIntent</string>

    <string name="hello_world">Hello world!</string>

    <string name="action_settings">Settings</string>

    <string name="crime_title_hint">Enter a title for the crime.</string>

</resources>

Save your files. Navigate back to fragment_crime.xml to see a preview of your fragment’s view.

Creating the CrimeFragment class

Right-click the com.bignerdranch.android.criminalintent package and select New → Java Class. Name the class CrimeFragment and click OK to generate the class.

Now, turn this class into a fragment. Update CrimeFragment to subclass the Fragment class.

Listing 7.9  Subclass the Fragment class (CrimeFragment.java)

public class CrimeFragment extends Fragment {

}

As you subclass the Fragment class, you will notice that Android Studio finds two classes with the Fragment name. You will see Fragment (android.app) and Fragment (android.support.v4.app). The android.app Fragment is the version of fragments that are built into the Android OS. We will use the support library version, so be sure to select the android.support.v4.app version of the Fragment class when you see the dialog, as shown in Figure 7.16.

Figure 7.16  Choosing the support library’s Fragment class

Choosing the support library’s Fragment class

Your code should match Listing 7.10.

Listing 7.10  Support Fragment import (CrimeFragment.java)

package com.bignerdranch.android.criminalintent;

import android.support.v4.app.Fragment;

public class CrimeFragment extends Fragment {

}

If you do not see this dialog or the wrong fragment class was imported, you can manually import the correct class. If you have an import for android.app.Fragment, remove that line of code. Import the correct Fragment class with the Option+Return (or Alt+Enter) shortcut. Be sure to select the support version of the Fragment class.

Implementing fragment lifecycle methods

CrimeFragment is a controller that interacts with model and view objects. Its job is to present the details of a specific crime and update those details as the user changes them.

In GeoQuiz, your activities did most of their controller work in activity lifecycle methods. In CriminalIntent this work will be done by fragments in fragment lifecycle methods. Many of these methods correspond to the Activity methods you already know, such as onCreate(Bundle).

In CrimeFragment.java, add a member variable for the Crime instance and an implementation of Fragment.onCreate(Bundle).

Listing 7.11  Overriding Fragment.onCreate(Bundle) (CrimeFragment.java)

public class CrimeFragment extends Fragment {

    private Crime mCrime;

    @Override

    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        mCrime = new Crime();

    }

}

There are a couple of things to notice in this implementation. First, Fragment.onCreate(Bundle) is a public method whereas Activity.onCreate(Bundle) is protected. Fragment.onCreate(…) and other Fragment lifecycle methods must be public because they will be called by whatever activity is hosting the fragment.

Second, similar to an activity, a fragment has a bundle to which it saves and retrieves its state. You can override Fragment.onSaveInstanceState(Bundle) for your own purposes just like with Activity.onSaveInstanceState(Bundle).

Also, note what does not happen in Fragment.onCreate(…): you do not inflate the fragment’s view. You configure the fragment instance in Fragment.onCreate(…), but you create and configure the fragment’s view in another fragment lifecycle method:

    public View onCreateView(LayoutInflater inflater, ViewGroup container,

        Bundle savedInstanceState)

This method is where you inflate the layout for the fragment’s view and return the inflated View to the hosting activity. The LayoutInflater and ViewGroup parameters are necessary to inflate the layout. The Bundle will contain data that this method can use to recreate the view from a saved state.

In CrimeFragment.java, add an implementation of onCreateView(…) that inflates fragment_crime.xml.

Listing 7.12  Overriding onCreateView(…) (CrimeFragment.java)

public class CrimeFragment extends Fragment {

    private Crime mCrime;

    @Override

    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        mCrime = new Crime();

    }

    @Override

    public View onCreateView(LayoutInflater inflater, ViewGroup container,

            Bundle savedInstanceState) {

        View v = inflater.inflate(R.layout.fragment_crime, container, false);

        return v;

    }

}

Within onCreateView(…), you explicitly inflate the fragment’s view by calling LayoutInflater.inflate(…) and passing in the layout resource ID. The second parameter is your view’s parent, which is usually needed to configure the widgets properly. The third parameter tells the layout inflater whether to add the inflated view to the view’s parent. You pass in false because you will add the view in the activity’s code.

Wiring widgets in a fragment

The onCreateView(…) method is also the place to wire up the EditText to respond to user input. After the view is inflated, get a reference to the EditText and add a listener.

Listing 7.13  Wiring up the EditText widget (CrimeFragment.java)

public class CrimeFragment extends Fragment {

    private Crime mCrime;

    private EditText mTitleField;

    ...

    @Override

    public View onCreateView(LayoutInflater inflater, ViewGroup container,

            Bundle savedInstanceState) {

        View v = inflater.inflate(R.layout.fragment_crime, container, false);

        mTitleField = (EditText)v.findViewById(R.id.crime_title);

        mTitleField.addTextChangedListener(new TextWatcher() {

            @Override

            public void beforeTextChanged(

                CharSequence s, int start, int count, int after) {

                // This space intentionally left blank

            }

            @Override

            public void onTextChanged(

                CharSequence s, int start, int before, int count) {

                mCrime.setTitle(s.toString());

            }

            @Override

            public void afterTextChanged(Editable s) {

                // This one too

            }

        });

        return v;

    }

}

Getting references in Fragment.onCreateView(…) works nearly the same as in Activity.onCreate(…). The only difference is that you call View.findViewById(int) on the fragment’s view. The Activity.findViewById(int) method that you used before is a convenience method that callsView.findViewById(int) behind the scenes. The Fragment class does not have a corresponding convenience method, so you have to call the real thing.

Setting listeners in a fragment works exactly the same as in an activity. In Listing 7.13, you create an anonymous class that implements the TextWatcher listener interface. TextWatcher has three methods, but you only care about one: onTextChanged(…).

In onTextChanged(…), you call toString() on the CharSequence that is the user’s input. This method returns a string, which you then use to set the Crime’s title.

Your code for CrimeFragment is now complete. It would be great if you could run CriminalIntent now and play with the code you have written. But you cannot. Fragments cannot put their views on screen. To realize your efforts, you first have to add a CrimeFragment to CrimeActivity.

Adding a UI Fragment to the FragmentManager

When the Fragment class was introduced in Honeycomb, the Activity class was changed to include a piece called the FragmentManager. The FragmentManager is responsible for managing your fragments and adding their views to the activity’s view hierarchy (Figure 7.17).

The FragmentManager handles two things: a list of fragments and a back stack of fragment transactions (which you will learn about shortly).

Figure 7.17  The FragmentManager

The FragmentManager

For CriminalIntent, you will only be concerned with the FragmentManager’s list of fragments.

To add a fragment to an activity in code, you make explicit calls to the activity’s FragmentManager. The first step is to get the FragmentManager itself. In CrimeActivity.java, add the following code to onCreate(…).

Listing 7.14  Getting the FragmentManager (CrimeActivity.java)

public class CrimeActivity extends FragmentActivity {

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_crime);

        FragmentManager fm = getSupportFragmentManager();

    }

}

If you see an error after adding this line of code, check the import statements to make sure that the support version of the FragmentManager class was imported.

You call getSupportFragmentManager() because you are using the support library and the FragmentActivity class. If you were not interested in using the support library, then you would subclass Activity and call getFragmentManager().

Fragment transactions

Now that you have the FragmentManager, add the following code to give it a fragment to manage. (We will step through this code afterward. Just get it in for now.)

Listing 7.15  Adding a CrimeFragment (CrimeActivity.java)

public class CrimeActivity extends FragmentActivity {

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_crime);

        FragmentManager fm = getSupportFragmentManager();

        Fragment fragment = fm.findFragmentById(R.id.fragment_container);

        if (fragment == null) {

            fragment = new CrimeFragment();

            fm.beginTransaction()

                .add(R.id.fragment_container, fragment)

                .commit();

        }

    }

}

The best place to start understanding the code that you added in Listing 7.15 is not at the beginning. Instead, find the add(…) operation and the code around it. This code creates and commits a fragment transaction.

        if (fragment == null) {

            fragment = new CrimeFragment();

            fm.beginTransaction()

                .add(R.id.fragment_container, fragment)

                .commit();

Fragment transactions are used to add, remove, attach, detach, or replace fragments in the fragment list. They are the heart of how you use fragments to compose and recompose screens at runtime. The FragmentManager maintains a back stack of fragment transactions that you can navigate.

The FragmentManager.beginTransaction() method creates and returns an instance of FragmentTransaction. The FragmentTransaction class uses a fluent interface - methods that configure FragmentTransaction return a FragmentTransaction instead of void, which allows you to chain them together. So the code highlighted above says, “Create a new fragment transaction, include one add operation in it, and then commit it.”

The add(…) method is the meat of the transaction. It has two parameters: a container view ID and the newly created CrimeFragment. The container view ID should look familiar. It is the resource ID of the FrameLayout that you defined in activity_crime.xml.

A container view ID serves two purposes:

·               It tells the FragmentManager where in the activity’s view the fragment’s view should appear.

·               It is used as a unique identifier for a fragment in the FragmentManager’s list.

When you need to retrieve the CrimeFragment from the FragmentManager, you ask for it by container view ID:

        FragmentManager fm = getSupportFragmentManager();

        Fragment fragment = fm.findFragmentById(R.id.fragment_container);

        if (fragment == null) {

            fragment = new CrimeFragment();

            fm.beginTransaction()

                .add(R.id.fragment_container, fragment)

                .commit();

        }

It may seem odd that the FragmentManager identifies the CrimeFragment using the resource ID of a FrameLayout. But identifying a UI fragment by the resource ID of its container view is built into how the FragmentManager operates. If you are adding multiple fragments to an activity, you would typically create separate containers with separate IDs for each of those fragments.

Now we can summarize the code you added in Listing 7.15 from start to finish.

First, you ask the FragmentManager for the fragment with a container view ID of R.id.fragment_container. If this fragment is already in the list, the FragmentManager will return it.

Why would a fragment already be in the list? The call to CrimeActivity.onCreate(…) could be in response to CrimeActivity being re-created after being destroyed on rotation or to reclaim memory. When an activity is destroyed, its FragmentManager saves out its list of fragments. When the activity is re-created, the new FragmentManager retrieves the list and re-creates the listed fragments to make everything as it was before.

On the other hand, if there is no fragment with the given container view ID, then fragment will be null. In this case, you create a new CrimeFragment and a new fragment transaction that adds the fragment to the list.

CrimeActivity is now hosting a CrimeFragment. Run CriminalIntent to prove it. You should see the view defined in fragment_crime.xml, as shown in Figure 7.18.

Figure 7.18  CrimeFragment’s view hosted by CrimeActivity

CrimeFragment’s view hosted by CrimeActivity

A single widget on screen may not seem like much of a reward for all the work you have done in this chapter. But you have laid down a solid foundation to do greater things with CriminalIntent in the chapters ahead.

The FragmentManager and the fragment lifecycle

Now that you know about the FragmentManager, let’s take another look at the fragment lifecycle (Figure 7.19).

Figure 7.19  The fragment lifecycle, again

The fragment lifecycle, again

The FragmentManager of an activity is responsible for calling the lifecycle methods of the fragments in its list. The onAttach(Activity)onCreate(Bundle), and onCreateView(…) methods are called when you add the fragment to the FragmentManager.

The onActivityCreated(…) method is called after the hosting activity’s onCreate(…) method has executed. You are adding the CrimeFragment in CrimeActivity.onCreate(…), so this method will be called after the fragment has been added.

What happens if you add a fragment while the activity is already running? In that case, the FragmentManager immediately walks the fragment through whatever steps are necessary to get it caught up to the activity’s state. For example, as a fragment is added to an activity that is already running, that fragment gets calls to onAttach(Activity)onCreate(Bundle)onCreateView(…)onActivityCreated(Bundle)onStart(), and then onResume().

Once the fragment’s state is caught up to the activity’s state, the hosting activity’s FragmentManager will call further lifecycle methods around the same time it receives the corresponding calls from the OS to keep the fragment’s state aligned with that of the activity.

Application Architecture with Fragments

Designing your app with fragments the right way is supremely important. Many developers, after first learning about fragments, try to use them for every reusable component in their application. This is the wrong way to use fragments.

Fragments are intended to encapsulate major components in a reusable way. A major component in this case would be on the level of an entire screen of your application. If you have a significant number of fragments on screen at once, your code will be littered with fragment transactions and unclear responsibility. A better architectural solution for reuse with smaller components is to extract them into a custom view (a class that subclasses View or one of its subclasses).

Use fragments responsibly. A good rule of thumb is to have no more than two or three fragments on the screen at a time (Figure 7.20).

Figure 7.20  Less is more

Less is more

The reason all our activities will use fragments

From here on, all of the apps in this book will use fragments – no matter how simple. This may seem like overkill. Many of the examples you will see in following chapters could be written without fragments. The user interfaces could be created and managed from activities, and doing so might even be less code.

However, we believe it is better for you to become comfortable with the pattern you will most likely use in real life.

You might think it would be better to begin a simple app without fragments and add them later, when (or if) necessary. There is an idea in Extreme Programming methodology called YAGNI. YAGNI stands for “You Aren’t Gonna Need It,” and it urges you not to write code if you think youmight need it later. Why? Because YAGNI. It is tempting to say “YAGNI” to fragments.

Unfortunately, adding fragments later can be a minefield. Changing an activity to an activity hosting a UI fragment is not difficult, but there are swarms of annoying gotchas. Keeping some interfaces managed by activities and having others managed by fragments only makes things worse because you have to keep track of this meaningless distinction. It is far easier to write your code using fragments from the beginning and not worry about the pain and annoyance of reworking it later, or having to remember which style of controller you are using in each part of your application.

Therefore, when it comes to fragments, we have a different principle: AUF, or “Always Use Fragments.” You can kill a lot of brain cells deciding whether to use a fragment or an activity, and it is just not worth it. AUF!

For the More Curious: Why Support Fragments are Superior

This book uses the support library implementation of fragments over the implementation built into the Android OS, which may seem like an unusual choice. After all, the support library implementation of fragments was initially created so that developers could use fragments on old versions of Android that do not support the API. Today, most developers can exclusively work with versions of Android that do include support for fragments.

We still prefer support fragments. Why? Support fragments are superior because you can update the version of the support library in your application and ship a new version of your app at any time. New releases of the support library come out multiple times a year. When a new feature is added to the fragment API, that feature is also added to the support library fragment API along with any available bug fixes. To use this new goodness, just update the version of the support library in your application.

As an example, official support for fragment nesting (hosting a fragment in a fragment) was added in Android 4.2. If you are using the Android OS implementation of fragments and supporting Android 4.0 and newer, you cannot use this API on all devices that your app supports. If you are using the support library, you can update the version of the library in your app and nest fragments until you run out of memory on the device.

There are no significant downsides to using the support library’s fragments. The implementation of fragments is nearly identical in the support library as it is in the OS. The only real downside is that you have to include the support library in your project and it has a nonzero size. However, it is currently under a megabyte – and you will likely use the support library for some of its other features as well.

We take a practical approach in this book and in our own application development. The support library is king.

For the More Curious: Using Built-In Fragments

If you are strong-willed and do not believe in the advice above, you can use the fragment implementation built into the Android OS.

To use standard library fragments, you would make three changes to the project:

·               Subclass the standard library Activity class (android.app.Activity) instead of FragmentActivity. Activities have support for fragments out of the box on API level 11 or higher.

·               Subclass android.app.Fragment instead of android.support.v4.app.Fragment.

·               To get the FragmentManager, call getFragmentManager() instead of getSupportFragmentManager().