Perhaps the largest change facing Android developers in 2011 was the introduction of the fragment system with Android 3.0, and the recent merging of the fragment system into the main code base with Android 4.0 Ice Cream Sandwich. Fragments are an optional layer you can put between your activities and your widgets, designed to help you reconfigure your activities to support screens both large (e.g., tablets) and small (e.g., phones). However, the fragment system also adds an extra layer of complexity, one that will take the Android developer community some time to adjust to. Hence, the public comments, blog posts, and sample apps using fragments are a little rarer, because fragments were introduced so long after Android itself was.
This chapter covers basic uses of fragments, including supporting fragments on pre–Android 3.0 devices.
Fragments are not widgets, like Button
or EditText
. Fragments are not containers, like LinearLayout
or RelativeLayout
. Fragments are not activities.
Rather, fragments aggregate widgets and containers. Fragments then can be placed into activities—sometimes several fragments for one activity, sometimes one fragment per activity. And the reason for this is the variation in Android screen sizes.
A tablet has a larger screen than does a phone. A TV has a larger screen than does a tablet. Taking advantage of that extra screen space makes sense, as outlined in Chapter 25, which explained how to handle multiple screen sizes. In that chapter, we profiled an EU4You
sample application, eventually winding up with an activity that would load in a different layout for larger-sized screens, one that had an embedded WebView
widget. The activity would detect that widget’s existence and use it to load web content related to a selected country, rather than launching a separate browser activity or some activity containing only a WebView
.
However, the scenario outlined in Chapter 25 was fairly trivial. Imagine that, instead of a WebView
, we have a TableLayout
containing 28 widgets. On larger-sized screens, we want the TableLayout
in the same activity as an adjacent ListView
; on smaller screens, we want the TableLayout
to be in a separate activity, since there would not be enough room otherwise. To do this using early Android technology, we would need to either duplicate all of the TableLayout
-handling logic in both activities, create an activity base class and hope that both activities can inherit from it, or turn the TableLayout
and its contents into a custom ViewGroup
…or do something else. And that would just be for one such scenario—multiply that by many activities in a larger application, and the complexity mounts.
Fragments reduce, but do not eliminate, that complexity.
With fragments, each discrete chunk of user interface that could be used in multiple activities (based on screen size) goes in a fragment. The activities in question determine, based on screen size, who gets the fragment.
In the case of EU4You
, we have two fragments. One fragment represents the list of countries. The other fragment represents the details for that country (in our case, a WebView
). On a larger-screen device, we want both fragments to be in one activity, while on a smaller-screen device, we will house those fragments in two separate activities. This provides to users with larger screens the same benefits they got with the last version of EU4You
: getting more information in fewer clicks. Yet the techniques we demonstrate with fragments will be more scalable, able to handle more complex UI patterns than the simple WebView
-or-not scenario of EU4You
.
In this case, our entire UI will be inside of fragments. That is not necessary. Fragments are an opt-in technology—you need them only for the parts of your UI that could appear in different activities in different scenarios. In fact, your activities that do not change at all (say, a help screen) might not use fragments whatsoever.
Fragments also give us a few other bells and whistles, including the following:
ListFragment
of the user's mail folders. Tapping a folder adds a second ListFragment
to the screen, showing the conversations in that folder. Tapping a conversation adds a third Fragment
to the screen, showing the messages in that conversation.ListFragment
slides off the screen to the left, the conversations ListFragment
slides left and shrinks to take up less room, and the messages Fragment
slides in from the right.Fragment
, that Fragment
slides off to the right, the conversations ListFragment
slides right and expands to fill more of the screen, and the folders ListFragment
slides back in from the left. None of that has to be managed by developers—simply adding the dynamic fragment via a FragmentTransaction
allows Android to automatically handle the Back button, including reversing all animations.setHasOptionsMenu()
in onCreate()
of your fragment to register an interest in this, and then override onCreateOptionsMenu()
and onOptionsItemSelected()
in the fragment the same way you might in an activity. A fragment can also register widgets to have context menus, and handle those context menus the same way as an activity would.TabHost
, where each tab's content is a fragment. Similarly, the action bar can have a navigation mode, with a Spinner
to switch between modes, where each mode is represented by a fragment.If you have access to any recent device running Honeycomb or Ice Cream Sandwich, fire up the Gmail application to see all the fragment bells and whistles in action.
If fragments were available only for Android 3.0 and higher, we would be right back where we started, as not all Android devices today run Android 3.0 and higher.
Fortunately, this is not the case, because Google has released the Android Compatibility Library (ACL), which is available via the Android SDK and AVD Manager (where you install the other SDK support files, create and start your emulator AVDs, and so forth). The ACL gives you access to the fragment system on versions of Android going back to Android 1.6. Because the vast majority of Android devices are running 1.6 or higher, this allows you to start using fragments while maintaining backward compatibility. Over time, this library may add other features to help with backward compatibility, for applications that wish to use it.
The material in this chapter focuses on using the ACL when employing fragments. Generally speaking, using the ACL for fragments is almost identical to using the native Android 3.0 fragment classes directly.
Since the ACL only supports versions back to Android 1.6, Android 1.5 devices will not be able to use fragment-based applications. This is a very small percentage of the Android device spectrum at this time—around 1 percent as of the time of this writing.
The first step toward setting up a fragment-based application is to create fragment classes for each of your fragments. Just as you inherit from Activity
(or a subclass) for your activities, you inherit from Fragment
(or a subclass) for your fragments.
Here, we will examine the Fragments/EU4You_6
sample project and the fragments that it defines.
NOTE: The convention of this book will be to use “fragment” as a generic noun and Fragment
to refer to the actual Fragment
class.
Besides inheriting from Fragment
, the only thing required of a fragment is to override onCreateView()
. This will be called as part of putting the fragment on the screen. You need to return a View
that represents the body of the fragment. Most likely, you will create your fragment's UI via an XML layout file, and onCreateView()
will inflate that fragment layout file.
For example, here is DetailsFragment
from EU4You_6
, which will wrap around our WebView
to show the web content for a given country:
import android.support.v4.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.WebView;
public class DetailsFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return(inflater.inflate(R.layout.details_fragment, container, false));
}
public void loadUrl(String url) {
((WebView)(getView().findViewById(R.id.browser))).loadUrl(url);
}
}
Note that we are inheriting not from android.app.Fragment
but from android.support.v4.app.Fragment
. The latter is the Fragment
implementation from the ACL, so it can be used across Android versions.
The onCreateView()
implementation inflates a layout that happens to have a WebView
in it:
<?xml version="1.0" encoding="utf-8"?>
<WebView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/browser"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
/>
It also exposes a loadUrl()
method, to be used by a hosting activity both to tell the fragment that it is time to display some web content and to supply the URL for doing the same. The implementation of loadUrl()
in DetailsFragment
uses getView()
to retrieve the View
created in onCreateView()
, finds the WebView
in it, and delegates the loadUrl()
call to the WebView
.
There are a myriad of other lifecycle methods available on Fragment
. The more important ones include mirrors of the standard onCreate()
, onStart()
, onResume()
, onPause()
, onStop()
, and onDestroy()
methods of an activity. Since the fragment is the one with the widgets, it will implement more of the business logic that formerly might have resided in the activity for these methods. For example, in onPause()
or onStop()
, since the user may not be returning to your application, you may wish to save any unsaved edits to some temporary storage. In the case of DetailsFragment
, there was nothing that really qualified here, so those lifecycle methods were left alone.
One Fragment
subclass that is sure to be popular is ListFragment
. This wraps a ListView
in a Fragment
, designed to simplify setting up lists of things such as countries, mail folders, mail conversations, and so forth. Similar to a ListActivity
, all you need to do is call setListAdapter()
with your chosen and configured ListAdapter
, plus override onListItemClick()
to respond to when the user clicks on a row in the list.
In EU4You_6
, we have a CountriesFragment
that represents the list of available countries. It initializes the ListAdapter
in onActivityCreated()
, which is called after onCreate()
has wrapped up in the activity that holds the fragment:
@Override
public void onActivityCreated(Bundle state) {
super
.onActivityCreated(state);
setListAdapter(new CountryAdapter());
if (state!=null) {
int position=state.getInt(STATE_CHECKED, -1);
if (position>-1) {
getListView().setItemChecked(position, true);
}
}
}
The code dealing with the Bundle
supplied to onCreate()
will be explained a bit later in this chapter.
The CountryAdapter
is nearly identical to the one from previous EU4You
samples, except that there is no getLayoutInflater()
method on a Fragment
, so we have to use the static from()
method on LayoutInflater
and supply our activity via getActivity()
:
class CountryAdapter extends ArrayAdapter<Country> {
CountryAdapter() {
super(getActivity(), R.layout.row, R.id.name, EU);
}
@Override
public View getView(int position, View convertView,
ViewGroup parent) {
CountryWrapper wrapper=null;
if (convertView==null) {
convertView=LayoutInflater
.from(getActivity())
.inflate(R.layout.row, null);
wrapper=new CountryWrapper(convertView);
convertView.setTag(wrapper);
}
else {
wrapper=(CountryWrapper)convertView.getTag();
}
wrapper.populateFrom(getItem(position));
return(convertView);
}
}
Similarly, the CountryWrapper
is no different from previous EU4You
samples:
static class CountryWrapper {
private TextView name=null;
private ImageView flag=null;
private View row=null;
CountryWrapper(View row) {
this.row=row;
name=(TextView)row.findViewById(R.id.name);
flag=(ImageView)row.findViewById(R.id.flag);
}
TextView getName() {
return(name);
}
ImageView getFlag() {
return(flag);
}
void populateFrom(Country nation) {
getName().setText(nation.name);
getFlag().setImageResource(nation.flag);
}
}
The list of countries is the same as well:
static {
EU.add(new Country(R.string.austria, R.drawable.austria,
R.string.austria_url));
EU.add(new Country(R.string.belgium, R.drawable.belgium,
R.string.belgium_url));
EU.add(new Country(R.string.bulgaria, R.drawable.bulgaria,
R.string.bulgaria_url));
EU0.add(new Country(R.string.cyprus, R.drawable.cyprus,
R.string.cyprus_url));
EU.add(new Country(R.string.czech_republic,
R.drawable.czech_republic,
R.string.czech_republic_url));
EU.add(new Country(R.string.denmark, R.drawable.denmark,
R.string.denmark_url));
EU.add(new Country(R.string.estonia, R.drawable.estonia,
R.string.estonia_url));
EU.add(new Country(R.string.finland, R.drawable.finland,
R.string.finland_url));
EU.add(new Country(R.string.france, R.drawable.france,
R.string.france_url));
EU.add(new Country(R.string.germany, R.drawable.germany,
R.string.germany_url));
EU.add(new Country(R.string.greece, R.drawable.greece,
R.string.greece_url));
EU.add(new Country(R.string.hungary, R.drawable.hungary,
R.string.hungary_url));
EU.add(new Country(R.string.ireland, R.drawable.ireland,
R.string.ireland_url));
EU.add(new Country(R.string.italy, R.drawable.italy,
R.string.italy_url));
EU.add(new Country(R.string.latvia, R.drawable.latvia,
R.string.latvia_url));
EU.add(new Country(R.string.lithuania, R.drawable.lithuania,
R.string.lithuania_url));
EU.add(new Country(R.string.luxembourg, R.drawable.luxembourg,
R.string.luxembourg_url));
EU.add(new Country(R.string.malta, R.drawable.malta,
R.string.malta_url));
EU.add(new Country(R.string.netherlands, R.drawable.netherlands,
R.string.netherlands_url));
EU.add(new Country(R.string.poland, R.drawable.poland,
R.string.poland_url));
EU.add(new Country(R.string.portugal, R.drawable.portugal,
R.string.portugal_url));
EU.add(new Country(R.string.romania, R.drawable.romania,
R.string.romania_url));
EU.add(new Country(R.string.slovakia, R.drawable.slovakia,
R.string.slovakia_url));
EU.add(new Country(R.string.slovenia, R.drawable.slovenia,
R.string.slovenia_url));
EU.add(new Country(R.string.spain, R.drawable.spain,
R.string.spain_url));
EU.add(new Country(R.string.sweden, R.drawable.sweden,
R.string.sweden_url));
EU.add(new Country(R.string.united_kingdom,
R.drawable.united_kingdom,
R.string.united_kingdom_url));
}
…as is the definition of a Country
, from a separate public class:
public class Country {
int name;
int flag;
int url;
Country(int name, int flag, int url) {
this.name=name;
this.flag=flag;
this.url=url;
}
}
One thing leaps out at you when you use fragment-based applications like Gmail. When you tap on a row in a list, and another fragment is shown (or updated) within the same activity, the row you tapped remains highlighted. This runs counter to the traditional use of a ListView
, where the list selector is present only when using a D-pad, trackball, or similar pointing device. The purpose is to show the user the context of the adjacent fragment.
The actual implementation differs from what you might expect. These ListView
widgets are actually implementing CHOICE_MODE_SINGLE
, what normally would be rendered using a RadioButton
along the right side of the rows. In a ListFragment
, though, the typical styling for a single-choice ListFragment
is via an “activated” background.
In EU4You_6
, this is handled via the row layout (res/layout/row.xml
) used by our CountryAdapter
:
<?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="wrap_content"
android:padding="2dip"
android:minHeight="?android:attr/listPreferredItemHeight"
style="@style/activated"
>
<ImageView android:id="@+id/flag"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|left"
android:paddingRight="4dip"
/>
<TextView android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|right"
android:textSize="5mm"
/>
</LinearLayout>
Notice the style
attribute, pointing to an activated
style. That is defined by EU4You_6
as a local style, versus one provided by the operating system. In fact, it has to have two implementations of the style, because the “activated” concept is new to Android 3.0 and cannot be used in previous versions of Android.
So, EU4You_6
has res/values/styles.xml
with a backward-compatible empty style:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="activated">
</style>
</resources>
It also has res/values-v11/styles.xml
. The -v11
resource set suffix means that this will be used only on API Level 11 (Android 3.0) and higher. Here, the style inherits from the standard Android Holographic theme and uses the standard activated background color:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="activated" parent="android:Theme.Holo">
<item name="android:background">?android:attr/activatedBackgroundIndicator</item>
</style>
</resources>
In CountriesFragment
, the activity will let us know if CountriesFragment
appears alongside DetailsFragment
—thus requiring single-choice mode—via an enablePersistentSelection()
method:
public void enablePersistentSelection() {
getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
}
Also, in onListItemClick()
, CountriesFragment
“checks” the row the user clicked on, thereby enabling the persistent highlight:
@Override
public void onListItemClick(ListView l, View v, int position,
long id) {
l.setItemChecked(position, true);
if (listener!=null) {
listener.onCountrySelected(EU.get(position));
}
}
The listener
object and call to onCountrySelected()
will be explained later in this chapter.
The ACL has one other subclass of Fragment
: DialogFragment
. This is used to help coordinate between a modal Dialog
and a fragment-based UI.
Android 3.0 itself has two more subclasses of Fragment
, which are not available in the ACL as of the time of this writing:
PreferenceFragment
: For use in the new Honeycomb-style PreferenceActivity
(covered in Chapter 31)WebViewFragment
: A Fragment
wrapped around a WebView
Having some fragment classes and their accompanying layouts is all well and good, but we need to hook them up to activities and get them on the screen. Along the way, we have to think about dealing with multiple screen sizes, much like we went with the WebView
-or-browser approach with the previous version of the EU4You
sample.
In Android 3.0 and higher, any activity can host a fragment. However, for the ACL, you need to inherit from FragmentActivity
to use fragments. This limitation of the ACL definitely causes challenges, particularly if you were aiming to put a map in a fragment, a topic we will discuss later in this book. Other activity base classes pose less of an issue—ListActivity
would be replaced by ListFragment
, for example.
Fragments can be added in either of two ways to an activity:
<fragment>
elements in the activity's layout. These fragments are fixed and will always exist for the lifetime of this activity instance.FragmentManager
and a FragmentTransaction
. This gives you more flexibility, but adds a degree of complexity. This technique is not covered in this book.One big limitation of dealing with multiple screen sizes is that the layouts need to have the same starting fragments for any configuration change. So, a small-screen version of an activity and a large-screen version of an activity can have different mixes of fragments, but a portrait layout and a landscape layout for the same screen size must have the same fragments defined. Otherwise, when the screen is rotated, Android will have problems, trying to work with a fragment that does not exist, for example.
We also need to work out communications between our fragments and our activities. The activities define what fragments they hold, so they typically know which classes implement those fragments and can call methods on them directly. The fragments, though, only know that they are hosted by some activity, and that activity may differ from case to case. Hence, the typical pattern is to use interfaces for fragment-to-activity communication:
We will see all of this as we work through the EU4You_6
activities and their corresponding layouts.
In the earlier versions of the EU4You
project, we had only one activity, also named EU4You
. In EU4You_6
, though, we have two activities:
EU4You
: Handles displaying the CountriesFragment
in all screen sizes, plus the DetailsFragment
on larger screensDetailsActivity
: Hosts the DetailsFragment
on smaller screensWhile we could probably get away with having EU4You
launch the browser activity for smaller screens, rather than have a DetailsActivity
host a WebView
-only DetailsFragment
, the latter approach is more realistic for more fragment-based applications.
First we'll take a look at the pieces of the EU4You
activity.
For normal-screen devices, we want to display only the CountriesFragment
. That is accomplished via res/layout/main.xml
just having the appropriate <fragment>
element:
<?xml version="1.0" encoding="utf-8"?>
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
class="com.commonsware.android.eu4you.CountriesFragment"
android:id="@+id/countries"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
/>
The class
attribute indicates which Java class implements the fragment. Otherwise, this layout is unremarkable.
Note that fragments do not get listed in the manifest file the way activities do.
For large-screen devices, in the landscape mode, we want to have both the CountriesFragment
and the DetailsFragment
, side by side. That way, users can tap on a country and view the details without flipping back and forth between activities. It also enables us to take advantage of the screen space better.
However, there is a catch. If we want to predefine those two fragments in our layout file, we have to use that same pair of fragments for both landscape and portrait modes—despite the fact that we do not want to use the DetailsFragment
in EU4You
in portrait mode (having a list vertically stacked over the WebView
would be odd looking, at best). As a workaround, we will use the same layout file for both orientations and then make adjustments in our Java code. Another approach to the problem would be to have the layout file only have the CountriesFragment
and to use FragmentManager
and a FragmentTransaction
to add in the DetailsFragment
. Here, though, we will use other tricks.
Hence, in res/layout-large/
(not res/layout-large-land/
), we have this layout:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<fragment class="com.commonsware.android.eu4you.CountriesFragment"
android:id="@+id/countries"
android:layout_weight="30"
android:layout_width="0px"
android:layout_height="fill_parent"
/>
<fragment class="com.commonsware.android.eu4you.DetailsFragment"
android:id="@+id/details"
android:layout_weight="70"
android:layout_width="0px"
android:layout_height="fill_parent"
/>
</LinearLayout>
Note that we are responsible for the positioning of the fragments, so here we use a horizontal LinearLayout
to wrap around the two <fragment>
elements.
When the user chooses a Country in the CountriesFragment
, we want to let our containing activity know about that. In this case, it so happens that the only activity that will ever host CountriesFragment
is EU4You
. However, perhaps in the future that will not be the case. So, we should abstract out the communications from CountriesFragment
to its hosting activity via a listener interface.
Hence, the EU4You_6
project has a CountryListener
interface:
package com.commonsware.android.eu4you;
public interface Country Listener {
void onCountrySelected(Country c);
}
The CountriesFragment
holds onto an instance of CountryListener
, supplied by the hosting activity:
public void setCountryListener(CountryListener listener) {
this.listener=listener;
}
And, when the user clicks on a Country and triggers onListItemClick()
, CountriesFragment
calls the onCountrySelected()
method on the interface:
@Override
public void onListItemClick(ListView l, View v, int position,
long id) {
l.setItemChecked(position, true);
if (listener!=null) {
listener.onCountrySelected(EU.get(position));
}
}
The EU4You
activity is not long, though it is a bit tricky:
package com.commonsware.android.eu4you;
import android.content.Intent;
import android.content.res.Configuration;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.view.View;
public class EU4You extends FragmentActivity implements Country Listener {
private boolean detailsInline=false;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
CountriesFragment countries
=(CountriesFragment)getSupportFragmentManager()
.findFragmentById(R.id.countries);
countries.setCountryListener(this);
Fragment f=getSupportFragmentManager().findFragmentById(R.id.details);
detailsInline=(f!=null &&
(getResources().getConfiguration().orientation==
Configuration.ORIENTATION_LANDSCAPE));
if (detailsInline) {
countries.enablePersistentSelection();
}
else if (f!=null) {
f.getView().setVisibility(View.GONE);
}
}
@Override
public void onCountrySelected(Country c) {
String url=getString(c.url);
if (detailsInline) {
((DetailsFragment)getSupportFragmentManager()
.findFragmentById(R.id.details))
.loadUrl(url);
}
else {
Intent i=new Intent(this, DetailsActivity.class);
i.putExtra(DetailsActivity.EXTRA_URL, url);
startActivity(i);
}
}
}
Our mission in onCreate()
is to wire up our fragments. The fragments themselves are created by our call to setContentView()
, inflating our layout and the fragments defined therein. In addition, though, EU4You
does the following:
CountriesFragment
and registers itself as the CountryListener
, since EU4You
implements that interface.DetailsFragment
, if it exists. If it exists and we are in landscape mode, we tell the CountriesFragment
to enable the persistent highlight, to remind the user what details are being loaded on the right. If it exists and we are in portrait mode, we actually do not want DetailsFragment
but need it to be consistent with the layout mode, so we mark the fragment's contents as being GONE
. If the DetailsFragment
does not exist, we do not have to do anything special.Getting the FragmentManager
for calls like findFragmentById()
is accomplished via getFragmentManager()
. The ACL, however, defines a separate getSupportFragmentManager()
, to ensure you are working with the ACL's implementation of FragmentManager
and to work across the wider range of Android versions.
In addition, since EU4You
implements the CountryListener
interface, it must implement onCountrySelected()
. Here, EU4You
notes whether or not we should be routing to an inline edition of DetailsFragment
. If we should be, then onCountrySelected()
passes the Country
to the DetailsFragment
, so it loads that Country's web page. Otherwise, we launch the DetailsActivity
, supplying the URL as an extra.
The DetailsActivity
will be used where the DetailsFragment
is not being shown in the EU4You
activity, including in the following cases:
DetailsFragment
in the layoutEU4You
is hiding its own DetailsFragment
The layout just has our <fragment>
element in it, since there is nothing else to show:
<?xml version="1.0" encoding="utf-8"?>
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
class="com.commonsware.android.eu4you.DetailsFragment"
android:id="@+id/details"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
/>
DetailsActivity
simply passes the URL from the Intent
extra on to the DetailsFragment
, telling it what web content to display:
package com.commonsware.android.eu4you;
import android.support.v4.app.FragmentActivity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
public class DetailsActivity extends FragmentActivity {
public static final String EXTRA_URL="com.commonsware.android.eu4you.EXTRA_URL";
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.details);
DetailsFragment details
=(DetailsFragment)getSupportFragmentManager()
.findFragmentById(R.id.details);
details.loadUrl(getIntent().getStringExtra(EXTRA_URL));
}
}
In Chapter 19, we reviewed how activities can deal with configuration changes, such as screen rotations. How does this translate into a world of fragments?
Well, as is typical, there is good news, and there is other news.
The good news is that fragments have onSaveInstanceState()
methods that they can override, behaving much like their activity counterparts. The Bundle
then is made available in a variety of places, such as onCreate()
and onActivityCreated()
, though there is no dedicated onRestoreInstanceState()
.
The other news is that not only do fragments lack onRetainNonConfigurationInstance()
, but the ACL's FragmentActivity
does not allow you to extend onRetainNonConfigurationInstance()
, as that is used internally. Applications using the direct Android implementation of fragments do not suffer from this problem. This limitation is substantial, and the developer community is still collectively working out ways to get past the limitation.
The overall design approach for fragments favors having business logic in the fragment, with activities serving as an orchestration layer for interfragment navigation and things that fragments are incapable of (e.g., onRetainNonConfigurationInstance()
). For example, the Gmail application originally probably had much of its business logic implemented in each activity (e.g., an activity for folders, an activity for a list of conversations, an activity for a single conversation). Nowadays, that application is probably built around having that business logic delegated to fragments, with the activities merely choosing which fragments to display based upon available screen size.
This has caused quite a bit of restructuring of existing applications since fragments debuted at the start of 2011. For example, a ListActivity
might have launched another activity from onListItemClick()
. The first-cut refactoring of that would have the fragment's onListItemClick()
launch an activity. However, the fragment does not know whether or not the content requested by the user should be shown in another activity—it might go to another fragment within the current activity. Hence, the fragment should not blindly call startActivity()
but rather should call a method on its container activity (or, more likely, a listener interface implemented by that activity), telling it of the click event and letting it decide the right course of action.