Using Fragments in Android - A Worked Example
Previous | Table of Contents | Next |
An Introduction to Android Fragments | An Android Master/Detail Flow Tutorial |
<google>BUY_ANDROID</google>
As outlined in the previous chapter, fragments provide a convenient mechanism for creating reusable modules of application functionality consisting of both sections of a user interface and the corresponding behavior. Once created, fragments can be embedded within activities.
Having explored the overall theory of fragments in the previous chapter, the objective of this chapter is to create an example Android application designed to demonstrate the actual steps involved in both creating and using fragments, and also implementing communication between one fragment and another within an activity.
About the Example Fragment Application
The application created in this chapter will consist of a single activity and two fragments. The user interface for the first fragment will contain a toolbar of sorts consisting of an EditText view, a SeekBar and a Button, all contained within a RelativeLayout view. The second fragment will consist solely of a TextView object, also contained within a RelativeLayout view.
The two fragments will be embedded within the main activity of the application and communication implemented such that when the button in the first fragment is pressed, the text entered into the EditText view will appear on the TextView of the second fragment using a font size dictated by the position of the SeekBar in the first fragment.
Since this application will be required to work on all generations of earlier versions of Android, it will also be necessary to make use of the appropriate Android support library.
Creating the Example Project
Launch Eclipse and create an Android Application Project named FragmentExample with the appropriate package name and SDK selections. As with previous examples, request the creation of a blank activity and the use of the default launcher icons. On the New Blank Activity screen of the New Android Application wizard, set the Activity Name to FragmentExampleActivity and the Layout Name to activity_fragment_example.
Adding the Android Support Library
As previously discussed, support for fragments was not introduced until Android 3.0. This means that for applications that make use of fragments to be compatible with older versions of Android, use must be made of version 4 of the Android Support Library. The purpose of the Android Support Library is to make features that were introduced in later versions of Android available to applications that need to be compatible with earlier Android versions.
Assuming that the installation steps outlined in the chapter entitled Setting up an Android Development Environment were followed, then the Android Support Library package should already be installed on your development system. To verify this, launch Eclipse and select the Window -> Android SDK Manager menu option. When the manager window has appeared on the screen, scroll down to the Extras section and make sure that the column to the far right of the Android Support Library item is listed as Installed as shown in Figure 22-1:
Figure 22-1
With the Android Support Library installed, the next step is to verify that it is already included in the project. Remaining within the Eclipse environment, locate the FragmentExample project within the Package Explorer panel and unfold the libs folder. If the support library is already included in the project, it will be listed as android-support-v4.jar:
Figure 22-2
If the support library package is not listed, it will need to be added before proceeding with this chapter. This involves locating the jar file in the Android SDK installation directory and placing a copy in the libs folder of the project. Begin by using the file system browser of your operating system to navigate to the following location (where <path_to_adt_installation> is the directory into which the ADT Bundle was originally installed when you set up your development environment):
<path_to_adt_installation>/sdk/extras/android/support/v4
Within the above folder will be a file named android-support-v4.jar. Simply click and drag this file and drop it onto the libs folder within the FragmentExample project in the Eclipse Package Explorer panel. When prompted by the File Operation panel, select the option to copy the file into the project before clicking OK.
The support library is now installed within the project and is ready to be used.
Creating the First Fragment Layout
The next step is to create the user interface for the first fragment that will be used within our activity. This user interface will, of course, reside in an XML layout file so begin by right-clicking on the FragmentExample entry in the Package Explorer window and selecting the New -> Android XML File menu option. In the resulting dialog, verify that the Resource Type: menu is set to Layout before entering toolbar_fragment.xml into the File: field. From the Root Element list, select RelativeLayout and click Finish.
Using the Package Explorer panel, locate and double click on the new layout file to load it into the Graphical Layout tool. Click on the toolbar_fragment.xml tab along the bottom of the layout tool panel to access the XML directly. Modify the current XML to configure the fragment with the three required views:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <Button android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/seekBar1" android:layout_centerHorizontal="true" android:layout_marginTop="17dp" android:text="Button" /> <EditText android:id="@+id/editText1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:layout_centerHorizontal="true" android:layout_marginTop="16dp" android:ems="10" android:inputType="text" > <requestFocus /> </EditText> <SeekBar android:id="@+id/seekBar1" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_below="@+id/editText1" android:layout_marginTop="14dp" /> </RelativeLayout>
Once the changes have been made, switch back to the Graphical Layout view of the user interface. Edit the text displayed on the button, creating a new string resource named button_text that reads “Change Text”. Upon completion of these steps, the user interface layout should resemble that of Figure 22-3
Figure 22-3
Save the file before continuing. With the layout for the first fragment implemented, the next step is to create a class to go with it.
Creating the First Fragment Class
In addition to a user interface layout, a fragment also needs to have a class associated with it to do the actual work behind the scenes. Add a class for this purpose to the project by unfolding the src folder under the FragmentExample project in the Package Explorer panel and right-clicking on the package name given to the project when it was created (for example com.example.fragmentexample). From the resulting menu, select the New -> Class option. In the resulting New Java Class dialog, name the class ToolbarFragment. Next, click on the Browse button to the right of the Superclass field. In the superclass selection dialog, enter Fragment into the text box. From the matching items list, locate and double click on the Fragment - android.support.v4.app entry (selecting the android.app instance of the class will not make use of the support library precluding the execution of the application on older Android versions).
<google>BUY_ANDROID</google>
Click Finish to complete the class creation process. Once the class has been created, it should, by default, appear in the editing panel where it will read as follows:
package com.example.fragmentexample; import android.support.v4.app.Fragment; public class ToolbarFragment extends Fragment { }
For the time being, the only changes to this class are to override the onCreateView() method to make sure the layout file is inflated and displayed when the fragment is used within an activity. This method should be implemented as follows:
package com.example.fragmentexample; import android.os.Bundle; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; public class ToolbarFragment extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the layout for this fragment View view = inflater.inflate(R.layout.toolbar_fragment, container, false); return view; } }
Once the changes are complete, save the file. Later in this chapter, more functionality will be added to this class. Before that, however, we need to create the second fragment.
Creating the Second Fragment Layout
Add a second new Android XML file to the project, once again selecting the options to create a layout resource file with a RelativeLayout as the root element. Name the file text_fragment.xml and click Finish. When the layout loads into the Graphical Layout tool, change to the XML view by clicking on the text_fragment.xml tab at the bottom of the layout panel. Modify the XML to add a TextView to the fragment layout as follows:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <TextView android:id="@+id/textView1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:layout_centerVertical="true" android:text="text" android:textAppearance="?android:attr/textAppearanceLarge" /> </RelativeLayout>
Once the XML changes have been made, switch back to the Graphical Layout view of the user interface. Edit the text displayed on the TextView, creating a new string resource named text_label that reads “Fragment Two”. Upon completion of these steps, the user interface layout for this second fragment should resemble that of Figure 22-4:
Figure 22-4
Save the resource file before continuing.
As with the first fragment, this one will also need to have a class associated with it. Unfold the src category under the FragmentExample project (if it is not already unfolded) in the Package Explorer panel and right-click on the package name given to the project when it was created. From the resulting menu, select the New -> Class option. Using the same steps as those for the first fragment, name the fragment TextFragment and set the Superclass field to Fragment - android.support.v4.app. Click Finish to create the class.
Edit the new TextFragment.java class file and modify it to implement the onCreateView() method:
package com.example.fragmentexample; import android.os.Bundle; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; public class TextFragment extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.text_fragment, container, false); return view; } }
Save the file once the changes are complete. Now that the basic structure of the two fragments has been implemented, they are ready to be embedded in the application’s main activity.
Adding the Fragments to the Activity
The main activity for the application has associated with it an XML layout file named activity_fragment_example.xml. For the purposes of this example, the fragments will be added to the activity using the <fragment> element within this file. Using the Package Explorer panel, navigate to the res -> layout section of the FragmentExample project and double click on the activity_fragment_example.xml file to load it into the Graphical Layout tool. Switch from the graphical view to XML by clicking on the activity_fragment_example.xml tab at the bottom of the layout panel. Modify the XML to remove the default TextView object, any padding properties and to embed the two fragments as follows:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".FragmentExampleActivity" > <fragment android:id="@+id/toolbar_fragment" android:name="com.example.fragmentexample.ToolbarFragment" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_alignParentTop="true" tools:layout="@layout/toolbar_fragment" /> <fragment android:id="@+id/text_fragment" android:name="com.example.fragmentexample.TextFragment" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_centerVertical="true" tools:layout="@layout/text_fragment" /> </RelativeLayout>
Click on the Graphical Layout tab to return to the graphical view and note that the fragments are now visible in the layout as demonstrated in Figure 22-5:
Figure 22-5
Once the changes have been made, save the resource file before continuing.
Making the Toolbar Fragment Talk to the Activity
When the user touches the button in the toolbar fragment, the fragment class is going to need to get the text from the EditText view and the current value of the SeekBar and send them to the text fragment. As outlined in An Introduction to Android Fragments, fragments should not communicate with each other directly, instead using the activity in which they are embedded as an intermediary.
The first step in this process is to make sure that the toolbar fragment responds to the button being clicked. We also need to implement some code to keep track of the value of the SeekBar view. For the purposes of this example, we will implement these listeners within the ToolbarFragment class. Select the ToolbarFragment.java file and modify it so that it reads as shown in the following listing:
package com.example.fragmentexample; import android.os.Bundle; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.app.Activity; import android.widget.Button; import android.widget.EditText; import android.widget.SeekBar; import android.widget.SeekBar.OnSeekBarChangeListener; public class ToolbarFragment extends Fragment implements OnSeekBarChangeListener { private static int seekvalue = 10; private static EditText edittext; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the layout for this fragment View view = inflater.inflate(R.layout.toolbar_fragment, container, false); edittext = (EditText) view.findViewById(R.id.editText1); final SeekBar seekbar = (SeekBar) view.findViewById(R.id.seekBar1); seekbar.setOnSeekBarChangeListener(this); final Button button = (Button) view.findViewById(R.id.button1); button.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { buttonClicked(v); } }); return view; } public void buttonClicked (View view) { } @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { seekvalue = progress; } @Override public void onStartTrackingTouch(SeekBar arg0) { // TODO Auto-generated method stub } @Override public void onStopTrackingTouch(SeekBar arg0) { // TODO Auto-generated method stub } }
Before moving on, we need to take some time to explain the above code changes. First, the class is declared as implementing the OnSeekBarChangeListener interface. This is because the user interface contains a SeekBar instance and the fragment needs to receive notifications when the user slides the bar to change the font size. Implementation of the OnSeekBarChangeListener interface requires that the onProgressChanged(), onStartTrackingTouch() and onStopTrackingTouch() methods be implemented. These methods have been implemented but only the onProgressChanged() method is actually required to perform a task, in this case storing the new value in a variable named seekvalue which has been declared at the start of the class. Also declared is a variable in which to store a reference to the EditText object.
The onCreateView() method has been modified to obtain references to the EditText, SeekBar and Button views in the layout. Once a reference to the button has been obtained it is used to set up an onClickListener on the button which is configured to call a method named buttonClicked() when a click event is detected. This method is also then implemented, though at this point it does not do anything.
The next phase of this process is to set up the listener that will allow the fragment to call the activity when the button is clicked. This follows the mechanism outlined in the previous chapter:
public class ToolbarFragment extends Fragment implements OnSeekBarChangeListener { private static int seekvalue = 10; private static EditText edittext; ToolbarListener activityCallback; public interface ToolbarListener { public void onButtonClick(int position, String text); } @Override public void onAttach(Activity activity) { super.onAttach(activity); try { activityCallback = (ToolbarListener) activity; } catch (ClassCastException e) { throw new ClassCastException(activity.toString() + " must implement ToolbarListener"); } } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the layout for this fragment View view = inflater.inflate(R.layout.toolbar_fragment, container, false); edittext = (EditText) view.findViewById(R.id.editText1); final SeekBar seekbar = (SeekBar) view.findViewById(R.id.seekBar1); seekbar.setOnSeekBarChangeListener(this); final Button button = (Button) view.findViewById(R.id.button1); button.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { buttonClicked(v); } }); return view; } public void buttonClicked (View view) { activityCallback.onButtonClick(seekvalue, edittext.getText().toString()); } . . . }
The above implementation will result in a method named onButtonClick() belonging to the activity class being called when the button is clicked by the user. All that remains, therefore, is to declare that the activity class implements the newly created ToolbarListener interface and to implement the onButtonClick() method.
Since the Android Support Library is being used for fragment support in earlier Android versions, the activity also needs to be changed to subclass from FragmentActivity instead of Activity. Bringing these requirements together results in the following modified FragmentExampleActivity.java file:
package com.example.fragmentexample; import android.os.Bundle; import android.support.v4.app.FragmentActivity; import android.view.Menu; public class FragmentExampleActivity extends FragmentActivity implements ToolbarFragment.ToolbarListener { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_fragment_example); } public void onButtonClick(int fontsize, String text) { } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.activity_fragment_example, menu); return true; } }
With the code changes as they currently stand, the toolbar fragment will detect when the button is clicked by the user and call a method on the activity passing through the content of the EditText field and the current setting of the SeekBar view. It is now the job of the activity to communicate with the Text Fragment and to pass along these values so that the fragment can update the TextView object accordingly.
Making the Activity Talk to the Text Fragment
As outlined in An Introduction to Android Fragments, an activity can communicate with a fragment by obtaining a reference to the fragment class instance and then calling public methods on the object. As such, within the TextFragment class we will now implement a public method named changeTextProperties() which takes as arguments an integer for the font size and a string for the new text to be displayed. The method will then use these values to modify the TextView object. Within the Package Explorer panel, double click on the TextFragment.java file and modify it to add this new method and to add code to the onCreateView() method to obtain the ID of the textview object:
package com.example.fragmentexample; import android.os.Bundle; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; public class TextFragment extends Fragment { private static TextView textview; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.text_fragment, container, false); textview = (TextView) view.findViewById(R.id.textView1); return view; } public void changeTextProperties(int fontsize, String text) { textview.setTextSize(fontsize); textview.setText(text); } }
When the TextFragment fragment was placed in the layout of the activity, it was given an ID of text_fragment. Using this ID, it is now possible for the activity to obtain a reference to the fragment instance and call the changeTextProperties() method on the object. Edit the FragmentExampleActivity.java file and modify the onButtonClick() method as follows:
public void onButtonClick(int fontsize, String text) { TextFragment textFragment = (TextFragment) getSupportFragmentManager().findFragmentById(R.id.text_fragment); textFragment.changeTextProperties(fontsize, text); }
Testing the Application
With the coding for this project now complete, the last remaining task is to run the application. When the application is launched, the main activity will start and will, in turn, create and display the two fragments. When the user touches the button in the toolbar fragment, the onButtonClick() method of the activity will be called by the toolbar fragment and passed the text from the EditText view and the current value of the SeekBar. The activity will then call the changeTextProperties() method of the second fragment, which will modify the TextView to reflect the new text and font size:
Figure 22-6
Summary
The goal of this chapter has been to work through the creation of an example project intended specifically to demonstrate the steps involved in using fragments within an Android application. Topics covered included the use of the Android Support Library for compatibility with Android versions predating the introduction of fragments, the inclusion of fragments within an activity layout and the implementation of inter-fragment communication.
<google>BUY_ANDROID</google>
Previous | Table of Contents | Next |
An Introduction to Android Fragments | An Android Master/Detail Flow Tutorial |