The following is the first few sections of a chapter from The Busy Coder's Guide to Android Development, plus headings for the remaining major sections, to give you an idea about the content of the chapter.


Advanced Preferences

We saw SharedPreferences and PreferenceFragment earlier in the book. However, we can have more elaborate preference collection options if we wish, such as a full master-detail implementation like the Settings app sports. There are also many other common attributes on the preference XML elements that we might consider taking advantage of, such as allowing us to automatically enable and disable preferences based upon whether some other preference is checked or unchecked.

In this chapter, we will explore some of these additional capabilities in the world of Android preferences.

Prerequisites

Understanding this chapter requires that you have read the core chapters, particularly the one on SharedPreferences.

Introducing PreferenceActivity

If you have a fairly simple set of preferences to collect from the user, using a single PreferenceFragment should be sufficient.

On the far other end of the spectrum, Android’s Settings app collects a massive amount of preference values from the user. These are spread across a series of groups of preferences, known as preference headers.

While your app may not need to collect as many preferences as does the Settings app, you may need more than what could be collected easily in a single PreferenceFragment. In that case, you can consider adopting the same structure of headers-and-fragments that the Settings app uses, by means of a PreferenceActivity.

To see this in action, take a look at the Prefs/FragmentsBC sample project. It is very similar to the original SharedPreferences demo app from before. However, this one arranges to collect a fifth preference value, in a separate PreferenceFragment, and uses PreferenceActivity to allow access to both PreferenceFragment UI structures.

Defining Your Preference Headers

In the master-detail approach offered by PreferenceActivity, the “master” list is a collection of preference headers. Typically, you would define these in another XML resource. In the sample project, that is found in res/xml/preference_headers.xml:

<preference-headers xmlns:android="http://schemas.android.com/apk/res/android">

  <header
    android:fragment="com.commonsware.android.preffragsbc.EditPreferences$First"
    android:summary="@string/header1summary"
    android:title="@string/header1title">
  </header>
  <header
    android:fragment="com.commonsware.android.preffragsbc.EditPreferences$Second"
    android:summary="@string/header2summary"
    android:title="@string/header2title">
  </header>

</preference-headers>

Here, your root element is <preference-headers>, containing a series of <header> elements. Each <header> contains at least three attributes:

  1. android:fragment, which identifies the Java class implementing the PreferenceFragment to use for this header, as is described in the next section
  2. android:title, which is a few words identifying this header to the user

Once again, you may wish to also include android:summary, which is a short sentence explaining what the user will find inside of this header.

You can, if you wish, include one or more <extra> child elements inside the <header> element. These values will be put into the “arguments” Bundle that the associated PreferenceFragment can retrieve via getArguments().

Creating Your PreferenceActivity

EditPreferences — which in the original sample app was a regular Activity — is now a PreferenceActivity. It contains little more than the two fragments referenced in the above preference header XML:

package com.commonsware.android.preffragsbc;

import android.os.Bundle;
import android.preference.PreferenceActivity;
import android.preference.PreferenceFragment;
import java.util.List;

public class EditPreferences extends PreferenceActivity {
  @Override
  public void onBuildHeaders(List<Header> target) {
    loadHeadersFromResource(R.xml.preference_headers, target);
  }

  @Override
  protected boolean isValidFragment(String fragmentName) {
    if (First.class.getName().equals(fragmentName)
        || Second.class.getName().equals(fragmentName)) {
      return(true);
    }

    return(false);
  }

  public static class First extends PreferenceFragment {
    @Override
    public void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);

      addPreferencesFromResource(R.xml.preferences);
    }
  }

  public static class Second extends PreferenceFragment {
    @Override
    public void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);

      addPreferencesFromResource(R.xml.preferences2);
    }
  }
}

onBuildHeaders() is where we supply the preference headers, via a call to loadHeadersFromResource().

We also need to have an isValidFragment() method, that will return true if the supplied fragment name is one we should be showing in this PreferenceActivity, false otherwise. This will only be called on Android 4.4+. However, we need to set up the project build target (e.g., compileSdkVersion in Android Studio) to API Level 19 or higher. Failing to have this method will cause your app to crash on Android 4.4+ devices, when the user tries to bring up one of your PreferenceFragments.

Each PreferenceFragment is then responsible for calling addPreferencesFromResource() to populate its contents. In this case, we now have two such resources: res/xml/preferences.xml (the original, used by First) and res/xml/preferences2.xml (used by Second).

The Results

On a wide enough screen — like that of a Nexus 9 in landscape — we get a master-detail presentation:

PreferenceActivity UI, on a Landscape Nexus 9
Figure 614: PreferenceActivity UI, on a Landscape Nexus 9

Here, we see the first preference fragment already pre-selected, showing its settings. Tapping on the second header will show the other preferences.

On a smaller screen, the master-detail approach means that we see a list of headers first:

PreferenceActivity UI, on a Portrait Nexus 5
Figure 615: PreferenceActivity UI, on a Portrait Nexus 5

Tapping the headers give us access to the individual fragments.

Intents for Headers or Preferences

The preview of this section may contain nuts.

Conditional Headers

The preview of this section is being chased by zombies.

Dependent Preferences

The preview of this section was whisked away by a shark-infested tornado.

Nested Screens

The preview of this section was eaten by a grue.

Listening to Preference Changes

The preview of this section was the victim of a MITM ('Martian in the middle') attack.

Defaults, and Defaults

The preview of this section was the victim of a MITM ('Martian in the middle') attack.

Listening to Preference Value Changes

The preview of this section was abducted by space aliens.

Dynamic ListPreference Contents

The preview of this section was whisked away by a shark-infested tornado.

Dealing with External Changes to Preferences

The preview of this section apparently resembled a Pokémon.

Preferences in Device Settings App

The preview of this section is unavailable right now, but if you leave your name and number at the sound of the tone, it might get back to you (BEEEEEEEEEEEEP!).