Android Settings Preference Using PreferenceFragmentCompat

April 5, 2018

PreferenceFragmentCompat vs PreferenceFragment

PreferenceFragmentCompat v7 is supported on API Level 7 while PreferenceFragment require API level 11.

PreferenceFragmentCompat have additional features like setDivider

Based on PreferenceFragment documentation, PreferenceFragment class was deprecated in API level P. Use PreferenceFragmentCompat.

If you are interested to implement PreferenceFragment, refer to Create Settings Preference Activity In Android Using PreferenceFragment.

Implementation

Import library

Include the com.android.support:preference in modules’s build.gradle.

// implementation 'com.android.support:preference-v7:26.1.0'
// compile 'com.android.support:preference-v14:26.1.0'
// https://developer.android.com/topic/libraries/support-library/revisions.html
implementation 'com.android.support:preference-v14:26.1.0'

NOTE: There are 2 version of Preference Support Library: v7 and v14

NOTE: You can check for Recent Support Library Revisions

PreferenceFragmentCompat sample code

NOTE: make sure you use all android.support.v7.preference.* classes and don’t mix with android.preference.*.

NOTE: PreferenceFragment.onCreate(savedInstanceState: Bundle?) -> PreferenceFragmentCompat.onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?)

import android.support.v7.app.AppCompatActivity

import android.support.v7.preference.EditTextPreference
import android.support.v7.preference.Preference
import android.support.v7.preference.PreferenceFragmentCompat
import android.support.v7.preference.SwitchPreferenceCompat

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
        supportFragmentManager.beginTransaction()
                // .replace(android.R.id.content, SettingsFragment())
                .replace(R.id.content, SettingsFragment())
                .commit()
    }

    class SettingsFragment: PreferenceFragmentCompat() {

        override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
            // 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.

NOTE: there is no need to use android.support.v7.preference.Preference in xml.

NOTE: android.support.v7.preference.Preference is required if you plan to override the class to create a custom Preference.

<?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>

Edit preferenceTheme

Edit res/values/styles.xml to include preferenceTheme.

<style name="AppTheme.NoActionBar">
    <item name="preferenceTheme">@style/PreferenceThemeOverlay.v14.Material</item>
    <!-- <item name="preferenceTheme">@style/PreferenceThemeOverlay</item> -->
    <item name="windowActionBar">false</item>
    <item name="windowNoTitle">true</item>
</style>

NOTE: you will bump into java.lang.IllegalStateException: Must specify preferenceTheme in theme exception if preferenceTheme is not set

Make sure the activity apply this theme is AndroidManifest.xml.

<?xml version="1.0" encoding="utf-8"?>
<manifest ...> 
    <application ...>
        ...
        <activity
            android:name=".view.SettingsActivity"
            android:label="@string/title_activity_settings"
            android:parentActivityName=".MainActivity"
            android:theme="@style/AppTheme.NoActionBar"
            >
        </activity>
    </application>
</manifest>

Start activity on preference click

class SettingsFragment: PreferenceFragmentCompat() {

    ...

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

Disable SwitchPreference change state but still listen to click

class SettingsFragment: PreferenceFragmentCompat() {

    ...

    override fun onPreferenceTreeClick(preference: Preference): Boolean {
        return when (preference.key) {
            getString(R.string.pref_key_auto_lock) -> {
                (preference as SwitchPreferenceCompat).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(preference)
            }
        }
    }
}

Hide divider of PreferenceFragmentCompat

class SettingsFragment: PreferenceFragmentCompat() {
    ...

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

        setDivider(null)
    }
}

NOTE: the above code can be put in onViewCreated as well

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