Settings screens are an essential component of most Android applications. They allow users to customize the app according to their preferences. The PreferenceFragmentCompat
class, part of the AndroidX Preference library, provides a robust and straightforward way to build settings screens using XML for layout definition and Kotlin for logic implementation.
What is PreferenceFragmentCompat
?
PreferenceFragmentCompat
is a class from the AndroidX Preference library designed to display preferences as a hierarchy of Preference
objects. These preferences can be stored using SharedPreferences
and are automatically updated when the user changes settings.
Why Use PreferenceFragmentCompat
?
- Simple and Structured: Organizes settings into a structured, user-friendly interface.
- Automatic Storage: Utilizes
SharedPreferences
to automatically store and retrieve user settings. - Customizable: Provides various types of preferences (checkboxes, lists, switches, etc.) that can be customized via XML.
- Compatibility: Part of the AndroidX library, ensuring backward compatibility and consistent behavior across different Android versions.
Step-by-Step Implementation
Let’s walk through building a settings screen using PreferenceFragmentCompat
with XML layouts and Kotlin code.
Step 1: Add Dependencies
First, ensure that you have the necessary dependencies in your build.gradle
file:
dependencies {
implementation("androidx.preference:preference-ktx:1.2.1")
}
Step 2: Create a Preference XML File
Create an XML file that defines the structure and content of your settings. For example, preferences.xml
inside the res/xml
directory:
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<PreferenceCategory app:title="User Profile">
<EditTextPreference
app:key="username"
app:title="Username"
app:summary="Enter your username"
app:defaultValue="Guest"
app:useSimpleSummaryProvider="true"/>
<EditTextPreference
app:key="email"
app:title="Email"
app:summary="Enter your email address"
app:defaultValue="guest@example.com"
app:useSimpleSummaryProvider="true"
app:inputType="textEmailAddress"/>
</PreferenceCategory>
<PreferenceCategory app:title="Notifications">
<SwitchPreferenceCompat
app:key="enable_notifications"
app:title="Enable Notifications"
app:summaryOn="Notifications are enabled"
app:summaryOff="Notifications are disabled"
app:defaultValue="true"/>
</PreferenceCategory>
<PreferenceCategory app:title="Advanced Settings">
<ListPreference
app:key="theme"
app:title="Theme"
app:summary="Select your preferred theme"
app:entries="@array/theme_entries"
app:entryValues="@array/theme_values"
app:defaultValue="light"
app:useSimpleSummaryProvider="true"/>
</PreferenceCategory>
</PreferenceScreen>
Create the arrays theme_entries
and theme_values
in res/values/arrays.xml
:
<resources>
<string-array name="theme_entries">
<item>Light</item>
<item>Dark</item>
<item>System Default</item>
</string-array>
<string-array name="theme_values">
<item>light</item>
<item>dark</item>
<item>system</item>
</string-array>
</resources>
Step 3: Create a Settings Fragment
Create a Kotlin class that extends PreferenceFragmentCompat
and inflates the preference XML:
import android.os.Bundle
import androidx.preference.PreferenceFragmentCompat
class SettingsFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.preferences, rootKey)
}
}
Step 4: Integrate the Settings Fragment into an Activity
Add the settings fragment to your activity. For instance, you can create a SettingsActivity
:
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.appcompat.widget.Toolbar
import androidx.fragment.app.commit
class SettingsActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_settings)
setSupportActionBar(findViewById(R.id.toolbar))
supportActionBar?.setDisplayHomeAsUpEnabled(true)
if (savedInstanceState == null) {
supportFragmentManager.commit {
replace(R.id.settings_container, SettingsFragment())
}
}
}
}
Create a layout file (e.g., activity_settings.xml
) for the activity that includes a FrameLayout
to host the settings fragment and a toolbar:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/>
<FrameLayout
android:id="@+id/settings_container"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
Step 5: Add Theme Attribute to Activity in AndroidManifest.xml
Add android:theme="@style/Theme.AppCompat.Light"
to the activity
tag inside your AndroidManifest.xml
:
<activity
android:name=".SettingsActivity"
android:exported="false"
android:label="Settings"
android:theme="@style/Theme.AppCompat.Light">
<meta-data
android:name="android.app.lib_name"
android:value="" />
</activity>
Step 6: Accessing Preference Values
You can access the stored preference values using SharedPreferences
:
import android.content.Context
import androidx.preference.PreferenceManager
fun getUsername(context: Context): String {
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
return sharedPreferences.getString("username", "Guest") ?: "Guest"
}
Handling Preference Changes
To react to preference changes, implement the SharedPreferences.OnSharedPreferenceChangeListener
in your activity or application context. For example:
import android.content.SharedPreferences
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import androidx.preference.PreferenceManager
class MainActivity : AppCompatActivity(), SharedPreferences.OnSharedPreferenceChangeListener {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
PreferenceManager.getDefaultSharedPreferences(this)
.registerOnSharedPreferenceChangeListener(this)
}
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
key?.let {
when (it) {
"enable_notifications" -> {
val notificationsEnabled = sharedPreferences?.getBoolean(it, true) ?: true
Log.d("Settings", "Notifications enabled: $notificationsEnabled")
}
"theme" -> {
val theme = sharedPreferences?.getString(it, "light") ?: "light"
Log.d("Settings", "Theme changed to: $theme")
// Apply theme here
}
else -> {
Log.d("Settings", "Preference changed: $it")
}
}
}
}
override fun onDestroy() {
super.onDestroy()
PreferenceManager.getDefaultSharedPreferences(this)
.unregisterOnSharedPreferenceChangeListener(this)
}
}
Customizing Preference Appearance
You can customize the appearance of your preferences by defining custom layouts for individual preference types. For example:
<androidx.preference.Preference
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout="@layout/custom_preference_layout"
app:iconSpaceReserved="false"/>
Conclusion
Using PreferenceFragmentCompat
in Kotlin with XML-based layouts is a straightforward and effective way to create customizable settings screens in Android applications. By following the steps outlined above, you can quickly build, integrate, and manage application settings while providing users with a seamless and personalized experience. This approach ensures a clean, structured, and maintainable codebase, making it an excellent choice for modern Android development.