We are going to learn how to create and manage fragments with a sample Android application. This application is going to list book names. When a book name is clicked, the author of the book will be displayed. This application will be designed for small and large screen devices, this way we will see how to use fragments for different screen sizes. The following is the screenshot of this application for small screens. As you can see in this screenshot, the left hand side of the screen has the list of books and when a book is clicked, the right hand side of the screen will be displayed which shows the author of the clicked book:
We will firstly implement these screens, and then we will design this application for large screens.
In this application, we have two activities, one for the first screen and one for the second screen. Each activity consists of one fragment. The following diagram shows the structure of this application:
The XML code for the layout of
Fragment B
is as follows:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center_horizontal" android:orientation="vertical" > <TextView android:id="@+id/textViewAuthor" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceLarge" /> </LinearLayout>
As you can see in this code, it has a LinearLayout
layout
with a
TextView
component. TextView
is for displaying the author of the book. We don't have a layout for Fragment A
, because it is a ListFragment
property which includes the ListView
component.
Now we need two classes that extend the Fragment
classes for each fragment. The following is the class for Fragment A
:
package com.chapter5; import android.app.ListFragment; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.ArrayAdapter; public class Chapter5_1FragmentA extends ListFragment implements OnItemClickListener { @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); //initialize the adapter and set on click events of items ArrayAdapter<String> adapter = new ArrayAdapter<String>(getActivity(), android.R.layout.simple_list_item_1, Book.BOOK_NAMES); this.setListAdapter(adapter); getListView().setOnItemClickListener(this); } @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { //Start a new Activity in order to display author name String author = Book.AUTHOR_NAMES[position]; Intent intent = new Intent(getActivity().getApplicationContext(), Chapter5_1Activity_B.class); intent.putExtra("author", author); startActivity(intent); } }
As you can see, the Chapter5_1FragmentA
class extends ListFragment
, because we are listing the books in this screen. It is similar to ListActivity
and this class has a ListView
view. In the
onActivityCreated
method we set the ListAdapter
property of the ListFragment
. The source for the adapter is a class that contains the string arrays of book names and authors as shown in the following code block:
package com.chapter5; public class Book { public static final String[] BOOK_NAMES = { "Book Name 1", "Book Name 2", "Book Name 3", "Book Name 4", "Book Name 5", "Book Name 6", "Book Name 7", "Book Name 8" }; public static final String[] AUTHOR_NAMES = { "Author of Book 1", "Author of Book 2", "Author of Book 3", "Author of Book 4", "Author of Book 5", "Author of Book 6", "Author of Book 7", "Author of Book 8" }; }
After initializing ListAdapter
, we set the OnItemClickListener
event of the ListView
view. This event is called when a ListView
item is clicked. When an item is clicked, the onItemClick
method is called. In this method, a new activity is started with the author of the book. As you can see in the code, we reach the owner activity of the fragment with the
getActivity()
method. We could receive the ApplicationContext
with the getActivity()
method. Remember that the OnCreateView
method is called before OnActivityCreated
, and because of that we initialized ListAdapter
and ListView
in the OnActivityCreated
method,
because we need the user interface components to be created before we initialize them and they are created in OnCreateView
. We don't need to override the OnCreateView
method of ListFragment
, because it returns a ListView
. You can override the
OnCreateView
method if you want to use a customized ListView
.
The following is the class for Fragment B
:
package com.chapter5; import android.app.Fragment; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; public class Chapter5_1FragmentB extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_b, container, false); return view; } }
As you can see from this code, if a fragment has a user interface, this method should be overridden and should return a view. In our sample application, we are returning a view inflated with the XML layout that we previously implemented.
Now we need two activity classes that host these fragments. The following is the
Activity
class of Activity A
that hosts Fragment A
:
package com.chapter5; import android.app.Activity; import android.os.Bundle; public class Chapter5_1Activity_A extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_a); } }
It is a simple Activity
class that just sets the content view with a layout. The XML layout code of the Activity A
class is as follows:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical"> <fragment android:id="@+id/fragment_a" android:layout_width="match_parent" android:layout_height="match_parent" class="com.chapter5.Chapter5_1FragmentA" /> </LinearLayout>
As you can see from this code, we specified Fragment A
with the class property com.chapter5.Chapter5_1FragmentA
. Furthermore, we specified the id
property. Fragments should have either an
id
or a
tag
property as an identifier because Android needs that in restoring the fragment when the activity is restarted.
The Activity
class for Activity B
that hosts Fragment B
is as follows:
package com.chapter5; import android.app.Activity; import android.os.Bundle; import android.widget.TextView; public class Chapter5_1Activity_B extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_b); Bundle extras = getIntent().getExtras(); if (extras != null) { String s = extras.getString("author"); TextView view = (TextView) findViewById(R.id.textViewAuthor); view.setText(s); } } }
It is a simple Activity
class that just sets the content view with a layout. The XML layout code of Activity B
is as follows:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <fragment android:id="@+id/fragment_b" android:layout_width="match_parent" android:layout_height="match_parent" class="com.chapter5.Chapter5_1FragmentB" /> </LinearLayout>
As you can see from this code, we specified Fragment B
with the class property com.chapter5.Chapter5_1FragmentB
.
In our previous sample application, we added a fragment to an activity layout in XML layout code. You can also add a fragment to an activity programmatically. The following is the programmatically added fragment version of our previous sample application and XML layout code of the activity:
package com.chapter5; import android.app.Activity; import android.app.FragmentManager; import android.app.FragmentTransaction; import android.os.Bundle; public class Chapter5_1Activity_A extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_a); addFragment(); } public void addFragment() { FragmentManager fragmentManager = getFragmentManager(); FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); Chapter5_1FragmentA fragment = new Chapter5_1FragmentA(); fragmentTransaction.add(R.id.layout_activity_a, fragment); fragmentTransaction.commit(); } } <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/layout_activity_a" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" > </LinearLayout>
As you can see from this XML code, we removed the Fragment
tags because we are adding Fragment A
programmatically. As you can see in the Chapter5_1Activity_A
class, we added a method called
addFragment()
. We used the FragmentTransaction
class in order to add Fragment A
. The FragmentTransaction
class is used for operations such as adding fragments, removing fragments, attaching fragments to the UI, and so on. As you can see in the addMethod()
method, you can get an instance of FragmentTransaction
with FragmentManager
using the
beginTransaction()
method. Finally we have to call the
commit()
method for the changes to be applied.
FragmentManager
is
used for managing fragments. As you can see in the code, you can get an instance of FragmentManager
by the
getFragmentManager()
method. FragmentManager
allows you to begin a transaction by the beginTransaction()
method, get a fragment in activity by the findFragmentById()
or findFragmentbyTag()
methods, and pop a fragment off the back stack by the
popBackStack()
method.
In our example, we started an activity in the ListFragment
class' onItemClick
method
. We can establish the same operation by creating a callback interface in ListFragment
and make the Activity
class implement that callback. By this way the Fragment
class will notify the owner Activity
class. When the owner Activity
class is notified, it can share the notification by other fragments. This way, fragments can share an event and communicate. We can go about this operation using the following steps:
Chapter5_1FragmentA
class:public interface OnBookSelectedListener { public void onBookSelected(int bookIndex); }
OnBookSelectedListener
and assign the owner activity to that instance in the Chapter5_1FragmentA
class:OnBookSelectedListener mListener; @Override public void onAttach(Activity activity) { super.onAttach(activity); mListener = (OnBookSelectedListener) activity; }
As you can see from this code, the owner activity class of Chapter5_1FragmentA
should implement the onBookSelectedListener
instance or there will be a class cast exception.
Chapter5_1Activity_A
class implement the onBookSelectedListener
interface:public class Chapter5_1Activity_A extends Activity implements OnBookSelectedListener { //some code here @Override public void onBookSelected(int bookIndex) { String author = Book.AUTHOR_NAMES[bookIndex]; Intent intent = new Intent(this,Chapter5_1Activity_B.class); intent.putExtra("author", author); startActivity(intent); } //some more code here }
As you can see from this code, Chapter5_1Activity_A
receives a selected book index in the event callback and starts the activity with author data.
onBookSelected
method in the onItemClick
method of the Chapter5_1FragmentA
class:@Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { mListener.onBookSelected(position); }
In this way, we made the activity and fragment share an event callback.
Our sample book listing application is designed for small screens. When you execute this application on a larger screen, it will look bad. We have to use the space efficiently in larger screen sizes. In order to achieve this, we have to create a new layout for large screens. The new layout is as follows:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/layout_small_a" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="horizontal" > <fragment android:id="@+id/fragment_a" android:layout_width="fill_parent" android:layout_height="fill_parent" class="com.chapter5.Chapter5_1FragmentA" android:layout_weight="1"/> <fragment android:id="@+id/fragment_b" android:layout_width="fill_parent" android:layout_height="fill_parent" class="com.chapter5.Chapter5_1FragmentB" android:layout_weight="1"/> </LinearLayout>
As you can see from this code, we put two fragments in a horizontal LinearLayout
layout. In the previous sample application, there was one fragment in each activity, but in this activity there are two fragments in order to use the space efficiently. By setting the layout_weight
property
to 1
, we make the fragments consume equal spaces on the screen.
We have to put this new layout XML file under a folder named layout-xlarge-land
under the res
folder. In this way, the Android uses this layout file when the device screen is large and in landscape mode. Android decides which layout file to use on runtime according to layout folder names. layout
is the default folder name for Android. If Android can't find a suitable layout folder for a device screen size and mode, it uses the layout in the layout
folder. Some of the common qualifiers for layout are as follows:
small
for small screen sizesnormal
for normal screen sizeslarge
for large screen sizesxlarge
for extra large screen sizesland
for landscape orientationport
for portrait orientationHowever, this layout is not enough to make our sample function correctly on large screens. To make the new layout function correctly, we have to change how the fragments are managed. Update the
onBookSelected
property in Chapter5_1Activity_A
as follows:
@Override public void onBookSelected(int bookIndex) { FragmentManager fragmentManager = getFragmentManager(); Fragment fragment_b = fragmentManager.findFragmentById(R.id.fragment_b); String author = Book.AUTHOR_NAMES[bookIndex]; if(fragment_b == null) { Intent intent = new Intent(this, Chapter5_1Activity_B.class); intent.putExtra("author", author); startActivity(intent); } else { TextView textViewAuthor = (TextView)fragment_b.getView().findViewById(R.id.textViewAuthor); textViewAuthor.setText(author); } }
As you can see from this code, we get the Fragment B
class by using FragmentManager
. If the fragment_b
is not null, we understand that this activity contains Fragment B
, and the device has a large screen, because Fragment B
is used in Activity A
only when the screen is large and in landscape mode. Then using fragment_b
, we get the textViewAuthor
TextView component and update its text with the chosen book's author name. On the right of the screen we see the author name of the chosen book.
If fragment_b
is null, we understand that the device has a small screen, and we start a new activity using Intent
.
In the AndroidManifest.xml
file, we have to set the minimum SDK version to API Level 14, because fragments have been available for small screens since API Level 14. The AndroidManifest.xml
file should look like the following code block:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.chapter5"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk android:minSdkVersion="14" />
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name" >
<activity
android:name=".Chapter5_1Activity_A"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".Chapter5_1Activity_B"/>
</application>
</manifest>
Our sample application will look like the following on a large screen:
3.94.202.151