In order to support larger screen devices like tablets, Google introduced fragments with API level 11. A fragment is a portion of an activity and can help manage a portion of the screen. We can think of a fragment as a mini-activity within an activity. FIGURE 9.1 shows a screen for Version 4 of this chapter’s app divided into three parts whose backgrounds are blue, red, and green. We can organize that screen so that each part is a fragment. Fragments are reusable. They can be reused within the same app or in a different app. Thus it is desirable to use them.
In this chapter, we demonstrate how to use and manage fragments. We build an app to play the hangman game. We use the Model-View-Controller architecture but we keep the Model as simple as possible so that we can focus on how fragments work. Furthermore, we also discuss how to nest layouts so that each fragment is managed by a separate layout manager.
The Model is the Hangman
class, shown in EXAMPLE 9.1. It encapsulates a game where the user has to guess all the letters in a word but has only so many tries to do it. We keep our Model to the very minimum, and limit the number of possible words to just a few.
We provide a default value for the number of allowed guesses at line 6 and an array of hard coded words at line 7. At line 8, the instance variable word
stores the word to guess. The guessesAllowed
and guessesLeft
instance variables keep track of the number of guesses allowed and the number of guesses that the user still has. Finally, the indexesGuessed
array keeps track of the indexes of the letters in the String
word
that the user has guessed correctly so far. A value of true
means a correct guess at that index.
The Hangman
constructor, coded at lines 13–23, sets the value of the number of guesses allowed (lines 14–17). It then randomly selects a word within the array words
(lines 19–21), and instantiates the indexesGuessed
array. We could envision a much more complex Model where another constructor could pull a list of possible words from a file, a database, or even a remote website, and then selects one at random.
We provide methods to play the game (lines 33–43), retrieve the state of completion of the word (lines 45–53), and check if the game is over (lines 55–69). We also provide accessors for guessesAllowed
and guessesLeft
(lines 25–27 and 29–31).
Figure 9.1 shows an actual game. At the bottom of the left pane, are the number of guesses that the user still has. Inside the upper right pane, are the letters that the user has correctly guessed so far, and we provide an EditText
to input the next letter. At the bottom of the right pane, is the status or final result of the game.
Since fragments have been available since API level 11, we need to specify API level 11 or higher when we create the app (at the time of this writing, the default is API level 15).
A fragment must always be embedded inside an activity and although a fragment has its own life-cycle methods, they depend directly on the fragment’s parent activity’s life-cycle methods. For example, if the activity is stopped or destroyed, then the fragment is automatically stopped or destroyed. An activity can have several fragments inside it, and as long as the activity is running, these fragments can have their own life cycles independently of each other. A fragment is reusable and can be used in several activities. A fragment can define its own layout. It may also be invisible and perform tasks in the background.
In order to code our own user-defined fragments, we extend the Fragment
class, a direct subclass of Object
.
The Fragment
class has several subclasses that encapsulate specialized fragments. FIGURE 9.2 shows some of them. A DialogFragment
displays a dialog window, which is displayed on top of its activity’s view. A ListFragment
displays a list of items and provides event handling when the user selects one of these items. A PreferenceFragment
shows a hierarchy of preferences as a list. A WebViewFragment
displays a web page.
These subclasses of the Fragment
class encapsulate specialized fragment functionalities. For example, the ListFragment
class is very useful when we want to display a list of items that the user can interact with in some part of the screen. Clicking on an item may trigger some changes inside another fragment on another part of the screen.
In this chapter, in order to better understand how fragments work, we build our own subclasses of Fragment
. We explore the following topics:
▸ How to create and add a fragment for an activity using XML only.
▸ How to create and add a fragment for an activity using XML and code.
▸ How to create and add a fragment for an activity using code only.
▸ How a fragment and its activity communicate.
▸ How to create and add an invisible fragment.
▸ How to make a fragment reusable.
In Version 0 of the app, we show how we define and add a fragment; we do not use the Model yet and we keep the app as simple as possible. We choose the Empty Activity template when we create the app.
EXAMPLE 9.2 shows the activity_main.xml file. There are two elements inside the top LinearLayout
element: a fragment
element, defined at lines 9–15, and a LinearLayout
element, defined at lines 17–37. At line 11, we specify that this fragment is an instance of the GameControlFragment class, which we code later in this section. The top LinearLayout
element’s orientation is horizontal
(line 6), so the fragment is positioned on the left side of the screen, and the LinearLayout
on the right side of the screen. They both have the same android: layout_weight
value of 1
(lines 13 and 20), so they both occupy 50% (= 1/(1 + 1)) of the screen, as shown in Figure 9.1 (the fragment is in red, the LinearLayout
in blue and green) and illustrated in FIGURE 9.3. The android:layout_weight
XML attribute of the LinearLayout. LayoutParams
class enables us to specify how much space each View
should occupy within its parent LinearLayout
. We specify the value of 0dp
for the android:layout_width
attribute for both the fragment
and LinearLayout
elements at lines 14 and 21. This is typical when the android_layout:weight
attribute is used. The 0dp
value means that we want to let the system decide how much space is allocated to the various elements based on the values for the layout_weight
attributes.
If we change the android:layout_weight
values to 1 and 3, we get screen proportions of 25% (1/(1 + 3)) and 75% (3/(1 + 3)), as shown in FIGURE 9.4.
Inside the LinearLayout
element, we define two LinearLayout
elements at lines 23–28 and 30–35. Because that LinearLayout
element has its orientation vertical
(line 18), the two LinearLayout
elements will be at the top and bottom of it (showing in blue and green in Figure 9.1). They have the same android:layout_weight
value of 1
(lines 27 and 34), so they both occupy 50% of the right side of the screen (the heights of the blue and green parts are the same on Figure 9.1). To better visualize their position, we color them in blue (line 26) and green (line 33), respectively. Later in this chapter, we will place a fragment in each of them. We must give an id to the fragment
element (line 10). If we do not, the app will crash at runtime.
EXAMPLE 9.3 shows the fragment_game_control.xml file (which we place in the layout directory). We use this layout XML file for the left pane of the screen, reserved by the fragment
element in Example 9.2. It is managed by a LinearLayout
(and its background is red (line 5)).
Like an activity, a fragment has several life-cycle methods, as shown in TABLE 9.1. The methods identified with “a” in the table are automatically called when a fragment is created or restarted. The methods identified with “b” are automatically called when a fragment becomes inactive or is closed. Note that all these methods are public
, contrary to the life-cycle methods in the Activity
class, which are protected
. When we want to inflate a fragment from a layout XML file, we must override the onCreateView
method to do so. The onCreateView
method provides a LayoutInflater
parameter that enables us to inflate an XML file using its inflate
method, shown in TABLE 9.2. Its ViewGroup
parameter represents the ViewGroup
inside which the fragment is to be placed. The Bundle
parameter contains data from the previous instance of the fragment. That data can be read when a fragment is resumed.
TABLE 9-1 Life-cycle methods of the Fragment
class
Step | Method | Called |
---|---|---|
1a | void onAttach( Context context ) | When the fragment is attached to its context. |
2a | void onCreate( Bundle savedInstanceState ) | When the fragment is created. |
3a | View onCreateView( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState ) | After onCreate; creates and returns the View associated with this fragment. |
4a | void onViewCreated( View view, Bundle savedInstanceState ) | After onCreateView, but before any saved state has been restored to the View. |
5a | void onActivityCreated( Bundle savedInstanceState ) | When the fragment’s activity has finished executing its own onCreate method. |
6a | void onViewStateRestored( Bundle savedInstanceState ) | When the saved state of the fragment’s View hierarchy has been restored (requires API level 17). |
7a | void onStart( ) | When the fragment becomes visible to the user. |
8a | void onResume( ) | When the fragment is visible to the user, who can start interacting with the fragment. |
4b | void onPause( ) | When the activity is being paused or another fragment is modifying it. |
3b | void onStop( ) | When the fragment is no longer visible to the user. |
2b | void onDestroyView( ) | After onStop; the View created by onCreateView has been detached from this fragment. |
1b | void onDestroy( ) | When the fragment is no longer in use. |
0b | void onDetach( ) | Just before the fragment is detached from its activity. |
TABLE 9.2 Selected inflate
methods of the LayoutInflater
class
View inflate( int resource, ViewGroup root, boolean attachToRoot ) | Resource is an id for an XML resource that is being inflated. Root is an optional View to be the parent of the inflated layout if attachToRoot is true. If attachToRoot is false, root is only used to set the correct layout parameters for the root view in the XML resource. |
EXAMPLE 9.4 shows the GameControlFragment
class. At this point, we also keep it to a minimum: it only inflates the fragment_game_control.xml file at lines 16–17, inside the onCreateView
method (lines 13–18). The first argument we pass to the inflate
method is the resource id for the fragment, fragment_game_control
. The second argument is the container that the fragment goes into, the ViewGroup
passed by the onCreateView
method, container
. The third argument is false
because in this case, the inflated XML file is already inserted into container
. Specifying true
would create a redundant ViewGroup
hierarchy.
When an activity state is restored, any fragment belonging to the activity is re-instantiated and its default constructor is called automatically. Thus, any fragment class must have a default constructor (lines 10–11).
FIGURE 9.5 shows a preview of the app, Version 0, in landscape orientation.
We can check what methods of the Activity
and Fragment
classes are automatically called and in what order by adding Log statements inside the various life-cycle methods of both the MainActivity
and the GameControlFragment
classes. If we add them to both classes, we would see the output shown in FIGURE 9.6 as the user starts the app and interacts with the app and the device. It typically shows two things:
▸ When the activity is created or restarted, a method of the Activity
class executes first and a similar method of the Fragment
class executes afterward.
▸ When the activity becomes inactive or is closed, a method of the Fragment
class executes first and a similar method of the Activity
class executes afterward.
In Version 1 of the app, we populate the left area of the screen (the fragment) with the two GUI components, and we start using the Model.
EXAMPLE 9.5 shows the updated activity_main.xml file. We give ids to the LinearLayout
elements (at lines 24 and 32) at the top right and bottom right of the screen so that we can access them later to place fragments in them. We change from hard coded colors to colors defined in the colors.xml file (lines 27 and 35), as shown in EXAMPLE 9.6.
EXAMPLE 9.7 shows the updated fragment_game_control.xml file. We place two Linear-Layouts
(lines 9–14, 16–20) in it: they each contain a basic GUI component (a button and a label). By placing one component per layout manager and giving each layout manager the same android: layout_weight
value of 1
via the wrapCenterWeight1
style at lines 9 and 16, we space out the components evenly. The wrapCenterWeight1
style, which also sets android:layout_width
and android:layout_height
to wrap_content
and android:gravity
to center
, is defined in the styles.xml file (lines 11–16), shown in EXAMPLE 9.9. The use of styles simplifies the coding of our layout XML files.
We give a background color defined in colors.xml to the outer LinearLayout
(line 6). The button is styled at line 12 using the buttonStyle
style, defined in Example 9.9 at lines 31–33. ButtonStyle
inherits from textStyle
, defined at lines 18–23 in Example 9.9.
The TextView
element (lines 17–19) is styled with the textStyle
style at line 19. The TextView
element stores how many guesses the user has left. We retrieve that value from the Model. The button, whose text says PLAY (line 11—the value of the play String
in the strings.xml file—line 3 of EXAMPLE 9.8), triggers execution of the play
method when the user clicks on it (line 13).
We keep the MainActivity
class, shown in EXAMPLE 9.10, to a minimum. It includes a Hangman
instance variable representing the Model at line 10. Since the onCreate
method can be called several times during the life cycle of this activity, we only instantiate it (line 16) if it has not been instantiated before (i.e., if it is null
). We call the getGuessesLeft
method of the Hangman class with it at line 19 in order to set the text inside the TextView
of the fragment. We retrieve that TextView
at line 18 using the findViewById
method. The class also includes the play
method (lines 22–23), which is implemented as a do-nothing method at this point (if we do not implement it, if we run and click on the button, the app will stop working).
FIGURE 9.7 shows the app, Version 1, running in horizontal orientation.
In Version 2, we display the fragment on the top right pane (with the blue background) of the screen showing the state of completion of the word, and an EditText
for the user to enter a letter. We code the fragment in an XML file, fragment_game_state.xml, and create the fragment by code.
Furthermore, in order to better focus on fragments and keep things simple, we run the app in horizontal orientation only by adding this line to the AndroidManifest.xml file inside the activity
element:
android:screenOrientation=”landscape”
EXAMPLE 9.11 shows the fragment_game_state.xml file. It includes a TextView
, which shows the state of completion of the word and an EditText
, where the user can enter a letter. We give ids to these two elements at lines 11 and 17 so that we can later retrieve them using the findViewById
method. They are styled with textSyle
and editTextStyle
, which are both defined in the styles.xml file shown in Example 9.9. Like in Example 9.7 for the left pane fragment, we wrap each element around a LinearLayout
element styled with the wrapCenterWeight1
style, so that the elements are centered and spaced evenly.
We want to close the soft keyboard when the user touches the Done
key. In order to do that, we set the value of the android:imeOptions
attribute, inherited from TextView
, to actionDone
at line 19. IME stands for Input Method Editor, and it enables the user to enter text. It is not restricted to a keyboard—the user can also enter text by voice. The attribute android:imeOptions
enables us to specify the options that we want to enable.
We need to show the game state fragment when the app starts. We first need to create the fragment and add it to the activity, then set the text inside the TextView
element of the fragment. For that, we can call the currentIncompleteWord
method of our Model. To incorporate a fragment into an activity, we need to implement the following steps:
▸ Get a reference to the fragment manager for this Activity
.
▸ Create a fragment transaction.
▸ Create a fragment.
▸ Have the fragment transaction add the fragment to a container (a ViewGroup
).
▸ Commit the fragment transaction.
The FragmentManager
abstract
class provides the functionality to interact with fragments within an activity. A reference to the FragmentManager
for the current Activity
can be obtained by calling the getFragmentManager
method, shown in TABLE 9.3, with the Activity
reference. TABLE 9.4 lists some methods of the FragmentManager
class.
TABLE 9.3 The getFragmentManager method of the Activity
class
Method | Description |
---|---|
FragmentManager getFragmentManager( ) | Returns the FragmentManager for this Activity. |
TABLE 9.4 Selected methods of the FragmentManager
class
Method | Description |
---|---|
FragmentTransaction beginTransaction( ) | Starts a series of operations for this fragment manager. Returns a FragmentTransaction. |
Fragment findFragmentById( int id ) | Returns a Fragment identified by its id. |
Fragment findFragmentByTag( String tag ) | Returns a Fragment identified by its tag. |
Thus, we can obtain a FragmentManager
reference for the current Activity
using:
FragmentManager fragmentManager = getFragmentManager();
To obtain a FragmentTransaction
for the preceding FragmentManager
, we write:
FragmentTransaction transaction = fragmentManager.beginTransaction( );
The FragmentTransaction
class provides methods (see TABLE 9.5) to perform fragment operations, such as adding a fragment to an activity, removing it, replacing it, hiding it, showing it, etc. These methods return the FragmentTransaction
that calls them, so that we can chain method calls. There are other methods, for example, to define animations or transitions when a fragment transaction is being performed. It is an abstract
class so we cannot instantiate a FragmentTransaction
object with a constructor. We can use the beginTransaction
method of the FragmentManager
class to instantiate a FragmentTransaction
object.
After having obtained a FragmentTransaction
named transaction
for an Activity
, we can add a GameStateFragment
whose id is game_state
to that Activity
using the following code:
GameStateFragment fragment = new GameStateFragment( ); transaction.add( R.id.game_state, fragment ); transaction.commit( );
When performing a fragment transaction, for example, adding or removing a fragment to or from an activity, we can add it to the back stack of the activity by calling the addToBackStack
method before calling the commit
method. The back stack keeps track of the history of these fragment transactions within an activity so that the user can revert to a prior fragment by pressing the Back button of the device.
TABLE 9.5 Selected methods of the FragmentTransaction
class
Method | Description |
---|---|
FragmentTransaction add( int containerViewId, Fragment fragment, String tag ) | Adds a fragment to an activity. ContainerViewId is the resource id of the container that the fragment will be placed in; fragment is the fragment to be added. Tag is an optional tag name for the fragment. The added fragment can later be retrieved by the fragment manager using the findFragmentByTag method. Returns the FragmentTransaction reference calling this method; thus, method calls can be chained. |
FragmentTransaction add( int containerViewId, Fragment fragment ) | Calls the method above with a null tag parameter. |
FragmentTransaction add( Fragment fragment, String tag ) | Calls the method above with a resource id parameter equal to 0. |
FragmentTransaction hide( Fragment fragment ) | Hides fragment (i.e., hides its View). |
FragmentTransaction show( Fragment fragment ) | Shows fragment (i.e., shows its View if it had been hidden before—the default is that the fragment is shown). |
FragmentTransaction remove( Fragment fragment ) | Remove fragment from its activity. |
FragmentTransaction replace( int containerViewId, Fragment fragment, String tag ) | Similar to the first add method, but instead replaces an existing fragment inside the container whose id is containerViewId. |
FragmentTransaction replace( int containerViewId, Fragment fragment ) | Calls the method above with a null tag parameter. |
int commit( ) | Schedules that fragment transaction to be committed. |
FragmentTransaction addToBackStack( String name ) | Adds this transaction to the back stack of the activity; name is optional and stores the name of that back stack state. |
EXAMPLE 9.12 shows the updated MainActivity
class. When the app starts, the onCreate
method executes, and we create the fragment and attach it to the activity. At line 23, we get a reference to the fragment manager for this activity by calling the getFragmentManager
method from the Activity
class. We only want to create a new GameStateFragment
and add it to the activity if it has not been created yet. Using the findFragmentById
method at line 24, we check if there is already a fragment with the id game_state
within this activity. If there is, we do nothing. If there is not, we create one and add it to the activity at lines 25–28.
At line 25, using the FragmentManager
reference, we get a FragmentTransaction
reference, transaction
. At line 26, we define and instantiate a GameStateFragment
reference, fragment
. At line 27, we add fragment
to this Activity
, placing it inside the ViewGroup
(a LinearLayout
in this case) whose resource id is game_state
. Finally, we commit the fragment transaction at line 28. At this point, we still need to place the text of the puzzle inside the TextView
of the puzzle fragment that we just added to the activity. In order to do that, we first need to access the View
of the fragment and use the findViewById
method, passing the id of the TextView
as its argument. However, at this point the View
of the fragment is null
, so attempting to use it would result in a NullPointerException
, crashing the app. Thus, we do this inside the GameStateFragment
class. Thus, in the GameStateFragment
class, we need to access the value of the current incomplete word of the hangman game. To that end, we provide the getGame
method (lines 32–34) in the MainActivity
class. From the GameStateFragment
class, we can access the activity by calling the getActivity
method (see TABLE 9.6), inherited from the Fragment
class. With a MainActivity
reference, we can access the game by calling getGame
, and then access the incomplete word as follows:
TABLE 9.6 The getActivity
and getView
methods of the Fragment
class
Method | Description |
---|---|
Activity getActivity( ) | Returns the activity that this fragment belongs to. |
View getView( ) | Returns the root View for this fragment. |
MainActivity fragmentActivity = ( MainActivity ) getActivity( ); String currentWord = fragmentActivity.getGame( ).currentIncompleteWord( );
EXAMPLE 9.13 shows the GameStateFragment
class. Since it inherits from Fragment
, we provide a mandatory default constructor at lines 12–13. We inflate the fragment_game_state.xml file at lines 18–19 inside the onCreateView
method. When the fragment transaction is committed at line 28 of Example 9.12, the constructor, the onAttach
, onCreate
, onCreateView
, onActivityCreated
, and onStart
methods of the Fragment
life cycle are called in that order. When the onStart
method (lines 22–30) executes, the fragment’s View
and the TextView
inside it have been instantiated. Thus, we can set the text inside the TextView
at that point.
We get a reference to the fragment’s View
at line 24 using the getView
method (shown in Table 9.6), inherited from the Fragment
class. With it, we can call the findViewById
method to access any View
inside it that has been given an id. At lines 25–26, we call it and assign the TextView
whose id is state_of_game
to gameStateTV
. We get a reference to the parent Activity
of this fragment at line 27, casting it to a MainActivity
. We chain method calls to getGame
and currentIncompleteWord
in order to retrieve the game’s incomplete word and assign the resulting value to the text of gameStateTV
at lines 28–29.
FIGURE 9.8 shows the app running in the emulator after the user enters the letter U
and hits the Done button on the soft keyboard.
In Version 3, we display the fragment on the bottom right pane (with the green background) of the screen showing a message about the result of the game in a TextView
. This time, we do not use an XML file to define the fragment, we define and create it entirely by code.
Defining the fragment’s Graphical User Interface (GUI) is similar to defining a View by code instead of using an XML file. We do this in the GameResultFragment
class. Creating the fragment is identical to creating a game state fragment. It takes place in the MainActivity
class.
EXAMPLE 9.14 shows the updated MainActivity
class. It is very similar to its previous version shown in Example 9.12. We add a GameResultFragment
in the LinearLayout
whose id is game_result
(lines 31–36).
In the GameResultFragment
class, we define the fragment by code. This fragment only has a TextView
in it. Furthermore, we need to access that TextView
to set its text when the game ends. Rather than giving the TextView
an id when we create it, we declare it as an instance variable so that we have a direct reference to it inside the GameResultFragment
class (line 12 of EXAMPLE 9.15). The onCreateView
method, at lines 14–20, calls the setUpFragmentGui
method at line 17 in order to create the GUI for this fragment. It returns the View
returned by its super
method at lines 18–19. The setUpFragmentGui
method is coded at lines 22–28. Since it is possible that onCreateView
is called several times during the life cycle of the fragment, we can in turn expect setUpFragmentGui
to be called several times too. Thus, we want to instantiate gameResultTV
(line 24) and add it to the ViewGroup
that is the root View
of the fragment (line 26) only once, when it is null
(line 23). We center the text inside gameResultTV
at line 25.
In the onStart
method (lines 30–33), which is automatically called after onCreateView
and onActivityCreated
, we hard code the text inside gameResultTV
to “GOOD LUCK” (line 32).
We need to center the TextView
of GameResultFragment
within its parent LinearLayout
. Inside the activity_main.xml file, we add the following attribute-value pair to the last LinearLayout
element (the one whose id is game_result
and is located in the right bottom pane):
android:gravity=”center”
FIGURE 9.9 shows the app running in the emulator. All three fragments are now present:
▸ The left pane (game control) fragment in red is defined and created using XML.
▸ The top right pane (game status) fragment in blue is defined in XML and created by code.
▸ The bottom right pane (game result) fragment in green is defined and created by code.
We have now created an activity with three fragments inside, defined and created in different ways. In Version 4, we complete the app by processing the user’s letter and enabling the user to play the game. We also illustrate how fragments can communicate with their activity when processing an event.
The user enters a letter in the game state fragment, then clicks on the PLAY button on the left pane, which triggers execution of the play
method inside the MainActivity
class. To enable play, we do the following:
▸ Capture the letter entered by the user.
▸ Call the guess
method of Hangman.
▸ Update the number of guesses left in the TextView
inside the left pane.
▸ Update the incomplete word in the TextView
inside the top right pane.
▸ Clear the EditText.
▸ If the game is over, update the message in the TextView
inside the bottom right pane and clear the hint in the EditText.
EXAMPLE 9.16 shows the play
method. We retrieve the EditText
using its id at line 46 and assign it to the variable input
. At line 47, we get an Editable
reference for input
. If it is not null
and contains at least one character (line 48), we process the first character as the user’s play. Otherwise, we do nothing.
At lines 49–53, we play and update the number of guesses left. Since we gave the TextView
that displays the number of guesses left an id (status
) in the fragment_game_control.xml file, we retrieve it using the findViewById
method at line 52. We call getGuessesLeft
with game
at line 53 to retrieve the number of guesses left and display it inside the TextView
.
At lines 55–62, we update the state of the incomplete word. We first get a reference to the GameStateFragment
at lines 56–58, then get a reference to the fragment’s View
at line 59. With it, we call findViewById
at lines 60–61 in order to get a reference to the TextView
whose id is state_of_game
. We call the currentIncompleteWord
of Hangman
to set the text of that TextView
at line 62. At lines 64–65, we clear the EditText
.
We then check if the game is over at line 67. If it is not, we do nothing. If it is (line 68), we clear the hint in the EditText
(lines 78–79), and we tell the user the result of the game. In order to do this, we need to access the TextView
inside the GameResultFragment
; however, that TextView
does not have an id. Thus, in order to access it, we get a reference to the GameResultFragment
that contains it (lines 69–70) with the FragmentManager
and call setResult
, a new method of the GameResultFragment
class, with it (lines 72–76).
EXAMPLE 9.17 shows the setResult
method of the GameResultFragment
class. The rest of the class is unchanged. It sets the text of the gameResultTV
instance variable.
Figure 9.1, at the beginning of the chapter, shows the app running at some point in the game. FIGURE 9.10 shows the app running after the user won. The TextView
that was previously showing either a hint or a letter is no longer visible because its contents are empty and its android: layout_width
and android:layout_height
attributes are set to wrap_content
.
We can also use a fragment that can perform some work in the background of the app, without any visual representation. For this, we use the add
method of the FragmentTransaction
class that does not have a View
id parameter. If we want to be able to retrieve the fragment later, we should provide a tag name. Such a fragment could perform some work in the background of the app while the user is allowed to interact with the app. It could, for example, retrieve some data from a file, a database, a remote URL, or possibly use the device’s GPS to retrieve some live location data.
To demonstrate how to use such a fragment, we keep our example to a minimum. Our fragment is hard coded and includes the warning
method. We will give a warning to the user when he or she has only one guess left and display the String
returned by the warning
method. That information normally comes from the Model, but in the interest of keeping things as simple as possible, we hard code it in this class. EXAMPLE 9.18 shows the BackgroundFragment
class. Because this fragment is not visible, the onCreateView
method is not called. Thus, we did not code it. At lines 10–12, we code the warning
method, which we call from the MainActivity
class.
EXAMPLE 9.19 shows parts of the updated MainActivity
class. We create an invisible BackgroundFragment
in the onCreate
method at lines 40–45, without waiting for the user to interact with the app. This strategy can be used in apps where we need to bring data from an outside source. At line 43, we use the add
method of the FragmentTransaction
class with a Fragment
and a String
parameter. The String
represents a tag name for the fragment, and later we can retrieve that fragment via its tag name, background
, using the findFragmentByTag
method of the FragmentManager
class. We use the fragment in the play
method (lines 52–99). If the user has only one guess left (line 75), we display a warning inside the TextView
of the GameResultFragment
. We retrieve the BackgroundFragment
based on its tag at lines 76–77 and the GameResultFragment
based on its id at lines 78–79. We then call setResult
with the GameResultFragment
, passing the warning retrieved by calling the warning
method with the BackgroundFragment
(line 81).
FIGURE 9.11 shows the app running in the emulator. The user only has one guess left.
Version 5 of the app works, but the GameStateFragment
class is not really reusable. For example, at line 27 of Example 9.13, the use of the MainActivity
reference and the call to getGame
at line 28 inside the GameStateFragment
class assume that the MainActivity
class exists and includes the method getGame
.
EXAMPLE 9.20 shows a better implementation, making the GameStateFragment
class more reusable. Inside the fragment class, we use an inner interface, which we name Callbacks
(lines 15–17), and transfer all the method calls using a MainActivity
reference inside the fragment class to the MainActivity
class via that interface. Inside the fragment class, whenever we call a MainActivity
method, we add a method to the Callbacks
interface and call that method from the fragment class. The MainActivity
class implements the interface and thus must override that method. In this way, we effectively transfer the MainActivity
method calls from the fragment class to the MainActivity
class.
Another reusability issue is that the method getGame
returns a Hangman
reference. Thus, since getGame
is in the Callbacks
interface, which is inside the GameStateFragment
class, our GameStateFragment
class is only reusable with the Hangman
class. To solve that problem, we create an interface, which we name WordGame
, and make getGame
return a WordGame
reference instead. The WordGame
interface only has one method, currentIncompleteWord
. This is needed because we call that method inside the GameStateFragment
class with the WordGame
reference returned by getGame
. Thus, the GameStateFragment
class is now reusable with any activity that implements
the Callbacks
interface and with any class that implements
the WordGame
interface, which are both very simple and generic. In this app, we modify Hangman
so that it implements
WordGame
.
Here is how that method transfer mechanism is implemented in the GameStateFragment
class.
Before: Inside GameStateFragment
(old implementation), from Example 9.13:
After: Inside GameStateFragment
(new implementation), from Example 9.20:
The call to getGameFromActivity
at line 40 triggers a call by the mCallbacks
instance variable, declared and initialized at line 13, to the getGame
method of the Callbacks
interface (declared at line 16), which will execute inside a class implementing the Callbacks
interface and, therefore, implementing getGame
.
At lines 19–23, we declare and instantiate sDummyCallbacks
, an object of an anonymous class implementing the Callbacks
interface. That anonymous class overrides the getGame
method, returning null
, at lines 20–22; sDummyCallbacks
is assigned to the instance variable mCallbacks
of type Callbacks
at line 13.
The mCallbacks
instance variable is meant to be a reference to the activity that this fragment belongs to. The onAttach
method, inherited from the Fragment
class, is overridden at lines 44–51. This method is called when this fragment is attached to an activity. Furthermore, when that method is called, we expect its parameter, context
, to be an instance of a superclass of Context
(most likely a superclass of Activity
) that implements the Callbacks
interface. If it is not (line 46), we throw an IllegalStateException
at lines 47–48. If it is, the context
parameter is typecast to Callbacks
and is assigned to mCallbacks
at line 50.
In this new implementation, there is no mention of the MainActivity
class, and there is no mention of a method from the MainActivity
class named getGame
. The only requirement is that the MainActivity
class, or any activity class that we use with the GameStateFragment
class, implements the Callbacks
interface. This, in turn, forces MainActivity
, or any class that implements the Callbacks
interface, to override the getGame
method. Note that in this example, there is only one method in the Callbacks
interface, but more generally, there could be several.
The onDetach
method, inherited from the Fragment
class, is overridden at lines 53–56. That method is called when this fragment is detached from its activity. It reassigns the default Callbacks
object sDummyCallbacks
to mCallbacks
at line 55.
EXAMPLE 9.21 shows the class header of the updated MainActivity
class, which now implements the Callbacks
inner interface of GameStateFragment
. There is no other change as compared to Example 9.19: the getGame
method was already implemented before and is still the same, but it now overrides the getGame
method of the Callbacks
interface.
EXAMPLE 9.22 shows the WordGame
interface, which declares the currentIncompleteWord
method (line 4).
EXAMPLE 9.23 shows the class header of the updated Hangman
class, which now implements the WordGame
interface. There is no other change as compared to Example 9.1: the currentIncompleteWord
method was already implemented before and is still the same, but it now overrides the currentIncompleteWord
method of the WordGame
interface.
Note that we implemented Callbacks
as an inner interface of GameStateFragment
and WordGame
as a separate interface. Either can be implemented as an inner interface or as a separate interface.
Version 6 works but it is annoying to have to click on the PLAY button every time. In Version 7, we enable play as soon as the user closes the keyboard. We can either eliminate or convert the PLAY button to a “Play Another Game” button, which you can do in the exercises.
The EditText
is located inside the game state fragment, so event handling for Version 7 takes place in that fragment. OnEditorActionListener
, a public static
inner interface of the TextView
class, provides the functionality to handle a key event inside an EditText
. TABLE 9.7 lists its only method, onEditorAction
.
As always, in order to set up event handling, we do the following:
Write an event handler (a class extending a listener interface).
Instantiate an object of that class.
Register that object listener on one or more GUI components.
EXAMPLE 9.24 shows the updated GameStateFragment
class. At lines 79–94, we define the private
class OnEditorHandler
, which implements the TextView.OnEditorActionListener
interface. It overrides its only method, onEditorAction
, at lines 80–93. It does the following:
TABLE 9.7 The onEditorAction
method of the TextView.OnEditorActionListener
interface
Method | Description |
---|---|
boolean onEditorAction( TextView view, int keyCode, KeyEvent event ) | view is the TextView that was clicked; keyCode is an integer identifying the key that was pressed; event is the Key event. |
▸ Hides the keyboard (lines 82–87).
▸ Plays (lines 89–90).
Since we already have a play
method in the MainActivity
class, we can reuse its code. The existing play
method accepts a View
parameter, expected to be a Button
, which the method does not actually use. We could call it and pass null
, but it is not good practice to pass null
to a method from another class, since in theory (and in general), it could use that parameter to call methods. Thus, we create an additional play
method in the MainActivity
class (see EXAMPLE 9.25) that accepts no parameter and does exactly the same thing as the existing play
method (which we can leave as a do-nothing method—if we choose to delete it, we also need to delete the android: onClick
attribute in the fragment_game_control.xml file). We use the same strategy as in Version 6 to transfer the call to the play
method of MainActivity
via a play
method of the Callbacks
interface (declared at line 20), which we call with the fragment’s play
method (lines 71–73). Thus, the call to the fragment’s play
method at line 90 triggers a call to the Callbacks’ play
method. In this way, we keep our fragment class reusable (with a class implementing the Callbacks interface).
In order to close a keyboard by code, we can use an InputMethodManager
reference. The InputMethodManager
class manages the interaction between an application and its current input method. That class does not have a constructor but we can obtain an InputMethodManager
reference by calling the getSystemService
of the Context
class using an Activity
reference (which is a Context
reference since Activity
inherits from Context
).
The getSystemService
method has the following API:
Object getSystemService( String nameOfService )
Its String
parameter represents a service, and TABLE 9.8 lists some of the possible values. Depending on the service specified, it returns a manager object reference for that service. For example, if we specify LOCATION_SERVICE
, it returns a LocationManager
reference that we can use to gather location data from the device’s GPS system. If we specify WIFI_SERVICE
, it returns a WifiManager
reference that we can use to access data regarding the device’s Wi-Fi connectivity. Because that method returns a generic Object
, we need to typecast the returned object reference to what we expect. We want to obtain an InputMethodManager
reference, so we pass the argument Context.INPUT_METHOD_SERVICE
and cast the resulting object reference to an InputMethodManager
as follows (and as in lines 83–84):
InputMethodManager inputManager = ( InputMethodManager ) getActivity( ).getSystemService( Context.INPUT_METHOD_SERVICE );
To close the keyboard, we use one of the hideSoftInputFromWindow
methods of the InputMethodManager
class, shown in TABLE 9.9. The first parameter has type IBinder
, an interface that encapsulates a remotable object. A remotable object is an object that can be accessed outside its context via a proxy. We want to pass to hideSoftInputFromWindow
an IBinder
reference that represents the window that the current View
(in this case the EditText
) is attached to. We call the getCurrentFocus
method of the Activity
class with the current Activity
object. It returns the View
that has the focus in the current window. Using that View
, we call the get WindowToken
of the View
class. It returns a unique token that identifies the window that this View
is attached to. At line 86, we chain these two method calls using the expression:
getActivity( ).getCurrentFocus( ).getWindowToken( )
TABLE 9.8 Selected String
constants and their values, as well as the corresponding return type of the getSystemService
method of the Context
class when using one of those constants as the method’s parameter
String Constant | String Value | getSystemService Return Type |
---|---|---|
POWER_SERVICE | power | PowerManager |
LOCATION_SERVICE | location | LocationManager |
WIFI_SERVICE | wifi | WifiManager |
DOWNLOAD_SERVICE | download | DownloadManager |
INPUT_METHOD_SERVICE | input_method | InputMethodManager |
TABLE 9.9 Selected hideSoftInputFromWindow
method of the InputMethodManager
class
Method | Description |
---|---|
boolean hideSoftInputFromWindow( IBinder token, int flags ) | token is the token of the Window making the request as returned by the getWindowToken method of the View class; flags specifies additional condition for hiding the soft input. |
TABLE 9.10 shows the two constants of the InputMethodManager
class that can be used as values for the second parameter of the hideSoftInputFromWindow
method. Since the user explicitly opens the keyboard when trying to enter some input, we should not use the HIDE_IMPLICIT_ONLY
constant in this case. We use HIDE_NOT_ALWAYS
, so the second argument of we method call at line 87 is:
InputMethodManager.HIDE_NOT_ALWAYS
When we run the app, play happens as soon as the keyboard closes. We no longer need to click on the PLAY button.
TABLE 9.10 Selected int
constants of the InputMethodManager
class to be used as the second parameter of hideSoftInputFromWindow
Constant | Value | Meaning |
---|---|---|
HIDE_IMPLICIT_ONLY | 1 | The soft input window should only be hidden if it was not explicitly open by the user. |
HIDE_NOT_ALWAYS | 2 | The soft input window should normally be hidden unless it was originally forced open by code. |
Fragments were introduced in API level 11 to provide better support for large screen devices such as tablets.
A fragment is a portion of an activity, and can help manage a portion of the screen. We can think of a fragment as a mini-activity within an activity.
There can be several fragments within one activity.
A fragment can have a user interface or simply perform some work in the background of an activity.
A fragment has its own life-cycle methods but depends on its activity running.
An activity can use the getFragmentManager
method to access its fragment manager. With it, it can access and manage its fragments.
A fragment can access its activity using the getActivity
method.
A fragment can be defined, created, and added to an activity using XML only, code only, or a mix of both.
When creating and adding a fragment to an activity, we use the FragmentTransaction
class. When adding a fragment, we can identify it with an id, a tag, or both.
A fragment manager can find fragments either by id or by tag.
A fragment can be reused in several activities.
A fragment class should be coded so that it is reusable and not tied to a particular activity class. We can do this by using an inner interface inside the fragment class.
Fragments were introduced with API level
2
8
11
13
The direct superclass of Fragment is
Content
Activity
Object
FragmentActivity
How many fragments can there be in one activity?
0
1 only
0 or more
What method of the following is not
a life-cycle method of a fragment?
onStart
onCreate
onRestart
onStop
What method can be used by a fragment to access its activity?
activity
getActivity
getFragment
findActivity
What class can be used by an activity to access its fragments?
FragmentTransaction
Activity
FragmentManager
Manager
What method of the FragmentTransaction class is used to add a fragment to an activity?
add
addFragment
insert
insertFragment
An activity can retrieve one of its fragment via its
tag only
id only
tag or id
it cannot
We can make a fragment class reusable by transferring all the method calls by its activity inside the fragment class to the activity class via
an inner class
an interface
it is not possible
Inside an activity class, retrieve the fragment manager.
Inside an activity class, add a fragment of type MyFragment and place it inside a ViewGroup element whose id is my_id.
// fm is a FragmentManager reference FragmentTransaction transaction = fm.beginTransaction( ); // Your code goes here
Inside an activity class, add a fragment of type MyFragment and place it inside a ViewGroup element whose id is my_id. Give it the tag my_tag.
// fm is a FragmentManager reference FragmentTransaction transaction = fm.beginTransaction( ); // Your code goes here
Inside an activity class, add a fragment of type MyFragment that does not have a user interface. Give it the tag my_tag.
// fm is a FragmentManager reference FragmentTransaction transaction = fm.beginTransaction( ); // Your code goes here
Inside a fragment class, write the code so that the fragment is inflated from the my_fragment.xml file.
public View onCreateView( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState ) { // Your code goes here }
Inside a fragment class, write the code to retrieve the activity that this fragment belongs to. Assume that the activity type is MyActivity.
// Your code goes here
The following fragment class is not reusable. Change it so that it is.
package com.you.myapp; import android.app.Fragment; public class MyFragment extends Fragment { public MyFragment( ) { } public void onStart( ) { super.onStart( ); MyActivity activity = ( MyActivity ) getActivity( ); activity.update( ); } } // Your code goes here
Write a mini app containing one activity using two fragments as shown below:
Write a mini app containing one activity using three fragments as shown below:
Write a mini app containing one activity using four fragments as shown below:
Write an app containing one activity using two fragments (one left and one right) as follows:
The left fragment contains a button and the right fragment contains a label that simulates a traffic light, red when we start. When the user clicks on the button, the label changes to green. When the user clicks on the button again, the label changes to yellow. When the user clicks on the button again, the label changes to red, and so on. Even though the functionality is simple, you should include a Model for this app.
Write an app containing one activity using several fragments (one at the top and one or more below) as follows:
The top fragment should occupy one fourth of the screen and contain three radio buttons, showing 1, 2, and 3. When the user selects one of them, the bottom of the screen displays the corresponding number of fragments (use a different color for each), and they all should occupy the same amount of space. For example, if the user clicks on the radio button that says 2, then two fragments of equal height are displayed at the bottom of the screen.
Write an app containing one activity using two fragments (one on top and one at the bottom) as follows:
The app simulates a flashlight. The top fragment comprises 20% of the screen and contains an on and off switch. You can use the Switch class for this. The bottom fragment comprises 80% of the screen and is black. When the user turns the switch on, the bottom fragment turns yellow. When the user turns the switch off, the bottom fragment reverts back to black. Even though the functionality is simple, you should include a Model for this app.
Write an app containing one activity using two fragments (one on top and one at the bottom) as follows:
The app simulates a dimmer. The top fragment comprises 20% of the screen and contains a slider. You can use the SeekBar class for this. The bottom fragment comprises 80% of the screen and is yellow. When the user moves the slider, the yellow fades. You can assume that the dimmer slider controls the transparency of the yellow color. Even though the functionality is simple, you should include a Model for this app.
Write an app containing one activity using two fragments (one on the left and one on the right) as follows:
The app simulates a traffic light. The left fragment contains a button and the right fragment contains three labels, the top one is red when we start and the others are transparent. When the user clicks on the button, the top label becomes transparent and the bottom label changes to green. When the user clicks on the button again, the bottom label becomes transparent and the middle label changes to yellow. When the user clicks on the button again, the middle label becomes transparent and the top label changes to red, and so on. Even though the functionality is simple, you should include a Model for this app.
Write an app containing one activity using two fragments (one at the top and one below) as follows:
The top fragment should occupy one fourth of the screen and contains a button, the bottom fragment three fourths of the screen. When the user clicks on the button, a tic-tac-toe game shows up inside the bottom fragment. Play should be enabled. You should include a Model for the tic-tac-toe game.
Write an app containing one activity using two fragments (one at the top occupying 75% of the space and one below occupying 25% of the space) as follows:
The app generates a random number between 1 and 5 and the user needs to guess it. The bottom fragment shows an EditText asking the user to enter a number. The top fragment shows some feedback to the user (for example, “You won” or “You lost”). Even though the functionality is simple, you should include a Model for this app.
Write the same app as in exercise 26 but add a third fragment, this one invisible: it provides the functionality of generating a random number.
Write an app that improves this chapter’s app. On the left pane, add another fragment that contains a button so that the user can play another game by clicking on it.
Write the same app as in exercise 28 but add a file that stores a large number (1,000 or more) of potential words. The Hangman constructor should pick one at random from the file. Add an invisible fragment to instantiate a Hangman object and start the game with the word selected.
13.59.106.174