Updated 8 days ago | GitHub

Settings with PreferenceFragment

Overview

In Android apps, there are often settings pages that contain different options the user can tweak. The AndroidX Preference library provides PreferenceFragmentCompat, a fragment that displays a hierarchy of preference objects on screen as a list. These preferences automatically save to SharedPreferences as the user interacts with them.

The framework’s android.preference.PreferenceFragment and the old android.support.v7.preference support-library classes are historical: the framework classes were deprecated in API 28, and the support library was superseded by AndroidX. New code should use androidx.preference classes end-to-end.

Edit your Gradle dependencies

Open your app module’s Gradle file (Your-Project/app/build.gradle) and add the AndroidX Preference library (1.2.1 is the current stable release) to the dependencies:

dependencies {
    // your other dependencies...

    // Kotlin projects
    implementation "androidx.preference:preference-ktx:1.2.1"
    // or, for Java-only projects
    // implementation "androidx.preference:preference:1.2.1"
}

No theme workaround is needed: since version 1.1.0 the library falls back to the built-in PreferenceThemeOverlay style when your theme doesn’t set the preferenceTheme attribute, so the third-party “preference-v7 fix” libraries that used to patch this are obsolete.

Defining the XML

First, define the preference object hierarchy by creating a new XML file in res/xml (for example res/xml/preferences.xml):

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

    <PreferenceCategory app:title="@string/title_first_section">

        <SwitchPreferenceCompat
            app:key="switch_preference"
            app:title="@string/title_switch_preference"
            app:defaultValue="true" />

        <EditTextPreference
            app:key="edittext_preference"
            app:title="@string/title_edittext_preference"
            app:summary="@string/summary_edittext_preference"
            app:dialogTitle="@string/dialog_title_edittext_preference"
            app:dependency="switch_preference" />

    </PreferenceCategory>

    <PreferenceCategory app:title="@string/title_second_section">

        <ListPreference
            app:key="list_preference"
            app:title="@string/title_list_preference"
            app:dialogTitle="@string/dialog_title_list_preference"
            app:entries="@array/entries_list_preference"
            app:entryValues="@array/entryvalues_list_preference" />

        <Preference app:title="@string/title_intent_preference">
            <intent android:action="android.intent.action.VIEW"
                android:data="https://codepath.com/" />
        </Preference>

    </PreferenceCategory>

</PreferenceScreen>

The root for the XML file must be a <PreferenceScreen>. Within this screen, you can either list all preferences or group them with <PreferenceCategory>. The grouped preferences appear together under the same section heading. See Organize your settings for more grouping options.

All preferences are saved as key-value pairs in the default SharedPreferences with the key specified through the XML above. To retrieve an instance of those preferences, call the following with a context (note the import is androidx.preference.PreferenceManager):

val preferences = PreferenceManager.getDefaultSharedPreferences(context)

Preference Types

There are several main types of preferences used for settings, all in the androidx.preference package:

Preference types that open a dialog (ListPreference, MultiSelectListPreference, and EditTextPreference) descend from DialogPreference and can therefore define dialog-specific attributes in the XML (e.g. a dialogTitle).

Defining the ListPreference Options

Two arrays must be specified when defining a list preference. The first is under app:entries, which specifies human-readable options to display to the user. Second is an array of the values for each corresponding option, which is defined by app:entryValues.

    <ListPreference
        app:key="list_preference"
        app:title="@string/title_list_preference"
        app:dialogTitle="@string/dialog_title_list_preference"
        app:entries="@array/entries_list_preference"
        app:entryValues="@array/entryvalues_list_preference" />

Intents in Preferences

Preferences can also hold intents which can open a new activity or perform other intent actions. No data is persisted.

    <Preference app:title="@string/title_intent_preference">
        <intent android:action="android.intent.action.VIEW"
            android:data="https://codepath.com/" />
    </Preference>

Displaying the Settings Screen

Create a fragment that extends PreferenceFragmentCompat and inflate your XML hierarchy in onCreatePreferences(). To customize a preference programmatically — for example to attach an OnPreferenceChangeListener — look it up by key with findPreference() after calling setPreferencesFromResource():

class SettingsFragment : PreferenceFragmentCompat() {

    override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
        // Indicate here the XML resource you created above that holds the preferences
        setPreferencesFromResource(R.xml.preferences, rootKey)

        val listPreference = findPreference<ListPreference>("list_preference")
        listPreference?.setOnPreferenceChangeListener { preference, newValue ->
            // your code here; return true to persist newValue
            true
        }
    }
}

Then host the fragment from an activity (the common SettingsActivity pattern) with a regular fragment transaction:

class SettingsActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_settings)
        if (savedInstanceState == null) {
            supportFragmentManager
                .beginTransaction()
                .replace(R.id.settings_container, SettingsFragment())
                .commit()
        }
    }
}

where R.id.settings_container is any container view (such as a FrameLayout) in the activity’s layout. Register the activity in your manifest as usual and launch it with an Intent from wherever your app exposes its settings entry point.

Custom Preferences

If none of the previous preference types work for your needs, you can create a custom preference extending from DialogPreference, TwoStatePreference, or Preference itself.

For a detailed guide on implementing a custom preference, refer to the Customize your settings guide.

References