What's the proper way to setup an Android PreferenceFragment?
Let's say we want to have a settings screen with one checkbox preference fragment as shown below:
Here is a step by step guide on how to build a settings activity where you can add some preferences to toggle or change the configurations for your Android app:
Add a dependency for support of preference fragment in
build.gradle
file forapp
module:dependencies { compile 'com.android.support:preference-v7:25.1.0' }
Add
xml
Android resource directory insideres
directory.Inside
xml
directory, add a newXML resource file
namedpref_visualizer.xml
as below. We're going to add one check-box preference fragment inside it.<?xml version="1.0" encoding="utf-8"?> <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> <CheckBoxPreference android:defaultValue="true" android:key="show_base" android:summaryOff="Bass will not be shown currently." android:summaryOn="Bass will be shown currently." android:title="Show Bass" /> </PreferenceScreen>
PreferenceScreen
is the root tag which can hold as many preference fragments as you want. If you want to add more configurations of type list or text box then you need to add it here as a child ofPreferenceScreen
tag.Add a new Java class named
SettingsFragment
which will hostPreferenceScreen
. It should extendPreferenceFragmentCompat
class as shown below:import android.content.SharedPreferences; import android.content.SharedPreferences.OnSharedPreferenceChangeListener; import android.os.Bundle; import android.support.v7.preference.CheckBoxPreference; import android.support.v7.preference.EditTextPreference; import android.support.v7.preference.ListPreference; import android.support.v7.preference.Preference; import android.support.v7.preference.PreferenceFragmentCompat; import android.support.v7.preference.PreferenceScreen; import android.widget.Toast; public class SettingsFragment extends PreferenceFragmentCompat { @Override public void onCreatePreferences(Bundle bundle, String s) { addPreferencesFromResource(R.xml.pref_visualizer); } }
Now comes the final part where we build the association between an activity in the app and
SettingsFragment
class which hostsPreferenceScreen
. Add a new activity namedSettingsActivity
which inherits fromAppCompatActivity
class.SettingsActivity
class will act as the container forPreferenceScreen
.
Java file for SettingsActivity
:
import android.support.v4.app.NavUtils;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.MenuItem;
public class SettingsActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_settings);
}
}
Layout file for SettingsActivity
is shown below (activity_settings.xml
). Here android.name
property is the crux. It connects this activity to any of the classes present in your entire project which are inheriting from PreferenceFragmentCompat
class. I had only one such class named SettingsFragment
. You might have more than one class inheriting from PreferenceFragmentCompat
class if you app has multiple settings screen.
<?xml version="1.0" encoding="utf-8"?>
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/activity_settings"
android:name="android.example.com.visualizerpreferences.SettingsFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
You're all set!
what class should SettingsActivity extend?
What worked for me was extending AppCompatActivity
.
static final String ANIMATION = "animation" ;
static final String COUNTDOWN_ON_OFF = "countdown_on_off";
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
if (getFragmentManager().findFragmentById(android.R.id.content) == null)
{
getFragmentManager().beginTransaction().add(android.R.id.content, new Prefs()).commit();
}
}
I kicked out all the generated code related to preference headers and kept some template methods/ variables (which Android Studio generated in some earlier version) for my PreferenceFragment
public static class Prefs extends PreferenceFragment
{
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.preferences);
// Bind the summaries of EditText/List/Dialog/Ringtone preferences
// to their values. When their values change, their summaries are
// updated to reflect the new value, per the Android Design
// guidelines.
// findPreference() uses android:key like in preferences.xml !
bindPreferenceSummaryToValue(findPreference(ANIMATION));
}
}
A static method in my Activity
class (adapted from the template). You may want to check for other preference types:
/**
* Binds a preference's summary to its value. More specifically, when the
* preference's value is changed, its summary (line of text below the
* preference title) is updated to reflect the value. The summary is also
* immediately updated upon calling this method. The exact display format is
* dependent on the type of preference.
*
* @see #sBindPreferenceSummaryToValueListener
*/
private static void bindPreferenceSummaryToValue(Preference preference)
{
// Set the listener to watch for value changes.
preference.setOnPreferenceChangeListener(sBindPreferenceSummaryToValueListener);
// Trigger the listener immediately with the preference's
// current value.
if (preference instanceof CheckBoxPreference)
{
sBindPreferenceSummaryToValueListener.onPreferenceChange(preference,
PreferenceManager
.getDefaultSharedPreferences(preference.getContext())
.getBoolean(preference.getKey(), true));
}
else
{
sBindPreferenceSummaryToValueListener.onPreferenceChange(preference,
PreferenceManager
.getDefaultSharedPreferences(preference.getContext())
.getString(preference.getKey(), ""));
}
}
And finally, the Preference.OnPreferenceChangeListener
as static variable in the Activity
(also adapted from the template):
/**
* A preference value change listener that updates the preference's summary
* to reflect its new value.<br>
* template by Android Studio minus Ringtone Preference
*/
private static Preference.OnPreferenceChangeListener sBindPreferenceSummaryToValueListener = new Preference.OnPreferenceChangeListener()
{
@Override
public boolean onPreferenceChange(Preference preference, Object value)
{
String stringValue = value.toString();
if (preference instanceof ListPreference)
{
// For list preferences, look up the correct display value in
// the preference's 'entries' list.
ListPreference listPreference = (ListPreference) preference;
int index = listPreference.findIndexOfValue(stringValue);
// Set the summary to reflect the new value.
preference.setSummary(
index >= 0
? listPreference.getEntries()[index]
: null);
}
else if (preference instanceof RingtonePreference)
{
// my app didn't need that
return true;
}
else if (preference instanceof CheckBoxPreference)
{
Context ctx = preference.getContext();
boolean isChecked = (Boolean) value;
if (preference.getKey().equals(ANIMATION))
{
preference.setSummary(isChecked ? ctx.getString(R.string.sOn) : ctx.getString(R.string.sOff));
}
else if (preference.getKey().equals(COUNTDOWN_ON_OFF))
{
preference.setSummary(isChecked ? ctx.getString(R.string.sWhenPaused) : ctx.getString(R.string.sNever) );
}
}
else
{
// For all other preferences, set the summary to the value's
// simple string representation.
preference.setSummary(stringValue);
}
return true;
}
};
}
Here's on Kotlin and android-x :
gradle:
implementation 'androidx.appcompat:appcompat:1.1.0-rc01'
implementation 'androidx.core:core-ktx:1.2.0-alpha02'
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta2'
implementation 'com.google.android.material:material:1.1.0-alpha08'
implementation "androidx.preference:preference-ktx:1.1.0-rc01"
implementation 'androidx.core:core-ktx:1.2.0-alpha02'
implementation 'androidx.collection:collection-ktx:1.1.0'
implementation 'androidx.fragment:fragment-ktx:1.2.0-alpha01'
MainActivity.kt
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (savedInstanceState == null)
supportFragmentManager.commit { replace(android.R.id.content, PrefsFragment()) }
}
class PrefsFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.preferences, rootKey)
}
}
}
preferences.xml
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<androidx.preference.Preference android:title="hello"/>
</androidx.preference.PreferenceScreen>