The process of dynamically managing fragments commonly involves multiple steps. The steps may be simple like removing one fragment and adding another, or they may be more complex, involving the removal and addition of multiple fragments. In any case, we need to be certain that all dynamic changes to the fragments within an activity that constitute a shift from one application screen to the next occur together as a single unit of work. Android does this by grouping the steps into transactions using the FragmentTransaction class.
Conceptually, the FragmentTransaction class behaves in a manner consistent with other transaction models: start the transaction, identify the desired changes, and commit the transaction once all changes within that unit of work are identified.
When we're ready to make changes, we start a new FragmentTransaction instance by calling the beginTransaction method on the activity's FragmentManager instance, which returns back a reference to a FragmentTransaction instance. We then use the new FragmentTransaction instance to identify the desired changes to the list of displayed fragments within the activity. While we're in the transaction, these changes are queued up but not yet applied. Finally, when we've identified all the desired changes, we call the FragmentTransaction class' commit method.
Once all the changes in the transaction are applied, our application display is updated to reflect those changes, giving the user the feel of moving to a new screen of our application. Although a number of steps have occurred within our application, from the user's perspective everything behaves just as if we had displayed a new activity.
The call to the commit method does not apply the changes immediately.
When we work with the FragmentTransaction class, we are not doing any direct work on the application user interface. Instead, we're building a To-Do list of work to be done to the user interface in the future. Each method that we call on a FragmentTransaction instance adds another To-Do item to the list. When we're done adding to the To-Do list and we call the commit method, those instructions get packaged up and sent to the main UI thread's message queue. The UI thread then walks through the list, performing the actual user interface work on behalf of the FragmentTransaction instance.
The deferred execution of the work performed within a FragmentTransaction instance works well in most cases. It can, however, create problems if our application code needs to find a fragment or interact with a view that is added by a fragment immediately following the call to the commit method. Although such a requirement is not normally the case, it does sometimes come up.
If we do have such a requirement, we can force the FragmentTransaction instance's work to be executed immediately by calling the FragmentManager class' executePendingTransactions method after the call to the FragmentTransaction instance's commit method. When a call to the executePendingTransactions method returns, we know that all the committed FragmentTransaction work has been performed.
We need to be careful by only calling the executePendingTransactions method on the main UI thread; this method causes the pending user interface work to execute, and therefore triggers direct interaction with the user interface.
There are a number of methods available on the FragmentTransaction class to manipulate the fragments within an activity, but the most fundamental are the add
and remove
methods.
The add method allows us to place a newly created fragment instance within a specific view group of our activity as shown here:
// Begin the transaction FragmentManager fm = getFragmentManager(); FragmentTransaction ft = fm.beginTransaction(); // Create the Fragment and add BookListFragment2 listFragment = new BookListFragment2(); ft.add(R.id.layoutRoot, listFragment, "bookList"); // Commit the changes ft.commit();
We first create a new FragmentTransaction instance using the activity's FragmentManager instance. We then create a new instance of our BookListFragment2 class and attach it to the activity as a child of the LinearLayout view group identified by the R.id.layoutRoot ID value. Finally, we commit the FragmentTransaction instance indicating that we're done making changes.
The string value, "bookList", that we pass as the third parameter to the add method is simply a tag value. We can use the tag value to later locate the fragment instance in much the same way as we might use the id value. When adding fragments dynamically, we use tags as identifiers rather than id values simply because there is no way to associate an id value with a dynamically added fragment.
The tag value comes in handy when we're ready to display a different fragment because we need to have a reference to the existing fragment to pass to the remove method so that we can remove it before adding a new fragment. The following code shows how we can update the display to show the BookDescFragment class in place of the BookListFragment2 class we added in the previous code:
FragmentManager fm = getFragmentManager();
Fragment listFragment = fm.findFragmentByTag("bookList");
BookDescFragment bookDescFragment = new BookDescFragment();
FragmentTransaction ft = fm.beginTransaction();
ft.remove(listFragment);
ft.add(R.id.layoutRoot, bookDescFragment, "bookDescription");
ft.commit();We begin by using the tag value to find our existing BookListFragment2 instance using the FragmentManager class' findFragmentByTag method. We then create an instance of the new fragment we wish to add. Now that we have references to the fragment we want to remove and the one we want to add, we begin fragment transaction. Within the transaction, we remove the BookListFragment2 instance by passing the reference to the FragmentTransaction class' remove method and then add the new fragment using the add method just as we did earlier. Finally, we call the commit method to allow the changes to be made.
This process of removing the fragment instance under a particular view group and adding another in its place occurs frequently enough that the FragmentTransaction class includes a convenient method named replace. The replace method allows us to simply identify the information for the fragment we wish to add. It takes care of the details of removing any other fragments that may exist within the target view group. Using the replace method, the code to remove the BookListFragment2 instance and add the BookDescFragment instance can be written as follows:
FragmentManager fm = getFragmentManager(); bookDescFragment = new BookDescFragment(); FragmentTransaction ft = fm.beginTransaction(); ft.replace(R.id.layoutRoot, bookDescFragment, "bookDescription"); ft.commit();
Notice that this code, with the exception of the method name, is identical to the case of simply adding a fragment. We create our fragment instance, and then within the FragmentTransaction call, the replace method passes the id of the target view group, fragment instance, and tag. The replace method handles the details of removing any fragment that may currently be within the R.id.layoutRoot view group. It then adds the BookDescFragment instance to the view group.
As we move to this model of managing our application screens as fragments, we need to be sure that we're providing the user with an experience consistent with their expectations. An area that requires special attention is our application's handling of the back button.
When a user interacts with the applications on their device, they naturally move forward through various application screens. The normal behavior is that a user can move back to a previous screen at any time by tapping the back button. This works because each time an application displays a new activity, Android automatically adds that activity to the Android back stack. This results in the expected behavior of the user moving to the previous activity with each tap of the back button.
This behavior is based on the assumption that one activity equals one application screen; an assumption that is no longer correct. When we transition the user from one application screen to another using the FragmentTransaction class, the application continues to display the same activity, leaving the back stack with no awareness of our application's new screen. This results in the application appearing to jump back multiple screens in response to the user tapping the back button because the back stack returns the user directly to the previous activity ignoring any intermediate changes made to the current activity.
The following figure demonstrates the issue:

startActivity method to display an instance of Activity1. Activity1 is automatically added to the back stack and is currently at the top of the stack.Activity1 then calls the startActivity method to display Activity2, which uses the FragmentTransaction.add method to add FragmentA. Activity2 is automatically added to the top of the back stack.Activity2 uses the FragmentTransaction.replace method to display FragmentB in place of FragmentA. As far as the user is concerned, the application is displaying a new screen showing the contents of FragmentB. The problem is that the back stack is unchanged.FragmentA, but instead when Android pops the back stack, the next screen it encounters is Activity1.We resolve this issue by calling the FragmentTransaction class' addToBackStack method within the FragmentTransaction instance that displays FragmentB. The addToBackStack method adds the changes within the transaction to the top of the back stack. This allows the user to use the back button to move through the application screens created within the FragmentTransaction instance just as one does with screens created by showing an activity.
We can call the addToBackStack method at any point during the transaction prior to calling the commit method. The addToBackStack method optionally accepts a string parameter that can be used to name the location in the back stack. This is useful if you wish to programmatically manipulate the back stack later, but in most cases this parameter value can be passed as null. We'll see the addToBackStack method in action shortly as we modify our application to use a more adaptive layout.