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
.