Chapter 28

Fragments

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.

Introducing Fragments

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.

The Problem Addressed by Fragments

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.

The Fragments Solution

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:

  • Capability to add fragments dynamically based on user interaction: For example, the Gmail application initially shows a 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.
  • Capability to animate dynamic fragments as they move on and off the screen: For example, when the user taps a conversation in Gmail, the folders 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.
  • Automatic Back button management for dynamic fragments: For example, when the user presses Back while viewing the messages 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.
  • Capability to add options to the options menu, and therefore to the action bar: Call 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.
  • Capability to add tabs to the action bar: The action bar can have tabs, replacing a 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.

The Android Compatibility Library

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.

Creating Fragment Classes

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.

General Fragments

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.

ListFragment

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;
  }
}
Persistent Highlight

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.

Other Fragment Base Classes

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

Fragments, Layouts, Activities, and Multiple Screen Sizes

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:

  • You can define them via <fragment> elements in the activity's layout. These fragments are fixed and will always exist for the lifetime of this activity instance.
  • You can add them on-the-fly via 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:

  • Define an interface for the methods that the fragment will want to call on its activity (or some other object supplied by that activity).
  • The activity provides an implementation of that interface via some setter method on the fragment when the fragment is created.
  • The fragment uses that interface implementation as needed.

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 screens
  • DetailsActivity: Hosts the DetailsFragment on smaller screens

While 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.

EU4You

First we'll take a look at the pieces of the EU4You activity.

The Layout

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.

The Other Layout

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.

The Listener Interface

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 Activity

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:

  • Finds the CountriesFragment and registers itself as the CountryListener, since EU4You implements that interface.
  • Finds the 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.

DetailsActivity

The DetailsActivity will be used where the DetailsFragment is not being shown in the EU4You activity, including in the following cases:

  • When the device has a normal screen size and therefore does not have the DetailsFragment in the layout
  • When the device has a large screen in the portrait size and therefore EU4You is hiding its own DetailsFragment
The Layout

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"
/>
The Activity

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));
  }
}

Fragments and Configuration Changes

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.

Designing for Fragments

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.140.188.241