Working with SharedPreferences
At some point, our app needs to use the values that the user provided via the preference UI. We might also want to save other data in SharedPreferences
that is not part of the preference UI.
Reading Preferences
Call getDefaultSharedPreferences()
on android.preference.PreferenceManager
to get the SharedPreferences
that is used by the preference UI for its values.
SharedPreferences
offers a series of getters to access named preferences, returning a suitably-typed result (e.g., getBoolean()
to return a boolean preference). The getters also take a default value, which is returned if there is no preference set under the specified key.
You can read values at any point. If you want to find out when the preference UI (or other code in your app) changes the preferences, call registerOnSharedPreferenceChangeListener()
on the SharedPreferences
to register an OnSharedPreferenceChangeListener
to be notified of when values change.
The sample app has a ConstraintLayout
that shows the values of our three preferences:
<?xml version="1.0" encoding="utf-8"?>
<layout 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">
<data>
<variable
name="state"
type="androidx.lifecycle.LiveData<com.commonsware.jetpack.simpleprefs.HomeViewState>" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".HomeFragment">
<TextView
android:id="@+id/checkLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:text="@string/labelCheck"
android:textAppearance="@style/TextAppearance.AppCompat.Large"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/fieldLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:text="@string/fieldLabel"
android:textAppearance="@style/TextAppearance.AppCompat.Large"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/checkLabel" />
<TextView
android:id="@+id/listLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:text="@string/listLabel"
android:textAppearance="@style/TextAppearance.AppCompat.Large"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/fieldLabel" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/barrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="end"
app:constraint_referenced_ids="checkLabel,fieldLabel,listLabel" />
<TextView
android:id="@+id/checkValue"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:text="@{state.isChecked ? @string/checked : @string/unchecked}"
android:textAppearance="@style/TextAppearance.AppCompat.Large"
app:layout_constraintBaseline_toBaselineOf="@id/checkLabel"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/barrier"
tools:text="checked" />
<TextView
android:id="@+id/fieldValue"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:text="@{state.fieldValue}"
android:textAppearance="@style/TextAppearance.AppCompat.Large"
app:layout_constraintBaseline_toBaselineOf="@id/fieldLabel"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/barrier"
tools:text="Something" />
<TextView
android:id="@+id/listValue"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:text="@{state.listValue}"
android:textAppearance="@style/TextAppearance.AppCompat.Large"
app:layout_constraintBaseline_toBaselineOf="@id/listLabel"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/barrier"
tools:text="ABE" />
<Button
android:id="@+id/edit"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:text="@string/editPrefs"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/listValue" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
We are using data binding to populate three of the TextView
widgets with certain values, pulled from a HomeViewState
that we get via LiveData
. That comes from HomeMotor
implementation of AndroidViewModel
:
package com.commonsware.jetpack.simpleprefs;
import android.app.Application;
import android.content.SharedPreferences;
import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.preference.PreferenceManager;
public class HomeMotor extends AndroidViewModel {
private final SharedPreferences prefs;
private final MutableLiveData<HomeViewState> states = new MutableLiveData<>();
public HomeMotor(@NonNull Application application) {
super(application);
prefs = PreferenceManager.getDefaultSharedPreferences(application);
prefs.registerOnSharedPreferenceChangeListener(LISTENER);
emitState();
}
@Override
protected void onCleared() {
prefs.unregisterOnSharedPreferenceChangeListener(LISTENER);
}
LiveData<HomeViewState> getStates() {
return states;
}
private void emitState() {
states.setValue(
new HomeViewState(prefs.getBoolean("checkbox", false),
prefs.getString("field", ""), prefs.getString("list", "")));
}
private SharedPreferences.OnSharedPreferenceChangeListener LISTENER =
(prefs, key) -> emitState();
}
package com.commonsware.jetpack.simpleprefs
import android.app.Application
import android.content.SharedPreferences
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.preference.PreferenceManager
class HomeMotor(application: Application) : AndroidViewModel(application) {
private val listener =
SharedPreferences.OnSharedPreferenceChangeListener { _, _ -> emitState() }
private val prefs = PreferenceManager.getDefaultSharedPreferences(application)
private val _states = MutableLiveData<HomeViewState>()
val states: LiveData<HomeViewState> = _states
init {
prefs.registerOnSharedPreferenceChangeListener(listener)
emitState()
}
override fun onCleared() {
prefs.unregisterOnSharedPreferenceChangeListener(listener)
}
private fun emitState() {
_states.value = HomeViewState(
prefs.getBoolean("checkbox", false),
prefs.getString("field", "") ?: "",
prefs.getString("list", "") ?: ""
)
}
}
When the motor is created, we call PreferenceManager.getDefaultSharedPreferences
to retrieve the SharedPreferences
object. Then, we call registerOnSharedPreferenceChangeListener()
to be notified of changes, then call emitState()
. That reads the values of our three preferences (using getBoolean()
and getString()
methods) and puts them in the HomeViewState
for the UI to use. Later on, when the HomeMotor
is cleared, we call unregisterOnSharedPreferenceChangeListener()
, so we no longer get updates (and so the motor can be garbage-collected).
The net result is that the HomeFragment
will show the initial default values, then will reflect the results of any changes that you make:
Modifying Preferences
Call edit()
on the SharedPreferences
object to get an “editor” for the preferences. This object has a set of setters that mirror the getters on the parent SharedPreferences
object. It also has:
-
remove()
to get rid of a single named preference -
clear()
to get rid of all preferences -
apply()
orcommit()
to persist your changes made via the editor
The last one is important — if you modify preferences via the editor and fail to save the changes, those changes will evaporate once the editor goes out of scope. commit()
is a blocking call, while apply()
works asynchronously. If you are on a background thread already, call commit()
; otherwise, call apply()
.
Prev Table of Contents Next
This book is licensed under the Creative Commons Attribution-ShareAlike 4.0 International license.