Using Preferences

Android has many different ways for you to store data for long-term use by your activity. The simplest ones to use are SharedPreferences and simple files.

Android allows activities and applications to keep preferences, in the form of key/value pairs (akin to a Map), that will hang around between invocations of an activity. As the name suggests, the primary purpose is for you to store user-specified configuration details, such as the last feed the user looked at in your feed reader, or what sort order to use by default on a list, or whatever. Of course, you can store in the preferences whatever you like, so long as it is keyed by a String and has a primitive value (boolean, String, etc.)

Preferences can either be for a single activity or shared among all activities in an application. Other components, such as services, also can work with shared preferences.

Getting What You Want

To get access to the preferences, you have three APIs to choose from:

The first two take a security mode parameter. The right answer here is MODE_PRIVATE, so no other applications can access the file. The getSharedPreferences() method also takes a name of a set of preferences; getPreferences() effectively calls getSharedPreferences() with the activity’s class name as the preference set name. The getDefaultSharedPreferences() method takes the Context for the preferences (e.g., your Activity).

All of those methods return an instance of SharedPreferences, which offers a series of getters to access named preferences, returning a suitably-typed result (e.g., getBoolean() to return a boolean preference). The getters also take a default value, which is returned if there is no preference set under the specified key.

Unless you have a good reason to do otherwise, you are best served using the third option above — getDefaultSharedPreferences() — as that will give you the SharedPreferences object that works with a PreferenceActivity by default, as will be described later in this chapter.

Stating Your Preference

Given the appropriate SharedPreferences object, you can use edit() to get an “editor” for the preferences. This object has a set of setters that mirror the getters on the parent SharedPreferences object. It also has:

  1. remove() to get rid of a single named preference
  2. clear() to get rid of all preferences
  3. apply() or commit() to persist your changes made via the editor

The last one is important — if you modify preferences via the editor and fail to save the changes, those changes will evaporate once the editor goes out of scope. commit() is a blocking call, while apply() works asynchronously. Ideally, use apply() where possible, though it was only added in Android 2.3, so it may not be available to you if you are aiming to support earlier versions of Android than that.

Conversely, since the preferences object supports live changes, if one part of your application (say, an activity) modifies shared preferences, another part of your application (say, a service) will have access to the changed value immediately.

Collecting Preferences with PreferenceFragment

Some “preferences” will be collected as part of the natural use of your user interface. For example, if you have a SeekBar to control a zoom level, you might elect to record the SeekBar position in SharedPreferences, so you can restore the user’s last zoom level later on.

However, in many cases, we have various settings that we would like the user to be able to configure but are not something that the user would configure elsewhere in our UI. You could roll your own UI to collect preferences in bulk from the user. On the whole, this is a bad idea. Instead, use preference XML resources and a PreferenceFragment.

Why?

One of the common complaints about Android developers is that they lack discipline, not following any standards or conventions inherent in the platform. For other operating systems, the device manufacturer might prevent you from distributing apps that violate their human interface guidelines. With Android, that is not the case — but this is not a blanket permission to do whatever you want. Where there is a standard or convention, please follow it unless you have a clear reason not to, so that users will feel more comfortable with your app and their device.

Using a PreferenceFragment for collecting preferences is one such convention.

The linchpin to the preferences framework and PreferenceFragment is yet another set of XML data structures. You can describe your application’s preferences in XML files stored in your project’s res/xml/ directory. Given that, Android can present a UI for manipulating those preferences, one which matches what you see in the Settings app. The user’s choices are then stored in the SharedPreferences that you get back from getDefaultSharedPreferences().

This can be seen in the Prefs/Fragment sample project.

Showing the Current Values

This project’s main activity hosts a TableLayout, into which we will load the values of five preferences:

<?xml version="1.0" encoding="utf-8"?>
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="match_parent">

  <TableRow>

    <TextView
      style="@style/label"
      android:text="@string/checkbox"/>

    <TextView
      android:id="@+id/checkbox"
      style="@style/value"/>
  </TableRow>

  <TableRow>

    <TextView
      style="@style/label"
      android:text="@string/ringtone"/>

    <TextView
      android:id="@+id/ringtone"
      style="@style/value"/>
  </TableRow>

  <TableRow>

    <TextView
      style="@style/label"
      android:text="@string/checkbox2"/>

    <TextView
      android:id="@+id/checkbox2"
      style="@style/value"/>
  </TableRow>

  <TableRow>

    <TextView
      style="@style/label"
      android:text="@string/text"/>

    <TextView
      android:id="@+id/text"
      style="@style/value"/>
  </TableRow>

  <TableRow>

    <TextView
      style="@style/label"
      android:text="@string/list"/>

    <TextView
      android:id="@+id/list"
      style="@style/value"/>
  </TableRow>

</TableLayout>

(from Prefs/Fragment/app/src/main/res/layout/content.xml)

The above layout is used by PreferenceContentsFragment, which populates the right-hand column of TextView widgets at runtime in onResume(), pulling the values from the default SharedPreferences for our application:

package com.commonsware.android.preffrag;

import android.app.Fragment;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

public class PreferenceContentsFragment extends Fragment {
  private TextView checkbox=null;
  private TextView ringtone=null;
  private TextView checkbox2=null;
  private TextView text=null;
  private TextView list=null;

  @Override
  public View onCreateView(LayoutInflater inflater, ViewGroup parent,
                           Bundle savedInstanceState) {
    View result=inflater.inflate(R.layout.content, parent, false);

    checkbox=(TextView)result.findViewById(R.id.checkbox);
    ringtone=(TextView)result.findViewById(R.id.ringtone);
    checkbox2=(TextView)result.findViewById(R.id.checkbox2);
    text=(TextView)result.findViewById(R.id.text);
    list=(TextView)result.findViewById(R.id.list);

    return(result);
  }

  @Override
  public void onResume() {
    super.onResume();

    SharedPreferences prefs=
        PreferenceManager.getDefaultSharedPreferences(getActivity());

    checkbox.setText(Boolean.valueOf(prefs.getBoolean("checkbox", false)).toString());
    ringtone.setText(prefs.getString("ringtone", "<unset>"));
    checkbox2.setText(Boolean.valueOf(prefs.getBoolean("checkbox2", false)).toString());
    text.setText(prefs.getString("text", "<unset>"));
    list.setText(prefs.getString("list", "<unset>"));
  }
}

(from Prefs/Fragment/app/src/main/java/com/commonsware/android/preffrag/PreferenceContentsFragment.java)

The main activity, FragmentsDemo, simply loads res/layout/main.xml, which contains a <fragment> element pointing at PreferenceContentsFragment. It also defines an options menu, which we will examine later in this section.

The result is an activity showing the default values of the preferences when it is first run, since we have not set any values yet:

Activity Showing Preference Values
Figure 296: Activity Showing Preference Values

Defining Your Preferences

First, you need to tell Android what preferences you are trying to collect from the user.

To do this, you will need to add a res/xml/ directory to your project, if one does not already exist. Then, for your PreferenceFragment, you will define one of these XML resource files. The root element of this XML file will be <PreferenceScreen>, and it will contain child elements, one per preference.

In the sample project, we have one such file, res/xml/preferences.xml:

<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">

  <CheckBoxPreference
    android:key="checkbox"
    android:summary="@string/pref1summary"
    android:title="@string/pref1title"/>

  <RingtonePreference
    android:key="ringtone"
    android:showDefault="true"
    android:showSilent="true"
    android:summary="@string/pref2summary"
    android:title="@string/pref2title"/>

  <EditTextPreference
    android:dialogTitle="@string/dialogtitle"
    android:key="text"
    android:summary="@string/pref3summary"
    android:title="@string/pref3title"/>

  <ListPreference
    android:dialogTitle="@string/listdialogtitle"
    android:entries="@array/cities"
    android:entryValues="@array/airport_codes"
    android:key="list"
    android:summary="@string/pref4summary"
    android:title="@string/pref4title"/>

</PreferenceScreen>
(from Prefs/Fragment/app/src/main/res/xml/preferences.xml)

Each preference element has two attributes at minimum:

  1. android:key, which is the key you use to look up the value in the SharedPreferences object via methods like getInt()
  2. android:title, which is a few words identifying this preference to the user

You may also wish to consider having android:summary, which is a short sentence explaining what the user is to supply for this preference.

There are lots of other attributes that are common to all preference elements, and there are more types of preference elements than the ones that we used in the preference XML shown above. We will examine more preference elements later in this chapter.

Creating Your PreferenceFragment

Preference XML, on API Level 11 and higher, is loaded by an implementation of PreferenceFragment. The mission of PreferenceFragment is to call addPreferencesFromResource() in onCreate(), supplying the resource ID of the preference XML to load (e.g., R.xml.preference2). That fragment, in turn, can be loaded up by a simple Activity.

In fact, the fragment is so short, you could even make it be a static class inside the activity, as is done in the sample app. The activity that collects the preferences, EditPreferences, has a Prefs static subclass of PreferenceFragment:

package com.commonsware.android.preffrag;

import android.app.Activity;
import android.os.Bundle;
import android.preference.PreferenceFragment;

public class EditPreferences extends Activity {
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    if (getFragmentManager().findFragmentById(android.R.id.content)==null) {
      getFragmentManager().beginTransaction()
          .add(android.R.id.content,
              new Prefs()).commit();
    }
  }

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

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

(from Prefs/Fragment/app/src/main/java/com/commonsware/android/preffrag/EditPreferences.java)

The only thing that Prefs does is call the inherited addPreferencesFromResource() in its onCreate() method, supplying the ID of the preference XML. All EditPreferences does is arrange to show the fragment, in this case using a FragmentTransaction.

The Results

An action bar item in MainActivity starts up the EditPreferences activity. If you click that from the overflow, you will get see the UI created from your XML by means of the PreferenceFragment:

Activity Collecting Preference Values
Figure 297: Activity Collecting Preference Values

If you make a change, such as tapping on the checkbox, and press BACK to return to the original activity, you will see the resulting change in the preference values themselves:

Original Activity, Showing Revised Preference Value
Figure 298: Original Activity, Showing Revised Preference Value

Android Studio’s Preferences Editor

If you open up a preference XML resource in Android Studio, you will be given an editor that is reminiscent of the layout resource editor. You will have two sub-tabs: “Text” with the XML and “Design” with a drag-and-drop UI:

Android Studio Preferences Editor
Figure 299: Android Studio Preferences Editor

The drag-and-drop editor UI works akin to its layout resource editor counterpart. You can drag a preference from the Palette into either the preview area or into the Component Tree to add it to the resource. For any selected preference, the Properties pane allows you to modify attributes, either from the default short list of popular properties or the full list of properties that you get from clicking “View all properties”.

Types of Preferences

There are a variety of subclasses of Preference in the Android SDK for use with PreferenceActivity. This section will outline the major ones. Later in the book we will examine how to create your own custom Preference classes.

CheckBoxPreference and SwitchPreference

The sample application shown above has a pair of CheckBoxPreference elements, one per preference XML file. A CheckBoxPreference is an “inline” preference, in that the widget the user interacts with (in this case, a CheckBox) is part of the preference screen itself, rather than contained in a separate dialog.

SwitchPreference is functionally equivalent to CheckBoxPreference, insofar as both collect boolean values from the user. The difference is that SwitchPreference uses a Switch widget that the user slides left and right to toggle between “on” and “off” states. Also note that SwitchPreference was added in API Level 14 and therefore will not be available to older Android versions.

EditTextPreference

EditTextPreference, when tapped by the user, pops up a dialog that contains an EditText widget. You can configure this widget via attributes on the <EditTextPreference> element — in addition to standard preference attributes like android:key, you can include any attribute understood by EditText, such as android:inputType.

The value stored in the SharedPreferences is a string.

The sample app has an EditTextPreference:

  <EditTextPreference
    android:dialogTitle="@string/dialogtitle"
    android:key="text"
    android:summary="@string/pref3summary"
    android:title="@string/pref3title"/>

(from Prefs/Fragment/app/src/main/res/xml/preferences.xml)

When the user taps on it in the PreferenceFragment, the user will see a dialog where they can fill in a value, or edit an existing value if they provided one previously:

EditTextPreference UI
Figure 300: EditTextPreference UI

RingtonePreference

RingtonePreference pops up a dialog with a list of ringtones installed on the device or emulator. However, bear in mind that older emulator images may not have any pre-installed ringtones.

In addition to the standard preference attributes, you can include android:showDefault, indicating that the list should contain a “Default ringtone” option. If the user chooses this ringtone, they are effectively choosing the same ringtone that they have set up for incoming phone calls.

You can also use android:showSilent, which allows the user to choose a “Silence” pseudo-ringtone, to indicate not to play any ringtone.

The sample app has a RingtonePreference:

  <RingtonePreference
    android:key="ringtone"
    android:showDefault="true"
    android:showSilent="true"
    android:summary="@string/pref2summary"
    android:title="@string/pref2title"/>

(from Prefs/Fragment/app/src/main/res/xml/preferences.xml)

When the user taps on it in the PreferenceFragment, the user will see a roster of ringtones, along with “Default” and “None” options, since we opted into those:

RingtonePreference UI
Figure 301: RingtonePreference UI

The value stored in the SharedPreferences is a string, specifically the string representation of a Uri pointing to a ContentProvider that can serve up the ringtone for playback. The use of ContentProvider will be covered in a later chapter, and playing back media like ringtones will be covered in another later chapter.

ListPreference and MultiSelectListPreference

Visually, a ListPreference looks just like RingtonePreference, except that you control what goes into the list. You do this by specifying a pair of string-array resources in your preference XML.

String resources hold individual strings; string array resources hold a collection of strings. Typically, you will find string array resources in res/values/arrays.xml and related resource sets for translation. The <string-array> element has the name attribute to identify the resource, along with child <item> elements for the individual strings in the array.

So, our sample app has a pair of <string-array> resources in res/values/arrays.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
  <string-array name="cities">
    <item>Philadelphia</item>
    <item>Pittsburgh</item>
    <item>Allentown/Bethlehem</item>
    <item>Erie</item>
    <item>Reading</item>
    <item>Scranton</item>
    <item>Lancaster</item>
    <item>Altoona</item>
    <item>Harrisburg</item>
  </string-array>
  <string-array name="airport_codes">
    <item>PHL</item>
    <item>PIT</item>
    <item>ABE</item>
    <item>ERI</item>
    <item>RDG</item>
    <item>AVP</item>
    <item>LNS</item>
    <item>AOO</item>
    <item>MDT</item>
  </string-array>
</resources>

(from Prefs/Fragment/app/src/main/res/values/arrays.xml)

Here, the actual strings are written in-line. They could just as easily be references to string resource (e.g., <item>@string/philly</item>). For user-facing strings, like those in the cities array, having them as string resources may make it easier for you to manage your translations.

The sample app then uses those arrays in a ListPreference:

  <ListPreference
    android:dialogTitle="@string/listdialogtitle"
    android:entries="@array/cities"
    android:entryValues="@array/airport_codes"
    android:key="list"
    android:summary="@string/pref4summary"
    android:title="@string/pref4title"/>

(from Prefs/Fragment/app/src/main/res/xml/preferences.xml)

This then allows the user to choose a city, when the user taps on this preference in the PreferenceFragment:

ListPreference UI
Figure 302: ListPreference UI

However, when the user chooses a city by name (e.g., Philadelphia), what is stored in the SharedPreferences is the corresponding airport code (e.g., PHL).

MultiSelectListPreference works much the same way, except:

We will see MultiSelectListPreference in action later in the book.