Create Settings Preference Activity In Android Using PreferenceFragment

April 5, 2018

PreferenceActivity or PreferenceFragment?

If you’re developing for Android 3.0 (API level 11) and higher, you should use a PreferenceFragment.

This article shall focus on PreferenceFragment only.

PreferenceFragment or PreferenceFragmentCompat?

Technically PreferenceFragment is supported on Android 3.0 (API level 11) and higher.

This class was deprecated in API level P. Use PreferenceFragmentCompat. Source: PreferenceFragment

PreferenceFragmentCompat have some extra features like methods like setDivider and setDividerHeight. There are 2 version of Preference Support Library: v7 and v14

This article shall focus on PreferenceFragment. If you are interested to implement PreferenceFragmentCompat, refer to Android Settings Preference Using PreferenceFragmentCompat

PreferenceScreen or Preference Headers?

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 the “headers” feature instead of building subscreens with nested PreferenceScreen elements. Source: Preference Headers

If you have many sub preference screens (imagine settings page for Android phone) and you want better view display for tablet, you might want to consider preference-headers. The obvious limitation is on the main preference screen (listing all sub preference options), you can’t have a Preference item (to display info or for action).

If you only have a simple settings page without the need for loading another sub preference screen, stick to PreferenceScreen. You can use PreferenceCategory to group your similar settings together.

This article shall focus on PreferenceScreen only.

Code

I am using AppCompatActivity with theme @style/AppTheme.NoActionBar (default generated by Android Studio) and CoordinatorLayout.

class SettingsActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_settings)
        setSupportActionBar(toolbar)

        // android.R.id.content is probably for old style activity
        fragmentManager.beginTransaction()
                // .replace(android.R.id.content, SettingsFragment())
                .replace(R.id.content, SettingsFragment())
                .commit()
    }

    class SettingsFragment: PreferenceFragment() {

        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)

            // Load the preferences from an XML resource
            addPreferencesFromResource(R.xml.pref_settings)
        }
    }
}

Content of R.layout.activity_settings

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".view.SettingsActivity">

    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/AppTheme.AppBarOverlay">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            app:popupTheme="@style/AppTheme.PopupOverlay" />

    </android.support.design.widget.AppBarLayout>

    <android.support.constraint.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">
        <!-- android:id="@android:id/content" -->
        <FrameLayout
            android:id="@+id/content"
            android:layout_width="match_parent"
            android:layout_height="match_parent"></FrameLayout>
    </android.support.constraint.ConstraintLayout>
</android.support.design.widget.CoordinatorLayout>

Content of R.xml.pref_settings.

<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
    xmlns:android="http://schemas.android.com/apk/res/android">
    <PreferenceCategory
        android:title="Login"
        android:key="pref_key_login_settings">

        <Preference
            android:key="@string/pref_key_change_password"
            android:title="Change Password"
            />

        <EditTextPreference
            android:key="@string/pref_key_change_password_hint"
            android:title="Change Password Hint"
            />
    </PreferenceCategory>

    <PreferenceCategory
        android:title="Security"
        android:key="pref_key_security_settings">

        <SwitchPreference
            android:key="@string/pref_key_auto_lock"
            android:title="Auto Lock"
            android:summary="Always prompt password login when the app is inactive for a while"
            />

    </PreferenceCategory>

    <PreferenceCategory
        android:title="About"
        android:key="pref_key_about_settings">

        <Preference
            android:key="@string/pref_key_upgrade"
            android:title="Upgrade"
            android:summary="Pro for peace of mind or be a Patron to support our work"
            />

        <Preference
            android:key="@string/pref_key_rate"
            android:title="❤ LuaPass"
            android:summary="Rate LuaPass on Play Store"
            />

        <Preference
            android:key="@string/pref_key_contact"
            android:title="Feedback"
            android:summary="Email us for question, suggestion or bug report"
            />

        <Preference
            android:key="@string/pref_key_version"
            android:title="Version"
            />

    </PreferenceCategory>
</PreferenceScreen>

Update preference summary programtically

class SettingsFragment: PreferenceFragment() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Load the preferences from an XML resource
        addPreferencesFromResource(R.xml.pref_settings)

        vak sharedPref = preferenceManager.sharedPreferences

        // set summary
        val versionPreference = findPreference(getString(R.string.pref_key_version))
        versionPreference.summary = BuildConfig.VERSION_NAME

        // set summary by reading value from SharedPreferences
        val passwordHintPreference = findPreference(getString(R.string.pref_key_change_password_hint)) as EditTextPreference
        passwordHintPreference.summary = passwordHintPreference.text

        // make sure summary is updated when preference change
        passwordHintPreference.setOnPreferenceChangeListener { preference, value ->
            passwordHintPreference.summary = value as String
            true
        }
    }

    ...
}

Start activity on preference click

class SettingsFragment: PreferenceFragment() {

    ...

    override fun onPreferenceTreeClick(preferenceScreen: PreferenceScreen?, preference: Preference): Boolean {
        return when (preference.key) {
            getString(R.string.pref_key_change_password) -> {
                startActivity(Intent(activity, ChangePasswordActivity::class.java))
                true
            }
            else -> {
                super.onPreferenceTreeClick(preferenceScreen, preference)
            }
        }
    }
}

Disable SwitchPreference change state but still listen to click

class SettingsFragment: PreferenceFragment() {

    ...

    override fun onPreferenceTreeClick(preferenceScreen: PreferenceScreen?, preference: Preference): Boolean {
        return when (preference.key) {
            getString(R.string.pref_key_auto_lock) -> {
                (preference as SwitchPreference).isChecked = false

                AlertDialog.Builder(activity)
                        .setTitle("Coming Soon")
                        .setMessage("This feature shall be available in coming releases.")
                        .setPositiveButton("Support Us") { dialog, whichButton ->
                            
                        }
                        .setNegativeButton(android.R.string.cancel, null)
                        .show()
                true
            }
            else -> {
                super.onPreferenceTreeClick(preferenceScreen, preference)
            }
        }
    }
}

Hide divider of PreferenceFragment

class SettingsFragment: PreferenceFragment() {
    ...

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)

        val listView = view.findViewById<ListView>(android.R.id.list)
        if (listView != null) {
            listView.divider = null
        }
    }
}

NOTE: the above code can be put in onViewCreated as well, but might not work in onCreateView for API 23

NOTE: if you are using PreferenceFragmentCompat, you can use setDivider.

This work is licensed under a
Creative Commons Attribution-NonCommercial 4.0 International License.