Including settings in your app gives your users the power to control some of the functionality of your app. This will provide a better user experience for your users.
Build an app's settings using various subclasses of the Preference
class rather than using View
objects.
Each Preference
appears as an item in a list. Direct subclasses provide containers for layouts involving multiple settings. For example:
PreferenceGroup
: Represents a group of settings (Preference objects).PreferenceCategory
: Provides a disabled title above a group as a section divider.PreferenceScreen
: Represents a top-level Preference that is the root of a Preference hierarchy. Use a PreferenceScreen in a layout at the top of each screen of settings.Other Preference
subclasses for settings provide the appropriate UI for users to change the setting. For example:
CheckBoxPreference
: Creates a list item that shows a checkbox for a setting that is either enabled or disabled. The saved value is a boolean (true if it's checked).ListPreference
: Creates an item that opens a dialog with a list of radio buttons.SwitchPreference
: Creates a two-state toggleable option (such as on/off or true/false).EditTextPreference
: Creates an item that opens a dialog with an EditText widget. The saved value is a String.RingtonePreference
: Lets the user to choose a ringtone from those available on the device.To be able to follow this tutorial, you'll need:
To support API Level 11 (Honeycomb) and above, we can use the PreferenceFragment
. This class is simply a Fragment that shows a hierarchy of Preference
objects as lists.
class SettingsActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) if (fragmentManager.findFragmentById(android.R.id.content) == null) { fragmentManager.beginTransaction() .add(android.R.id.content, SettingsFragment()).commit() } } class SettingsFragment : PreferenceFragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) addPreferencesFromResource(R.xml.preferences) } } }
In the code above, we created a nested class SettingsFragment
inside SettingsActivity
. Note that our class SettingsFragment
extends the PreferenceFragment
superclass and has a method addPrerenceFromResource
inside onCreate()
. In this method, we gave it a resource ID R.xml.preference
of the preference XML to launch - when the Fragment
is loaded. Finally, we host the Fragment
to the activity by simply using the FragmentTransaction
to add it to the UI-inside onCreate()
of SettingsActivity
.
Create preferences.xml file in res/xml directory. Note that you can give this file any name, but it's highly recommended to stick with the conventional name "preferences". Also, you typically should have only one such file in an app project.
<?xml version="1.0" encoding="utf-8"?> <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> <CheckBoxPreference android:key="checkbox" android:summary="Tap to check if on or off" android:title="Checkbox Preference" /> <RingtonePreference android:key="ringtone" android:showDefault="true" android:showSilent="true" android:summary="Pick a ringtone you like" android:title="Ringtone Preference" /> <EditTextPreference android:dialogTitle="Enter a text" android:key="text" android:summary="Click to show a text entry dialog" android:title="EditText Preference" /> <ListPreference android:dialogTitle="Select duration" android:entries="@array/settings_list_preference_titles" android:entryValues="@array/settings_list_preference_values" android:key="list" android:summary="Click to show a list to choose from" android:title="List Preference" /> <SwitchPreference android:key="switch" android:title="Switch Preference" android:summary="Click to switch on or off" android:defaultValue="true"/> </PreferenceScreen>
The root node for our preferences.xml file should be a PreferenceScreen
element. Inside this root element, we now have our individual Preference
. Here are the common attributes associated with a Preference
:
android:key
: this attribute is used to get the value in the SharedPreferences
object.android:title
: sets the title for the Preference
. This is the bold text.android:summary
: sets the summary for the Preference
(this is not required). This is the faded text below the title.android:defaultValue
: sets the default value for the Preference
.Let's now see how to update the preference summary with the value selected by the user.
class SettingsActivity : AppCompatActivity() { companion object { private val sBindPreferenceSummaryToValueListener = Preference.OnPreferenceChangeListener { preference, value -> val stringValue = value.toString() if (preference is ListPreference) { // For list preferences, look up the correct display value in // the preference's 'entries' list. val listPreference = preference val index = listPreference.findIndexOfValue(stringValue) // Set the summary to reflect the new value. preference.setSummary( if (index >= 0) listPreference.entries[index] else null) } else if (preference is RingtonePreference) { // For ringtone preferences, look up the correct display value // using RingtoneManager. if (TextUtils.isEmpty(stringValue)) { // Empty values correspond to 'silent' (no ringtone). preference.setSummary("Silent") } else { val ringtone = RingtoneManager.getRingtone( preference.getContext(), Uri.parse(stringValue)) if (ringtone == null) { // Clear the summary if there was a lookup error. preference.setSummary(null) } else { // Set the summary to reflect the new ringtone display name. val name = ringtone.getTitle(preference.getContext()) preference.setSummary(name) } } } else { // For all other preferences, set the summary to the value's // simple string representation. preference.summary = stringValue } true } private fun bindPreferenceSummaryToValue(preference: Preference) { // Set the listener to watch for value changes. preference.onPreferenceChangeListener = sBindPreferenceSummaryToValueListener // Trigger the listener immediately with the preference's // current value. sBindPreferenceSummaryToValueListener.onPreferenceChange(preference, PreferenceManager .getDefaultSharedPreferences(preference.context) .getString(preference.key, "")) } } class SettingsFragment : PreferenceFragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) addPreferencesFromResource(R.xml.preferences) bindPreferenceSummaryToValue(findPreference("ringtone")) bindPreferenceSummaryToValue(findPreference("text")) bindPreferenceSummaryToValue(findPreference("list")) } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) if (fragmentManager.findFragmentById(android.R.id.content) == null) { fragmentManager.beginTransaction() .add(android.R.id.content, SettingsFragment()).commit() } } }
In this class, we created a helper method called bindPreferenceSummaryToValue()
, which is inside our companion object, to update the preference summary text with the value the user has selected. We passed it a Preference
object as an argument. The findPreference()
will return a Preference
using the Preference
's key.
We have a variable sBindPreferenceSummaryToValueListener
that is an instance of Preference.OnPreferenceChangeListener
. This is simply a preference change listener that will help us update the preference summary to the value the user has selected. We check for special cases, like when the selected preference is a RingtonePreference
or a ListPreference
. For those preference types, we do some special handling to get the summary string. If the preference is neither (like an EditTextPreference
), we just set the summary to the preference's string value.
Inside the bindPreferenceSummaryToValue()
, we set the preference change listener by calling onPreferenceChangeListener
(in Java, it's setOnPreferenceChangeListener
instead) on the Preference
object.
There are several reasons why you might want to set up a listener for a specific setting:
To listen to a setting, use the Preference.OnPreferenceChangeListener
interface, which includes the onPreferenceChange()
method that returns the new value of the setting.
Result
Retrieving Preference Values
To start getting preference values for the settings screen, we call getDefaultSharedPreference()
which is inside the PreferenceManager
class - passing it a Context
object of the preferences whose values are wanted. Note that we get the values from the default SharedPreferences
for our application.
val prefs = PreferenceManager.getDefaultSharedPreferences(this) prefs.getBoolean("checkbox", false).toString() prefs.getString("ringtone", "<unset>") prefs.getString("text", "<unset>") prefs.getString("list", "<unset>")
You call the corresponding getter method for the type we want to retrieve the value from in SharedPreferences
. You pass it the key as the first argument, and the default value is the second argument.
Now, let's go through each of the Preference
entities we have.
Checkbox Preference
A CheckBoxPreference
is simply a CheckBox
widget that's included in the preference screen. This Preference
returns the value "true" if checked or "false" otherwise. In other words, it returns a boolean depending on the state of the widget.
<CheckBoxPreference android:key="checkbox" android:summary="Tap to check if on or off" android:title="Checkbox Preference" />
Other attributes you can add to a CheckBoxPreference
are:
android:summaryOff
: sets the summary for the Preference
in a preference screen when it's unchecked.android:summaryOn
: sets the summary for the Preference
in a preference screen when it's checked.android:disableDependentsState
: The state (true for on, or false for off) that causes dependents to be disabled. May be a boolean value, such as "true" or "false".Switch Preference
SwitchPreference
performs similar functionality to the CheckBoxPreference
. It provides a two-state ("on" or "off") toggleable option. This uses a Switch
widget that allows the user to slide left ("off") and right ("on"). This Preference
also includes the attributes described for the CheckBoxPreference
above. In addition, it has the following attributes:
android:switchTextOff
: sets the text used on the switch itself when in the "off" state.android:switchTextOn
: sets the text used on the switch itself when in the "on" state.<SwitchPreference android:key="switch" android:title="Switch Preference" android:summary="Click to switch on or off" android:defaultValue="true"/>
EditText Preference
This Preference
, when clicked, shows a dialog for the user to enter an input text. It uses the EditText widget - including all the attributes of that widget that you're already familiar with.
<EditTextPreference android:dialogTitle="Enter a text" android:key="text" android:summary="Click to show a text entry dialog" android:title="EditText Preference" android:inputType="text"/>
Note that the value stored in the SharedPreferences
is a string.
List Preference
This kind of Preference
will display a list of entries in a dialog when tapped. Here, you can specify a pair of string-array resources in your preference XML. This string-array resource simply contains a collection of strings. This resource is located at res/values/arrays.xml.
<?xml version="1.0" encoding="utf-8"?> <resources> <string-array name="settings_list_preference_titles"> <item>15 minutes</item> <item>30 minutes</item> <item>1 hour</item> <item>3 hours</item> <item>6 hours</item> <item>Never</item> </string-array> <string-array name="settings_list_preference_values"> <item>15</item> <item>30</item> <item>60</item> <item>180</item> <item>360</item> <item>-1</item> </string-array> </resources>
Here is our sample ListPreference
using this resource.
<ListPreference android:dialogTitle="Select duration" android:entries="@array/settings_list_preference_titles" android:entryValues="@array/settings_list_preference_values" android:key="list" android:summary="Click to show a list to choose from" android:title="List Preference" />
We set the entries and entry values using the android:entries
and android:entryValues
attributes respectively.
android:entries
: the human-readable array to present as a list.android:entryValues
: the array to find the value to save for a preference when an entry from entries is selected.MultiSelect List Preference
This one is similar to ListPreference
but instead of having radio buttons, we have check boxes. In other words, the user can select multiple items in the dialog. Note that the result is stored in a "string set" in the SharedPreferences
. This can be retrieved using getStringSet()
.
<MultiSelectListPreference android:dialogTitle="Dialog title" android:entries="@array/settings_multi_select_list_preference_titles" android:entryValues="@array/settings_multi_select_list_preference_values" android:key="multi_select_list" android:title="Multiselect title" />
Creating Setting Groups
It becomes a problem when you have a long list of preferences or settings, because users may have some trouble scanning or understanding them. To solve this problem, we can group our preferences.
<?xml version="1.0" encoding="utf-8"?> <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> <PreferenceCategory android:title="Basic Settings"> <CheckBoxPreference android:key="checkbox" android:summary="Tap to check if on or off" android:title="Checkbox Preference"/> ... </PreferenceCategory> <PreferenceCategory android:title="Advanced Settings"> <ListPreference android:dialogTitle="Select duration" android:entries="@array/settings_list_preference_titles" android:entryValues="@array/settings_list_preference_values" android:key="list" android:summary="Click to show a list to choose from" android:title="List Preference"/> ... </PreferenceCategory> </PreferenceScreen>
We simply surround the preferences we want to group in a PreferenceCategory
tag and give each group a title using the android:title
attribute.
Starting an Intent
Note that's it's possible to open an activity by just clicking a preference item from the settings screen. This can come in handy when you want to open a web page. Here's the code to do that:
<Preference android:summary="Click to start an activity" android:title="Preference"> <intent android:action="me.proft.preferencedemo.SETTING_START_ACTIVITY" android:targetClass="me.proft.preferencedemo.MyActivity" android:targetPackage="me.proft.preferencedemo" /> </Preference>
Here, we added a <intent> element inside the Preference
element.
android:action
: sets the action for the Intent
(this is similar to calling setAction()
on an Intent
object).android:targetClass
: sets the class part of the component name (same as calling setComponent()
on an Intent
object).android:targetPackage
: sets the package part of the component name.android:data
: sets the data to assign (same as calling setData()
on an Intent
object).To open a web page, for example, you could use the following:
<Preference android:title="@string/prefs_web_page" > <intent android:action="android.intent.action.VIEW" android:data="http://www.example.com" /> </Preference>