Mastering Android Application Development
上QQ阅读APP看书,第一时间看更新

Fragments

In this section, we are going to review briefly the key concepts of fragments to explain advanced features and components, such as Fragment Manager and the fragments back stack.

In our example, we will create an activity called MainActivity and four fragments: ListFragment, ContactFragment, SettingsFragment, and DetailsFragment. For this, you can create a fragments package and double-click on the package to go to New | Fragment | Blank Fragment. Take a look at the following dialog box:

Fragments

For now, you can create them without the fragment factory methods and the interface callbacks. We will cover these later in the chapter.

Our project so far should look like this in the Project view:

Fragments

Understanding the importance of fragments

A fragment represents a behavior or a portion of the user interface in an activity. You can combine multiple fragments in a single activity to build a multipane UI and reuse a fragment in multiple activities. You can think of a fragment as a modular section of an activity that has its own lifecycle and receives its own input events, which you can add or remove while the activity is running (sort of like a subactivity that you can reuse in different activities).

Understanding the importance of fragments

The fragment lifecycle is slightly different from the activity lifecycle. The first difference we notice is the use of the OnAttach() and OnDetach() methods, which connect the fragment to the activity.

Using onCreate(), we can create the view in OnCreateView(); after this we can call getView() in our fragment, and it won't be null.

The onActivityCreated() method tells the fragment that its activity has been completed on its own Activity.onCreate().

There are two ways to display a fragment:

The first way is to have the fragment in our layout XML. This will create our fragment when the view containing it is inflated. Execute the following code:

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

    <fragment android:name="com.example.android.MyFragment"
              android:id="@+id/headlines_fragment"
android:layout_width="match_parent"
              android:layout_height="match_parent" />
</LinearLayout>

The second way is to create our fragment programmatically and tell Fragment Manager to display it in a container. For this, you can use the following code:

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

    <Framelayout android:id="@+id/fragment_container"
android:layout_width="match_parent"
             android:layout_height="match_parent" />

</LinearLayout>

After this, inflate a FrameLayout container where the fragment will be inserted using the following lines of code:

Myfragment fragment = MyFragment.newInstance();
getSupportFragmentManager().beginTransaction()
                    .add(R.id.fragment_container, fragment).commit();

To finish with the key concepts, it is important to explain why Android examples create the fragments using the MyFragment.newInstance(params) factory method instead of using the default new MyFragment(params) constructor. Take a look at the following code:

public class MyFragment extends Fragment {
    
 // Static factory method that returns a new fragment
 // receiving a   parameter and initializing the fragment's arguments

    public static MyFragment newInstance(int param) {
        MyFragment fragment = new MyFragment();
        Bundle args = new Bundle();
        args.putInt("param", param);
        fragment.setArguments(args);
        return fragment;
    }
}

The reason behind this pattern is that Android only recreates Fragments using the default constructor; therefore, if we have a constructor with parameters, it will be ignored, and the parameters will be lost.

Tip

Note that we send the parameters in a bundle as arguments, allowing the fragment to retrieve the parameter if it has to be recreated (due to a device orientation change, we use the back navigation).

The Fragment Manager

The Fragment Manager is an interface used to interact with the fragments inside an activity. This means that any operation, such as adding, replacing, removing, or finding a fragment, has to be done through it.

To obtain Fragment Manager, our Activity needs to extend from FragmentActivity, which will allows us to call getFragmentManager() or getSupportFragmentManager() preferably that maintain backwards compatibility using the Fragment Manager included in Android.support.v4.

If we want to use nested fragments, we can manage them with getChildFragmentManager(). You cannot inflate a layout into a fragment when this layout includes <fragment>. Nested fragments are only supported when added to a fragment dynamically.

Now, we will discuss some scenarios that we will face sooner or later while working with fragments. Imagine that we have an activity with two fragments, A and B.

A typical scenario is that we are in a fragment and we want to execute a method from the activity. In this case, we have two options; one is to implement a public method in MyActivity, for instance doSomething(), so that we can cast getActivity to our activity and call the ((MyActivity)getActivity).doSomething(); method.

The second way is to make our activity implement an interface defined in our fragment, and make the instance of the activity a listener of this interface in our fragment during the onAttach(Activity) method. We will explain this software pattern in Chapter 4, Concurrency and Software Design Patterns. For the other way around, to get an Activity to communicate with a fragment (if we don't have fragment A instantiated in a variable in our activity), we can find the fragment in the manager. A fragment can be found using the ID of the container or a tag that we will take a look at in the following section:

FragmentManager fm = getSupportFragmentManger();
FragmentA fragmentA = fm.findFragmentById(R.id.fragment_container);
fragmentA.doSomething(params);

The final scenario would be in fragment A and speaking to B; for this, we just need to retrieve the manager from the activity and find the fragment. Run the following code:

FragmentManager fm = getActivity().getSupportFragmentManger();
FragmentA fragmentA = fm.findFragmentById(R.id.fragment_container);
fragmentA.doSomething(params);

Fragments stack

We have been speaking about finding a fragment in fragment manager and this is possible thanks to the Fragment Manager stack of fragments where we can add or remove fragments during transactions.

When we want to display a fragment dynamically, we can decide whether we want to add the fragment to the stack or not. Having the fragment on the stack allows us to navigate back to the previous fragment.

This is important for our example; if the user is on the first tab and clicks on an item on the list, we want him/her to see the details screen, DetailsFragment. Now, if the user is on DetailsFragment and clicks on the back button, we don't want him/her to leave the App; we want the app to navigate back to the fragment stack. This is why when we add DetailsFragment, we have to include the addToBackStack(String tag) option. The tag can either be null, or it can be a String type that will allow us to find this new fragment by the tag. It will look similar to the following:

FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.replace(R.id.simple_fragment, newFragment);
ft.addToBackStack(null);
ft.commit();

To clarify further, if we wanted to navigate between three fragments, A to B to C, and then navigate back, having a stack will allow us to go C to B to A. However, if we don't add the fragments to the back stack or if we add or replace them in the same container, A to B to C, this will leave us with only the C fragment and without the back navigation.

Now, to implement the back navigation in DetailsFragment, we have to let the activity know that when I click on back, I want to first navigate back in the fragment before quitting the app, as it does by default. This can be done by overriding onKeyDown and handling the fragment navigation if there is more than one fragment in the back stack. Run the following command:

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK && getSupportFragmentManager.getBackStackEntryCount > 1) {
getSupportFragment.popBackStack();
return true;
}
return super.onKeyDown(keyCode, event);
}