Settings screens provide a way for our users to configure our application for the way in which they want it to look, feel and behave. As well as this, it’s also a great place for us to provide links to external information such as privacy policies, open-source licences and more. Whilst we can build these screens ourselves, there can often be a lot of boilerplate for what seems like such a simple requirement. Not only do we need to create the user interface, we also need to create the logic used to read and write the values from our application preferences file.
Luckily, Android Jetpack houses what is known as the Preference Library. This provides us with a way to create our application preferences with minimal work from the developers side. Using this Preferences functionality from Android jetpack can not only help you to save development time, but it means that our settings screen will have a consistent material look and feel that ties in with the rest of our application.
Let’s take a dive into Preferences so that we can see what is available and how we can implement it into our applications.
Note: The current release of the Preferences library is androidx.preference:preference:1.1.0-beta01.
Setting up a Preferences screen
In order to build a preferences screen, we have what is known as the Preference hierarchy. This hierarchy is used to defined the different preferences properties that can be can be set for our application. There are two ways which we can define this hierarchy, programmatically or within XML. The way in which we load our settings into the corresponding component (say, a fragment) will depend on how we initially define our hierarchy.
Let’s start by building this settings hierarchy. If we are going to create our preferences via an XML resource then we need to begin by defining a new PreferenceScreen resource within the res/xml directory of our app. Within this we can then define the different preferences that we want to display on this screen.
<androidx.preference.PreferenceScreen
xmlns:app="http://schemas.android.com/apk/res-auto">
</androidx.preference.PreferenceScreen>
Within the body of this, we need to define the different settings that we wish to display within the preferences screen. If we’re creating preferences programatically then then go directly into the component (fragment) that we are going to be using for our screen.
Now, there are many different types of settings that can be used, but we’ll start with the simplest of them all which is a simple text item.
<Preference
android:key="preference_key"
android:title="@string/preference_title"
android:summary="@string/preference_summary" />
You’ll notice here that we define three attributes for our Preference item:
- key: This key is used to reference the preference value that is saved. This key will be used for the saving and retrieval of our preferences value
- title: The title used for the display of the preference
- summary: The description used to display on the preference
And when displayed on screen, we get something that simply looks like this:
If we are creating the Preference programatically, then we achieve this by instantiating a new instance of the Preference class:
val simplePreference = Preference(context).apply {
key = "simple_preference"
title = "Some title"
summary = "Some summary"
}
Here we can see our preference title displayed in the form of “Basic preference”, along with the corresponding summary, “This is a summary”, displayed underneath it. We can expand on the above by making use of the icon attribute on our Preference item, this allows us to use a drawable reference to display an icon at the start of our preference item:
<Preference
android:key="preference_key"
android:title="@string/preference_title"
android:summary="@string/preference_summary"
android:icon="@drawable/ic_menu_camera" />
If we are creating the preference programatically, then we achieve this by setting the icon attribute with our Preference class instance:
val iconPreference = Preference(context).apply {
icon = ContextCompat.getDrawable(context,
R.drawable.ic_menu_camera)
}
In some cases, our preference title might expand across more than a single line. This could be in the case where our user is on a smaller device that expected, their text size has been enlarged via the system properties or even our preference title is just too long!
When this is the case, we may wish to fix our setting title to a single line – this can be done by using the singleLineTitle attribute:
<Preference android:key="single_line_title" android:title="@string/title_single_line_title_preference" android:summary="@string/summary_single_line_title_preference" app:singleLineTitle="true" />
If we are creating this preference programatically, then we can again achieve this assigning this property for the instance of our Preference class:
val singleLinePreference = Preference(context).apply { ... isSingleLineTitle = true }
Widget preference items
As well as simple text preference items, the Preferences API provides us with a collection of widget based preferences that can be used to configure the different settings items found within our apps. These are often fundamental to settings screens, allowing your users to toggle and select the different settings options provided by your application.
Checkbox Preference
In some cases we’ll want to display a preference that allows our user to check and uncheck a preference item, for this we’ll make use of the CheckBoxPreference item:
<CheckBoxPreference android:key="checkbox" android:title="@string/title_checkbox_preference" android:summary="@string/summary_checkbox_preference"/>
As you can see from above, there are no extra attributes when compared with our original Preference item, the difference here is that the preference displays a checkbox at the start of the layout. The value fetched for this key will then be returned to us as an enabled or disabled state.
If we are creating the checkbox programatically, then we achieve this by instantiating a new instance of the CheckBoxPreference class:
val checkBoxPreference = CheckBoxPreference(context).apply { key = "checkbox" title = "Checkbox" summary = "This one has a checkbox" }
Switch Preference
Similar to the CheckBox we have the SwitchPreferenceCompat item. Again, no extra attributes are required to configure the display of a switch setting item. And similar to the CheckBox item, the value fetched for this key will then be returned to us as an enabled or disabled state.
<SwitchPreferenceCompat android:key="switch" android:title="@string/title_switch_preference" android:summary="@string/summary_switch_preference"/>
If we are creating the switch programatically, then we achieve this by instantiating a new instance of the SwitchPreferenceCompat class:
val switchPreference = SwitchPreferenceCompat(context).apply { key = "notifications" title = "Switch" summary = "This has a switch" }
Dropdown Preference
We also have access to what is known as a DropDownPreference – this preference widget allows us to display a dropdown of selectable items for user selection.
<DropDownPreference android:key="dropdown" android:title="@string/title_dropdown_preference" android:entries="@array/entries" app:useSimpleSummaryProvider="true" android:entryValues="@array/entry_values"/>
You can see from the above how we require a few extra attributes when it comes to this preference widget:
- entries: These are the items that you wish to display
- useSimpleSummaryProvider – Used to determine whether the summary of the preference should show the currently save item
- entryValues: These are the values which are to be used for the selectable entries.
In some cases it is likely that you want to provide the same set of values for both the entries and the entryValues. However, having these seperate attributes offer you the flexibility for when you need your value set to be different from the values displayed within the drop down itself. For example, the displayed entry of “Entry 1” may actually have the value of “entry_1’. This would not be visible to the user, but is more for use as an internal value.
If we are creating our Preferences programatically, then we can do so by instantiating a new instance of the DropDownPreference class:
val dropDownPreference = DropDownPreference(context).apply { key = "drop_down" title = "Some title" entries = arrayOf("One", "Two", "Three") entryValues = arrayOf("1", "2", "3") }
Seekbar Preference
The next widget preference we have is the SeekBarPreference, this allows our user to configure a setting which takes on a seekable format, such as volume.
<SeekBarPreference android:key="seekbar" android:title="Seek bar!" android:max="10" android:defaultValue="5" />
For the seekbar we have a couple of other attributes that we can set other than the usual key and title.
- max: This represents the maximum value that can be selected using the seekbar
- defaultValue: This represents the default value to be displayed on the seekbar when it is not / before it is set
val seekBarPreference = SeekBarPreference(context).apply { key = "seekbar" title = "Some title" max = 10 setDefaultValue(5) }
Dialog preference items
The preference API provides us with settings items that display a dialog for the user to input some data for the preference item. There are three type of DialogPreference classes that we have available for use – this is the EditTextPreference, ListPreference and MultiSelectListPreference classes:
Each DialogPreference class comes with a collection of attributes which can be used to customise the content which is to be displayed for the given dialog.
- dialogTitle – The title to be displayed for the dialog
- dialogMessage – The message to be used within the body of the dialog
- dialogIcon – The icon to be displayed within the dialog
- dialogLayoutResource – A custom layout to be used for the dialog
- positiveButtonText – The string resource to be used for the positive button text
- negativeButtonText – The string resource to be used for the negative button text
EditText Preference
The first one of these is the EditTextPreference item – this allows our user to input some text to be saved as the value for the preference item.
<EditTextPreference android:key="edittext" android:title="Some input" app:useSimpleSummaryProvider="true" android:dialogTitle="This is the title"/>
Other than the common attributes for a dialog preference item, there is a single other attribute available for customising the preference item:
- useSimpleSummaryProvider – Used to determine whether the summary of the preference should show the currently save item
When this preference item is selected, a dialog will be shown to our user to input the value which is to be saved as the preference value. Whilst all of the DialogPreference attributes do not need to be set, provided values for these allows you to customise the look and feel of the dialog.
If we are creating our preference items programatically, then we can create an EditTextPreference item like so:
val editTextPreference = EditTextPreference(context).apply { key = "edit_text" title = "Some title" }
Once we have our EditTextPreference defined in our class we can also set a binding listener on it, using the setOnBindEditTextListener method. This allows us to listen for when the dialog for the edit text preference is displayed to the user (from clicking the preference item).
editTextPreference.setOnBindEditTextListener {
...
}
List Preference
Next we have the ListPreference item – this displays a predefined list of items to our user which allows them to select a single item to be assigned as the preference value.
<ListPreference android:key="list" android:title="Some list" app:useSimpleSummaryProvider="true" android:entries="@array/list_preference_entries" android:entryValues="@array/list_preference_entry_values" />
For the list preference item there are a couple of attributes which can be used to configure the list:
- entries – These are the items that you wish to display
- useSimpleSummaryProvider – Used to determine whether the summary of the preference should show the currently saved item
- entryValues – These are the values which are to be used for the selectable entries.
If we are creating our preference items programatically, then we can create an ListPreference item like so:
val listPreference = ListPreference(context).apply { key = "drop_down" title = "Some title" entries = arrayOf("One", "Two", "Three") entryValues = arrayOf("1", "2", "3") }
Finally, the MultiSelectListPreference item works in a similar way to the ListPreference item. The difference is that with this, multiple list values can be selected by the user
<MultiSelectListPreference android:key="multi_select_list" android:title="Some multi select list" android:summary="This is a summary" android:entries="@array/list_preference_entries" android:entryValues="@array/list_preference_entry_values"/>
- dialogTitle – The title to be displayed on the dialog
- entries: These are the items that you wish to display
- summary: The text summary to be displayed with the setting
- entryValues: These are the values which are to be used for the selectable entries.
You’ll notice here that the MultiSelectListPreference does not have useSimpleSummaryProvider option. Instead, you must make use of the summary attribute to provide a summary to be displayed with the setting.
The MultiSelectListPreference can also be created programmatically like so:
val multiSelectListPreference = MultiSelectListPreference(context).apply { key = "multi_select_list" title = "Some multi select list" summary = "This is a summary" entries = arrayOf("One", "Two", "Three") entryValues = arrayOf("1", "2", "3") }
Category Preferences
It’s more than likely we’re going to have groups of preferences – as in, preferences items that can be categorised into different groups. In these cases it can be helpful to actually display these in their individual groups to break things up for our users – thankfully, the Preference API provides us with what is known as a PreferenceCategory which allows us to achieve this.
<PreferenceCategory android:title="Dialogs"> </PreferenceCategory>
For the PreferenceCategory we only need to define a title, which will be displayed a the top of our categorised settings. Once we have defined this PreferenceCategory within our preferences XML file we just need to play the corresponding Preference items within it.
The PreferenceCategory can also be created programmatically like so:
val notificationCategory = PreferenceCategory(context).apply { key = "notifications_category" title = "Notifications" }
Expandable Preferences
In some situations there may be a collection of preference items displayed for some part of the settings in our app, and in these cases we may want to allow our user to show and hide this collection. For this, we can make use of the PreferenceCategory item, defining the initialExpandedChildrenCount attribute. When this attribute is defined the category will collapse the remaining items after the initial count that we provided. If there are a lot of settings within a category then this can be used to prevent the user from being overwhelmed with the number of settings that are on display
<PreferenceCategory android:key="advanced" android:title="Advanced stuff" app:initialExpandedChildrenCount="1"> </PreferenceCategory>
Because we made use of the initialExpandedChildrenCount attribute when declaring our PreferenceCategory, only the first item is displayed in its expanded state. The remaining items fall into the collapsed state, meaning that the last item must be selected to display these:
At this point, all of our preference items become expanded and visible to the user. This preference configuration is useful where you may have a collection of preferences that are not primary settings for your application – in this case it may make sense to collapse them to avoid bloating the screen and overwhelming the user.
If we are creating the PreferenceCategory programmatically then we can still set the initial expanded count attribute:
val notificationCategory = PreferenceCategory(context).apply { key = "notifications_category" title = "Notifications" initialExpandedChildrenCount = 1 addPreference(...) addPreference(...) addPreference(...) }
Displaying our preferences
In order to display our preferences on the screen to our user, we’re going to need to create a new instance of the PreferenceFragmentCompat class. If we’re going to create our preferences from an xml file then this is going to be a pretty barebones class.
class SettingsFragment : PreferenceFragmentCompat() { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { setPreferencesFromResource(R.xml.preferences, rootKey) } }
Within this class you’ll notice we override an onCreatePreferences method – this is what is used to configure our preferences screen. Within this, we make use of the setPreferencesFromResource method to assign an xml file which will be used to configure our preferences. Here, our defined xml file will be taken and its contents will be used to build the preferences which are displayed on-screen to the user.
In this fragment, you may also wish to configure your settings programmatically instead of via an xml file. In the previous sections we outlined how you can create settings items programmatically, so let’s take a quick look at how we can apply these within our settings fragment. We begin by making use of the PreferenceManager reference to create an instance of a Preference Screen. When creating via xml all of this configuration is handled for us, in the case of programmatic preference configuration we are required to set these things up ourselves.
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { val context = preferenceManager.context val screen = preferenceManager.createPreferenceScreen(context) val notificationPreference = SwitchPreferenceCompat(context) val feedbackPreference = Preference(context) screen.addPreference(notificationPreference) screen.addPreference(feedbackPreference) preferenceScreen = screen }
Here we do this using the createPreferenceScreen() method on our Preference Manager instance – this returns us an instance of the Preference Screen that we will use to display our settings. Before we can show this to our users though, we need to add our settings items to it. The screen has a method, addPreference(), which allows us to add each of our settings items to the screen. Here we need to call this method for any setting item which we wish to add – so for example we created this in a previous section:
val notificationCategory = PreferenceCategory(context).apply { key = "notifications_category" title = "Notifications" initialExpandedChildrenCount = 1 }
Here, we’ll call addPreference, passing in our notificationCategory instance to add it to our preference screen:
screen.addPreference(notificationPreference)
Now that we would have done that for each of our preference items, we can finish off by setting the preferenceScreen property of our Settings Fragment to instance of the Preference Screen that we previously created
preferenceScreen = screen
At this point, we’ve either added all of our settings items programmatically, or have configured our preferences screen using an xml resource file. So now that we have our settings fragment, we need to actually display it to the user. For this, we’re going to create an activity to house our fragment and set our fragment container to display our settings fragment instance.
class SettingsActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_settings) supportFragmentManager .beginTransaction() .replace(R.id.settings_container, SettingsFragment()) .commit() } }
Reading preference values
When it comes to reading preferences values, we can make use of the PreferenceManager class to retrieve the default shared preferences for our application. From here we can access the preferences value by making use of the key that we assigned it during the definition of our settings.
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity_context) val name = sharedPreferences.getString("signature", "")
Listening for preference change events
We can use the OnPreferenceChangeListener to receive a callback when a preference value is being set by the user and is about to be persisted. Within this callback we are required to return a boolean value that states whether or not the preference value should be saved.
override fun onPreferenceChange(preference: Preference, newValue: Any): Boolean { return true }
Before we can listen for these events though we need to set the listener on the desired Preference item:
preference.onPreferenceChangeListener = this or preference.onPreferenceChangeListener = object : OnPreferenceChangeListener { ... }
We can set a collection of other listeners using the Preference library, read more about them over in the documentation.
In this article we’ve learnt about the different components contained within the Preferences API, along with how we can make use of them when creating Preference screens of our own. Whilst still in alpha, the Preference API aims to make the development process far more streamlined when it comes to settings screens within our applications. Once the Preference API is in a stable position, using it in our applications for these kind of screens seems like the obvious decision to make.
Have you already played with the Preference API, or is it something that you think your application and team will develop from? Please feel free to respond with any thoughts and/or questions 🙂