page.title=Settings page.tags=preference,preferenceactivity,preferencefragment @jd:body
Applications often include settings that allow users to modify app features and behaviors. For example, some apps allow users to specify whether notifications are enabled or specify how often the application syncs data with the cloud.
If you want to provide settings for your app, you should use Android's {@link android.preference.Preference} APIs to build an interface that's consistent with the user experience in other Android apps (including the system settings). This document describes how to build your app settings using {@link android.preference.Preference} APIs.
Settings Design
For information about how to design your settings, read the Settings design guide.
Instead of using {@link android.view.View} objects to build the user interface, settings are built using various subclasses of the {@link android.preference.Preference} class that you declare in an XML file.
A {@link android.preference.Preference} object is the building block for a single setting. Each {@link android.preference.Preference} appears as an item in a list and provides the appropriate UI for users to modify the setting. For example, a {@link android.preference.CheckBoxPreference} creates a list item that shows a checkbox, and a {@link android.preference.ListPreference} creates an item that opens a dialog with a list of choices.
Each {@link android.preference.Preference} you add has a corresponding key-value pair that the system uses to save the setting in a default {@link android.content.SharedPreferences} file for your app's settings. When the user changes a setting, the system updates the corresponding value in the {@link android.content.SharedPreferences} file for you. The only time you should directly interact with the associated {@link android.content.SharedPreferences} file is when you need to read the value in order to determine your app's behavior based on the user's setting.
The value saved in {@link android.content.SharedPreferences} for each setting can be one of the following data types:
Because your app's settings UI is built using {@link android.preference.Preference} objects instead of {@link android.view.View} objects, you need to use a specialized {@link android.app.Activity} or {@link android.app.Fragment} subclass to display the list settings:
How to set up your {@link android.preference.PreferenceActivity} and instances of {@link android.preference.PreferenceFragment} is discussed in the sections about Creating a Preference Activity and Using Preference Fragments.
Every setting for your app is represented by a specific subclass of the {@link android.preference.Preference} class. Each subclass includes a set of core properties that allow you to specify things such as a title for the setting and the default value. Each subclass also provides its own specialized properties and user interface. For instance, figure 1 shows a screenshot from the Messaging app's settings. Each list item in the settings screen is backed by a different {@link android.preference.Preference} object.
A few of the most common preferences are:
true
if it's checked).See the {@link android.preference.Preference} class for a list of all other subclasses and their corresponding properties.
Of course, the built-in classes don't accommodate every need and your application might require something more specialized. For example, the platform currently does not provide a {@link android.preference.Preference} class for picking a number or a date. So you might need to define your own {@link android.preference.Preference} subclass. For help doing so, see the section about Building a Custom Preference.
Although you can instantiate new {@link android.preference.Preference} objects at runtime, you should define your list of settings in XML with a hierarchy of {@link android.preference.Preference} objects. Using an XML file to define your collection of settings is preferred because the file provides an easy-to-read structure that's simple to update. Also, your app's settings are generally pre-determined, although you can still modify the collection at runtime.
Each {@link android.preference.Preference} subclass can be declared with an XML element that matches the class name, such as {@code <CheckBoxPreference>}.
You must save the XML file in the {@code res/xml/} directory. Although you can name the file anything you want, it's traditionally named {@code preferences.xml}. You usually need only one file, because branches in the hierarchy (that open their own list of settings) are declared using nested instances of {@link android.preference.PreferenceScreen}.
Note: If you want to create a multi-pane layout for your settings, then you need separate XML files for each fragment.
The root node for the XML file must be a {@link android.preference.PreferenceScreen <PreferenceScreen>} element. Within this element is where you add each {@link android.preference.Preference}. Each child you add within the {@link android.preference.PreferenceScreen <PreferenceScreen>} element appears as a single item in the list of settings.
For example:
<?xml version="1.0" encoding="utf-8"?> <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> <CheckBoxPreference android:key="pref_sync" android:title="@string/pref_sync" android:summary="@string/pref_sync_summ" android:defaultValue="true" /> <ListPreference android:dependency="pref_sync" android:key="pref_syncConnectionType" android:title="@string/pref_syncConnectionType" android:dialogTitle="@string/pref_syncConnectionType" android:entries="@array/pref_syncConnectionTypes_entries" android:entryValues="@array/pref_syncConnectionTypes_values" android:defaultValue="@string/pref_syncConnectionTypes_default" /> </PreferenceScreen>
In this example, there's a {@link android.preference.CheckBoxPreference} and a {@link android.preference.ListPreference}. Both items include the following three attributes:
The only instances in which this attribute is not required is when the preference is a {@link android.preference.PreferenceCategory} or {@link android.preference.PreferenceScreen}, or the preference specifies an {@link android.content.Intent} to invoke (with an {@code <intent>} element) or a {@link android.app.Fragment} to display (with an {@code android:fragment} attribute).
For information about all other supported attributes, see the {@link android.preference.Preference} (and respective subclass) documentation.
When your list of settings exceeds about 10 items, you might want to add titles to define groups of settings or display those groups in a separate screen. These options are described in the following sections.
If you present a list of 10 or more settings, users may have difficulty scanning, comprehending, and processing them. You can remedy this by dividing some or all of the settings into groups, effectively turning one long list into multiple shorter lists. A group of related settings can be presented in one of two ways:
You can use one or both of these grouping techniques to organize your app's settings. When deciding which to use and how to divide your settings, you should follow the guidelines in Android Design's Settings guide.
If you want to provide dividers with headings between groups of settings (as shown in figure 2), place each group of {@link android.preference.Preference} objects inside a {@link android.preference.PreferenceCategory}.
For example:
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> <PreferenceCategory android:title="@string/pref_sms_storage_title" android:key="pref_key_storage_settings"> <CheckBoxPreference android:key="pref_key_auto_delete" android:summary="@string/pref_summary_auto_delete" android:title="@string/pref_title_auto_delete" android:defaultValue="false"... /> <Preference android:key="pref_key_sms_delete_limit" android:dependency="pref_key_auto_delete" android:summary="@string/pref_summary_delete_limit" android:title="@string/pref_title_sms_delete"... /> <Preference android:key="pref_key_mms_delete_limit" android:dependency="pref_key_auto_delete" android:summary="@string/pref_summary_delete_limit" android:title="@string/pref_title_mms_delete" ... /> </PreferenceCategory> ... </PreferenceScreen>
If you want to place groups of settings into a subscreen (as shown in figure 3), place the group of {@link android.preference.Preference} objects inside a {@link android.preference.PreferenceScreen}.
For example:
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> <!-- opens a subscreen of settings --> <PreferenceScreen android:key="button_voicemail_category_key" android:title="@string/voicemail" android:persistent="false"> <ListPreference android:key="button_voicemail_provider_key" android:title="@string/voicemail_provider" ... /> <!-- opens another nested subscreen --> <PreferenceScreen android:key="button_voicemail_setting_key" android:title="@string/voicemail_settings" android:persistent="false"> ... </PreferenceScreen> <RingtonePreference android:key="button_voicemail_ringtone_key" android:title="@string/voicemail_ringtone_title" android:ringtoneType="notification" ... /> ... </PreferenceScreen> ... </PreferenceScreen>
In some cases, you might want a preference item to open a different activity instead of a settings screen, such as a web browser to view a web page. To invoke an {@link android.content.Intent} when the user selects a preference item, add an {@code <intent>} element as a child of the corresponding {@code <Preference>} element.
For example, here's how you can use a preference item to open a web page:
<Preference android:title="@string/prefs_web_page" > <intent android:action="android.intent.action.VIEW" android:data="http://www.example.com" /> </Preference>
You can create both implicit and explicit intents using the following attributes:
To display your settings in an activity, extend the {@link android.preference.PreferenceActivity} class. This is an extension of the traditional {@link android.app.Activity} class that displays a list of settings based on a hierarchy of {@link android.preference.Preference} objects. The {@link android.preference.PreferenceActivity} automatically persists the settings associated with each {@link android.preference.Preference} when the user makes a change.
Note: If you're developing your application for Android 3.0 and higher, you should instead use {@link android.preference.PreferenceFragment}. Go to the next section about Using Preference Fragments.
The most important thing to remember is that you do not load a layout of views during the {@link android.preference.PreferenceActivity#onCreate onCreate()} callback. Instead, you call {@link android.preference.PreferenceActivity#addPreferencesFromResource addPreferencesFromResource()} to add the preferences you've declared in an XML file to the activity. For example, here's the bare minimum code required for a functional {@link android.preference.PreferenceActivity}:
public class SettingsActivity extends PreferenceActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); addPreferencesFromResource(R.xml.preferences); } }
This is actually enough code for some apps, because as soon as the user modifies a preference, the system saves the changes to a default {@link android.content.SharedPreferences} file that your other application components can read when you need to check the user's settings. Many apps, however, require a little more code in order to listen for changes that occur to the preferences. For information about listening to changes in the {@link android.content.SharedPreferences} file, see the section about Reading Preferences.
If you're developing for Android 3.0 (API level 11) and higher, you should use a {@link android.preference.PreferenceFragment} to display your list of {@link android.preference.Preference} objects. You can add a {@link android.preference.PreferenceFragment} to any activity—you don't need to use {@link android.preference.PreferenceActivity}.
Fragments provide a more flexible architecture for your application, compared to using activities alone, no matter what kind of activity you're building. As such, we suggest you use {@link android.preference.PreferenceFragment} to control the display of your settings instead of {@link android.preference.PreferenceActivity} when possible.
Your implementation of {@link android.preference.PreferenceFragment} can be as simple as defining the {@link android.preference.PreferenceFragment#onCreate onCreate()} method to load a preferences file with {@link android.preference.PreferenceFragment#addPreferencesFromResource addPreferencesFromResource()}. For example:
public static class SettingsFragment extends PreferenceFragment { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Load the preferences from an XML resource addPreferencesFromResource(R.xml.preferences); } ... }
You can then add this fragment to an {@link android.app.Activity} just as you would for any other {@link android.app.Fragment}. For example:
public class SettingsActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Display the fragment as the main content. getFragmentManager().beginTransaction() .replace(android.R.id.content, new SettingsFragment()) .commit(); } }
Note: A {@link android.preference.PreferenceFragment} doesn't have a its own {@link android.content.Context} object. If you need a {@link android.content.Context} object, you can call {@link android.app.Fragment#getActivity()}. However, be careful to call {@link android.app.Fragment#getActivity()} only when the fragment is attached to an activity. When the fragment is not yet attached, or was detached during the end of its lifecycle, {@link android.app.Fragment#getActivity()} will return null.
The preferences you create probably define some important behaviors for your application, so it's necessary that you initialize the associated {@link android.content.SharedPreferences} file with default values for each {@link android.preference.Preference} when the user first opens your application.
The first thing you must do is specify a default value for each {@link android.preference.Preference} object in your XML file using the {@code android:defaultValue} attribute. The value can be any data type that is appropriate for the corresponding {@link android.preference.Preference} object. For example:
<!-- default value is a boolean --> <CheckBoxPreference android:defaultValue="true" ... /> <!-- default value is a string --> <ListPreference android:defaultValue="@string/pref_syncConnectionTypes_default" ... />
Then, from the {@link android.app.Activity#onCreate onCreate()} method in your application's main activity—and in any other activity through which the user may enter your application for the first time—call {@link android.preference.PreferenceManager#setDefaultValues setDefaultValues()}:
PreferenceManager.setDefaultValues(this, R.xml.advanced_preferences, false);
Calling this during {@link android.app.Activity#onCreate onCreate()} ensures that your application is properly initialized with default settings, which your application might need to read in order to determine some behaviors (such as whether to download data while on a cellular network).
This method takes three arguments:
When false
, the system sets the default values only if this method has never been
called in the past (or the {@link android.preference.PreferenceManager#KEY_HAS_SET_DEFAULT_VALUES}
in the default value shared preferences file is false).
As long as you set the third argument to false
, you can safely call this method
every time your activity starts without overriding the user's saved preferences by resetting them to
the defaults. However, if you set it to true
, you will override any previous
values with the defaults.
In rare cases, you might want to design your settings such that the first screen displays only a list of subscreens (such as in the system Settings app, as shown in figures 4 and 5). When you're developing such a design for Android 3.0 and higher, you should use a new "headers" feature in Android 3.0, instead of building subscreens with nested {@link android.preference.PreferenceScreen} elements.
To build your settings with headers, you need to:
A great benefit to using this design is that {@link android.preference.PreferenceActivity} automatically presents the two-pane layout shown in figure 4 when running on large screens.
Even if your application supports versions of Android older than 3.0, you can build your application to use {@link android.preference.PreferenceFragment} for a two-pane presentation on newer devices while still supporting a traditional multi-screen hierarchy on older devices (see the section about Supporting older versions with preference headers).
Each group of settings in your list of headers is specified by a single {@code <header>} element inside a root {@code <preference-headers>} element. For example:
<?xml version="1.0" encoding="utf-8"?> <preference-headers xmlns:android="http://schemas.android.com/apk/res/android"> <header android:fragment="com.example.prefs.SettingsActivity$SettingsFragmentOne" android:title="@string/prefs_category_one" android:summary="@string/prefs_summ_category_one" /> <header android:fragment="com.example.prefs.SettingsActivity$SettingsFragmentTwo" android:title="@string/prefs_category_two" android:summary="@string/prefs_summ_category_two" > <!-- key/value pairs can be included as arguments for the fragment. --> <extra android:name="someKey" android:value="someHeaderValue" /> </header> </preference-headers>
With the {@code android:fragment} attribute, each header declares an instance of {@link android.preference.PreferenceFragment} that should open when the user selects the header.
The {@code <extras>} element allows you to pass key-value pairs to the fragment in a {@link android.os.Bundle}. The fragment can retrieve the arguments by calling {@link android.app.Fragment#getArguments()}. You might pass arguments to the fragment for a variety of reasons, but one good reason is to reuse the same subclass of {@link android.preference.PreferenceFragment} for each group and use the argument to specify which preferences XML file the fragment should load.
For example, here's a fragment that can be reused for multiple settings groups, when each header defines an {@code <extra>} argument with the {@code "settings"} key:
public static class SettingsFragment extends PreferenceFragment { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); String settings = getArguments().getString("settings"); if ("notifications".equals(settings)) { addPreferencesFromResource(R.xml.settings_wifi); } else if ("sync".equals(settings)) { addPreferencesFromResource(R.xml.settings_sync); } } }
To display the preference headers, you must implement the {@link android.preference.PreferenceActivity#onBuildHeaders onBuildHeaders()} callback method and call {@link android.preference.PreferenceActivity#loadHeadersFromResource loadHeadersFromResource()}. For example:
public class SettingsActivity extends PreferenceActivity { @Override public void onBuildHeaders(List<Header> target) { loadHeadersFromResource(R.xml.preference_headers, target); } }
When the user selects an item from the list of headers, the system opens the associated {@link android.preference.PreferenceFragment}.
Note: When using preference headers, your subclass of {@link android.preference.PreferenceActivity} doesn't need to implement the {@link android.preference.PreferenceActivity#onCreate onCreate()} method, because the only required task for the activity is to load the headers.
If your application supports versions of Android older than 3.0, you can still use headers to provide a two-pane layout when running on Android 3.0 and higher. All you need to do is create an additional preferences XML file that uses basic {@link android.preference.Preference <Preference>} elements that behave like the header items (to be used by the older Android versions).
Instead of opening a new {@link android.preference.PreferenceScreen}, however, each of the {@link android.preference.Preference <Preference>} elements sends an {@link android.content.Intent} to the {@link android.preference.PreferenceActivity} that specifies which preference XML file to load.
For example, here's an XML file for preference headers that is used on Android 3.0 and higher ({@code res/xml/preference_headers.xml}):
<preference-headers xmlns:android="http://schemas.android.com/apk/res/android"> <header android:fragment="com.example.prefs.SettingsFragmentOne" android:title="@string/prefs_category_one" android:summary="@string/prefs_summ_category_one" /> <header android:fragment="com.example.prefs.SettingsFragmentTwo" android:title="@string/prefs_category_two" android:summary="@string/prefs_summ_category_two" /> </preference-headers>
And here is a preference file that provides the same headers for versions older than Android 3.0 ({@code res/xml/preference_headers_legacy.xml}):
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> <Preference android:title="@string/prefs_category_one" android:summary="@string/prefs_summ_category_one" > <intent android:targetPackage="com.example.prefs" android:targetClass="com.example.prefs.SettingsActivity" android:action="com.example.prefs.PREFS_ONE" /> </Preference> <Preference android:title="@string/prefs_category_two" android:summary="@string/prefs_summ_category_two" > <intent android:targetPackage="com.example.prefs" android:targetClass="com.example.prefs.SettingsActivity" android:action="com.example.prefs.PREFS_TWO" /> </Preference> </PreferenceScreen>
Because support for {@code <preference-headers>} was added in Android 3.0, the system calls {@link android.preference.PreferenceActivity#onBuildHeaders onBuildHeaders()} in your {@link android.preference.PreferenceActivity} only when running on Androd 3.0 or higher. In order to load the "legacy" headers file ({@code preference_headers_legacy.xml}), you must check the Android version and, if the version is older than Android 3.0 ({@link android.os.Build.VERSION_CODES#HONEYCOMB}), call {@link android.preference.PreferenceActivity#addPreferencesFromResource addPreferencesFromResource()} to load the legacy header file. For example:
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ... if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { // Load the legacy preferences headers addPreferencesFromResource(R.xml.preference_headers_legacy); } } // Called only on Honeycomb and later @Override public void onBuildHeaders(List<Header> target) { loadHeadersFromResource(R.xml.preference_headers, target); }
The only thing left to do is handle the {@link android.content.Intent} that's passed into the activity to identify which preference file to load. So retrieve the intent's action and compare it to known action strings that you've used in the preference XML's {@code <intent>} tags:
final static String ACTION_PREFS_ONE = "com.example.prefs.PREFS_ONE"; ... @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); String action = getIntent().getAction(); if (action != null && action.equals(ACTION_PREFS_ONE)) { addPreferencesFromResource(R.xml.preferences); } ... else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { // Load the legacy preferences headers addPreferencesFromResource(R.xml.preference_headers_legacy); } }
Beware that consecutive calls to {@link android.preference.PreferenceActivity#addPreferencesFromResource addPreferencesFromResource()} will stack all the preferences in a single list, so be sure that it's only called once by chaining the conditions with else-if statements.
By default, all your app's preferences are saved to a file that's accessible from anywhere within your application by calling the static method {@link android.preference.PreferenceManager#getDefaultSharedPreferences PreferenceManager.getDefaultSharedPreferences()}. This returns the {@link android.content.SharedPreferences} object containing all the key-value pairs that are associated with the {@link android.preference.Preference} objects used in your {@link android.preference.PreferenceActivity}.
For example, here's how you can read one of the preference values from any other activity in your application:
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this); String syncConnPref = sharedPref.getString(SettingsActivity.KEY_PREF_SYNC_CONN, "");
There are several reasons you might want to be notified as soon as the user changes one of the preferences. In order to receive a callback when a change happens to any one of the preferences, implement the {@link android.content.SharedPreferences.OnSharedPreferenceChangeListener SharedPreference.OnSharedPreferenceChangeListener} interface and register the listener for the {@link android.content.SharedPreferences} object by calling {@link android.content.SharedPreferences#registerOnSharedPreferenceChangeListener registerOnSharedPreferenceChangeListener()}.
The interface has only one callback method, {@link android.content.SharedPreferences.OnSharedPreferenceChangeListener#onSharedPreferenceChanged onSharedPreferenceChanged()}, and you might find it easiest to implement the interface as a part of your activity. For example:
public class SettingsActivity extends PreferenceActivity implements OnSharedPreferenceChangeListener { public static final String KEY_PREF_SYNC_CONN = "pref_syncConnectionType"; ... public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { if (key.equals(KEY_PREF_SYNC_CONN)) { Preference connectionPref = findPreference(key); // Set summary to be the user-description for the selected value connectionPref.setSummary(sharedPreferences.getString(key, "")); } } }
In this example, the method checks whether the changed setting is for a known preference key. It calls {@link android.preference.PreferenceActivity#findPreference findPreference()} to get the {@link android.preference.Preference} object that was changed so it can modify the item's summary to be a description of the user's selection. That is, when the setting is a {@link android.preference.ListPreference} or other multiple choice setting, you should call {@link android.preference.Preference#setSummary setSummary()} when the setting changes to display the current status (such as the Sleep setting shown in figure 5).
Note: As described in the Android Design document about Settings, we recommend that you update the summary for a {@link android.preference.ListPreference} each time the user changes the preference in order to describe the current setting.
For proper lifecycle management in the activity, we recommend that you register and unregister your {@link android.content.SharedPreferences.OnSharedPreferenceChangeListener} during the {@link android.app.Activity#onResume} and {@link android.app.Activity#onPause} callbacks, respectively:
@Override protected void onResume() { super.onResume(); getPreferenceScreen().getSharedPreferences() .registerOnSharedPreferenceChangeListener(this); } @Override protected void onPause() { super.onPause(); getPreferenceScreen().getSharedPreferences() .unregisterOnSharedPreferenceChangeListener(this); }
Caution: When you call {@link android.content.SharedPreferences#registerOnSharedPreferenceChangeListener registerOnSharedPreferenceChangeListener()}, the preference manager does not currently store a strong reference to the listener. You must store a strong reference to the listener, or it will be susceptible to garbage collection. We recommend you keep a reference to the listener in the instance data of an object that will exist as long as you need the listener.
For example, in the following code, the caller does not keep a reference to the listener. As a result, the listener will be subject to garbage collection, and it will fail at some indeterminate time in the future:
prefs.registerOnSharedPreferenceChangeListener( // Bad! The listener is subject to garbage collection! new SharedPreferences.OnSharedPreferenceChangeListener() { public void onSharedPreferenceChanged(SharedPreferences prefs, String key) { // listener implementation } });
Instead, store a reference to the listener in an instance data field of an object that will exist as long as the listener is needed:
SharedPreferences.OnSharedPreferenceChangeListener listener = new SharedPreferences.OnSharedPreferenceChangeListener() { public void onSharedPreferenceChanged(SharedPreferences prefs, String key) { // listener implementation } }; prefs.registerOnSharedPreferenceChangeListener(listener);
Beginning with Android 4.0, the system's Settings application allows users to see how much network data their applications are using while in the foreground and background. Users can then disable the use of background data for individual apps. In order to avoid users disabling your app's access to data from the background, you should use the data connection efficiently and allow users to refine your app's data usage through your application settings.
For example, you might allow the user to control how often your app syncs data, whether your app performs uploads/downloads only when on Wi-Fi, whether your app uses data while roaming, etc. With these controls available to them, users are much less likely to disable your app's access to data when they approach the limits they set in the system Settings, because they can instead precisely control how much data your app uses.
Once you've added the necessary preferences in your {@link android.preference.PreferenceActivity} to control your app's data habits, you should add an intent filter for {@link android.content.Intent#ACTION_MANAGE_NETWORK_USAGE} in your manifest file. For example:
<activity android:name="SettingsActivity" ... > <intent-filter> <action android:name="android.intent.action.MANAGE_NETWORK_USAGE" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </activity>
This intent filter indicates to the system that this is the activity that controls your application's data usage. Thus, when the user inspects how much data your app is using from the system's Settings app, a View application settings button is available that launches your {@link android.preference.PreferenceActivity} so the user can refine how much data your app uses.
The Android framework includes a variety of {@link android.preference.Preference} subclasses that allow you to build a UI for several different types of settings. However, you might discover a setting you need for which there’s no built-in solution, such as a number picker or date picker. In such a case, you’ll need to create a custom preference by extending the {@link android.preference.Preference} class or one of the other subclasses.
When you extend the {@link android.preference.Preference} class, there are a few important things you need to do:
The following sections describe how to accomplish each of these tasks.
If you directly extend the {@link android.preference.Preference} class, you need to implement {@link android.preference.Preference#onClick()} to define the action that occurs when the user selects the item. However, most custom settings extend {@link android.preference.DialogPreference} to show a dialog, which simplifies the procedure. When you extend {@link android.preference.DialogPreference}, you must call {@link android.preference.DialogPreference#setDialogLayoutResource setDialogLayoutResourcs()} during in the class constructor to specify the layout for the dialog.
For example, here's the constructor for a custom {@link android.preference.DialogPreference} that declares the layout and specifies the text for the default positive and negative dialog buttons:
public class NumberPickerPreference extends DialogPreference { public NumberPickerPreference(Context context, AttributeSet attrs) { super(context, attrs); setDialogLayoutResource(R.layout.numberpicker_dialog); setPositiveButtonText(android.R.string.ok); setNegativeButtonText(android.R.string.cancel); setDialogIcon(null); } ... }
You can save a value for the setting at any time by calling one of the {@link android.preference.Preference} class's {@code persist*()} methods, such as {@link android.preference.Preference#persistInt persistInt()} if the setting's value is an integer or {@link android.preference.Preference#persistBoolean persistBoolean()} to save a boolean.
Note: Each {@link android.preference.Preference} can save only one data type, so you must use the {@code persist*()} method appropriate for the data type used by your custom {@link android.preference.Preference}.
When you choose to persist the setting can depend on which {@link android.preference.Preference} class you extend. If you extend {@link android.preference.DialogPreference}, then you should persist the value only when the dialog closes due to a positive result (the user selects the "OK" button).
When a {@link android.preference.DialogPreference} closes, the system calls the {@link
android.preference.DialogPreference#onDialogClosed onDialogClosed()} method. The method includes a
boolean argument that specifies whether the user result is "positive"—if the value is
true
, then the user selected the positive button and you should save the new value. For
example:
@Override protected void onDialogClosed(boolean positiveResult) { // When the user selects "OK", persist the new value if (positiveResult) { persistInt(mNewValue); } }
In this example, mNewValue
is a class member that holds the setting's current
value. Calling {@link android.preference.Preference#persistInt persistInt()} saves the value to
the {@link android.content.SharedPreferences} file (automatically using the key that's
specified in the XML file for this {@link android.preference.Preference}).
When the system adds your {@link android.preference.Preference} to the screen, it calls {@link android.preference.Preference#onSetInitialValue onSetInitialValue()} to notify you whether the setting has a persisted value. If there is no persisted value, this call provides you the default value.
The {@link android.preference.Preference#onSetInitialValue onSetInitialValue()} method passes
a boolean, restorePersistedValue
, to indicate whether a value has already been persisted
for the setting. If it is true
, then you should retrieve the persisted value by calling
one of the {@link
android.preference.Preference} class's {@code getPersisted*()} methods, such as {@link
android.preference.Preference#getPersistedInt getPersistedInt()} for an integer value. You'll
usually want to retrieve the persisted value so you can properly update the UI to reflect the
previously saved value.
If restorePersistedValue
is false
, then you
should use the default value that is passed in the second argument.
@Override protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) { if (restorePersistedValue) { // Restore existing state mCurrentValue = this.getPersistedInt(DEFAULT_VALUE); } else { // Set default state from the XML attribute mCurrentValue = (Integer) defaultValue; persistInt(mCurrentValue); } }
Each {@code getPersisted*()} method takes an argument that specifies the default value to use in case there is actually no persisted value or the key does not exist. In the example above, a local constant is used to specify the default value in case {@link android.preference.Preference#getPersistedInt getPersistedInt()} can't return a persisted value.
Caution: You cannot use the
defaultValue
as the default value in the {@code getPersisted*()} method, because
its value is always null when restorePersistedValue
is true
.
If the instance of your {@link android.preference.Preference} class specifies a default value (with the {@code android:defaultValue} attribute), then the system calls {@link android.preference.Preference#onGetDefaultValue onGetDefaultValue()} when it instantiates the object in order to retrieve the value. You must implement this method in order for the system to save the default value in the {@link android.content.SharedPreferences}. For example:
@Override protected Object onGetDefaultValue(TypedArray a, int index) { return a.getInteger(index, DEFAULT_VALUE); }
The method arguments provide everything you need: the array of attributes and the index position of the {@code android:defaultValue}, which you must retrieve. The reason you must implement this method to extract the default value from the attribute is because you must specify a local default value for the attribute in case the value is undefined.
Just like a {@link android.view.View} in a layout, your {@link android.preference.Preference} subclass is responsible for saving and restoring its state in case the activity or fragment is restarted (such as when the user rotates the screen). To properly save and restore the state of your {@link android.preference.Preference} class, you must implement the lifecycle callback methods {@link android.preference.Preference#onSaveInstanceState onSaveInstanceState()} and {@link android.preference.Preference#onRestoreInstanceState onRestoreInstanceState()}.
The state of your {@link android.preference.Preference} is defined by an object that implements the {@link android.os.Parcelable} interface. The Android framework provides such an object for you as a starting point to define your state object: the {@link android.preference.Preference.BaseSavedState} class.
To define how your {@link android.preference.Preference} class saves its state, you should extend the {@link android.preference.Preference.BaseSavedState} class. You need to override just a few methods and define the {@link android.preference.Preference.BaseSavedState#CREATOR} object.
For most apps, you can copy the following implementation and simply change the lines that handle the {@code value} if your {@link android.preference.Preference} subclass saves a data type other than an integer.
private static class SavedState extends BaseSavedState { // Member that holds the setting's value // Change this data type to match the type saved by your Preference int value; public SavedState(Parcelable superState) { super(superState); } public SavedState(Parcel source) { super(source); // Get the current preference's value value = source.readInt(); // Change this to read the appropriate data type } @Override public void writeToParcel(Parcel dest, int flags) { super.writeToParcel(dest, flags); // Write the preference's value dest.writeInt(value); // Change this to write the appropriate data type } // Standard creator object using an instance of this class public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() { public SavedState createFromParcel(Parcel in) { return new SavedState(in); } public SavedState[] newArray(int size) { return new SavedState[size]; } }; }
With the above implementation of {@link android.preference.Preference.BaseSavedState} added to your app (usually as a subclass of your {@link android.preference.Preference} subclass), you then need to implement the {@link android.preference.Preference#onSaveInstanceState onSaveInstanceState()} and {@link android.preference.Preference#onRestoreInstanceState onRestoreInstanceState()} methods for your {@link android.preference.Preference} subclass.
For example:
@Override protected Parcelable onSaveInstanceState() { final Parcelable superState = super.onSaveInstanceState(); // Check whether this Preference is persistent (continually saved) if (isPersistent()) { // No need to save instance state since it's persistent, // use superclass state return superState; } // Create instance of custom BaseSavedState final SavedState myState = new SavedState(superState); // Set the state's value with the class member that holds current // setting value myState.value = mNewValue; return myState; } @Override protected void onRestoreInstanceState(Parcelable state) { // Check whether we saved the state in onSaveInstanceState if (state == null || !state.getClass().equals(SavedState.class)) { // Didn't save the state, so call superclass super.onRestoreInstanceState(state); return; } // Cast state to custom BaseSavedState and pass to superclass SavedState myState = (SavedState) state; super.onRestoreInstanceState(myState.getSuperState()); // Set this Preference's widget to reflect the restored state mNumberPicker.setValue(myState.value); }