This must come up very often.
When the user is editing preferences in an Android app, I'd like them to be able to see the currently set value of the preference in the Preference
summary.
Example: if I have a Preference setting for "Discard old messages" that specifies the number of days after which messages need to be cleaned up. In the PreferenceActivity
I'd like the user to see:
"Discard old messages" <- title
"Clean up messages after x days" <- summary where x is the current Preference value
Extra credit: make this reusable, so I can easily apply it to all my preferences regardless of their type (so that it work with EditTextPreference, ListPreference etc. with minimal amount of coding).
Source: Tips4all
Unfortunately there doesn't seem to be a simple, automated way of doing this (you'll notice that very few of the 'native' preferences do this). That said, I'm sure it's a pretty common requirement, so here's the technique I use to achieve it.
ReplyDeleteThe key is to make your PreferenceActivity class implement OnSharedPreferenceChangeListener. Using the onSharedPreferenceChanged method you can listen for specific preference keys and use setSummary on the related preference control to modify the text, like this:
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
// Let's do something a preference value changes
if (key.equals(KEY_A_CHECKBOX_PREFERENCE)) {
mCheckBoxPreference.setSummary(sharedPreferences.getBoolean(key, false) ? "Disable this setting" : "Enable this setting");
}
else if (key.equals(KEY_AN_EDITTEXT_PREFERENCE)) {
mEditBoxPreference.setSummary("Current value is " + sharedPreferences.getString(key, ""));
}
}
Listen for changes in preference using the registerOnSharedPreferenceChangeListener method on the applications Shared Preferences to assign this class as a listener, like this:
getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
Your best bet is to register it in onResume and deregister on onPause. You'll also need to update the text values when you register the listener to ensure you get the right initial values.
The following example is based on the AdvancedPreferences example project in the Android code samples.
public class AdvancedPreferences extends PreferenceActivity implements OnSharedPreferenceChangeListener {
public static final String KEY_LIST_PREFERENCE = "list_preference";
public static final String KEY_CHECKBOX_PREFERENCE = "checkbox_preference";
private CheckBoxPreference mCheckBoxPreference;
private ListPreference mListPreference;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Load the XML preferences file
addPreferencesFromResource(R.xml.advanced_preferences);
// Get a reference to the preferences
mCheckBoxPreference = (CheckBoxPreference)getPreferenceScreen().findPreference(KEY_ADVANCED_CHECKBOX_PREFERENCE);
mListPreference = (ListPreference)getPreferenceScreen().findPreference(KEY_LIST_PREFERENCE);
}
@Override
protected void onResume() {
super.onResume();
// Setup the initial values
mCheckBoxPreference.setSummary(sharedPreferences.getBoolean(key, false) ? "Disable this setting" : "Enable this setting");
mListPreference.setSummary("Current value is " + sharedPreferences.getValue(key, ""));
// Set up a listener whenever a key changes
getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
}
@Override
protected void onPause() {
super.onPause();
// Unregister the listener whenever a key changes
getPreferenceScreen().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this);
}
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
// Let's do something a preference value changes
if (key.equals(KEY_CHECKBOX_PREFERENCE)) {
mCheckBoxPreference.setSummary(sharedPreferences.getBoolean(key, false) ? "Disable this setting" : "Enable this setting");
}
else if (key.equals(KEY_LIST_PREFERENCE)) {
mListPreference.setSummary("Current value is " + sharedPreferences.getValue(key, ""));
}
}
}
There are ways to make this a more generic solution, if that suits your needs.
ReplyDeleteFor example, if you want to generically have all list preferences show their choice as summary, you could have this for your onSharedPreferenceChanged implementation:
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
Preference pref = findPreference(key);
if (pref instanceof ListPreference) {
ListPreference listPref = (ListPreference) pref;
pref.setSummary(listPref.getEntry());
}
}
This is easily extensible to other preference classes.
And by using the getPreferenceCount and getPreference functionality in PreferenceScreen and PreferenceCategory, you could easily write a generic function to walk the preference tree setting the summaries of all preferences of the types you desire to their toString representation
Here is my solution... FWIW
ReplyDeletepackage com.example.PrefTest;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.os.Bundle;
import android.preference.EditTextPreference;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.preference.PreferenceCategory;
import android.preference.PreferenceManager;
public class Preferences extends PreferenceActivity implements OnSharedPreferenceChangeListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.preferences);
PreferenceManager.setDefaultValues(Preferences.this, R.xml.preferences, false);
for(int i=0;i<getPreferenceScreen().getPreferenceCount();i++){
initSummary(getPreferenceScreen().getPreference(i));
}
}
@Override
protected void onResume(){
super.onResume();
// Set up a listener whenever a key changes
getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
}
@Override
protected void onPause() {
super.onPause();
// Unregister the listener whenever a key changes
getPreferenceScreen().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this);
}
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
updatePrefSummary(findPreference(key));
}
private void initSummary(Preference p){
if (p instanceof PreferenceCategory){
PreferenceCategory pCat = (PreferenceCategory)p;
for(int i=0;i<pCat.getPreferenceCount();i++){
initSummary(pCat.getPreference(i));
}
}else{
updatePrefSummary(p);
}
}
private void updatePrefSummary(Preference p){
if (p instanceof ListPreference) {
ListPreference listPref = (ListPreference) p;
p.setSummary(listPref.getEntry());
}
if (p instanceof EditTextPreference) {
EditTextPreference editTextPref = (EditTextPreference) p;
p.setSummary(editTextPref.getText());
}
}
}
This is the code you need to set the summary to the chosen value. It also sets the values on startup and respects the default values, not only on change. Just change "R.layout.prefs" to your xml-file and extend the setSummary-method to your needs. It actually is only handling ListPreferences, but it is easy to customize to respect other Preferences.
ReplyDeletepackage de.koem.timetunnel;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.os.Bundle;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.preference.PreferenceGroup;
public class Prefs
extends PreferenceActivity
implements OnSharedPreferenceChangeListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.addPreferencesFromResource(R.layout.prefs);
this.initSummaries(this.getPreferenceScreen());
this.getPreferenceScreen().getSharedPreferences()
.registerOnSharedPreferenceChangeListener(this);
}
/**
* Set the summaries of all preferences
*/
private void initSummaries(PreferenceGroup pg) {
for (int i = 0; i < pg.getPreferenceCount(); ++i) {
Preference p = pg.getPreference(i);
if (p instanceof PreferenceGroup)
this.initSummaries((PreferenceGroup) p); // recursion
else
this.setSummary(p);
}
}
/**
* Set the summaries of the given preference
*/
private void setSummary(Preference pref) {
// react on type or key
if (pref instanceof ListPreference) {
ListPreference listPref = (ListPreference) pref;
pref.setSummary(listPref.getEntry());
}
}
/**
* used to change the summary of a preference
*/
public void onSharedPreferenceChanged(SharedPreferences sp, String key) {
Preference pref = findPreference(key);
this.setSummary(pref);
}
// private static final String LOGTAG = "Prefs";
}
koem
Thanks, Reto, for the detailed explanation!
ReplyDeleteIn case this is of any help to anyone, I had to change the code proposed by Reto Meier to make it work with the SDK for Android 1.5
@Override
protected void onResume() {
super.onResume();
// Setup the initial values
mListPreference.setSummary("Current value is " + mListPreference.getEntry().toString());
// Set up a listener whenever a key changes
...
}
The same change applies for the callback function onSharedPreferenceChanged(...)
Cheers,
Chris
Actually, CheckBoxPreference does have the ability to specify a different summary based on the checkbox value. See the android:summaryOff and android:summaryOn attributes (as well as the corresponding CheckBoxPreference methods).
ReplyDeleteThanks for this tip!
ReplyDeleteI have one preference screen and want to show the value for each list preference as the summary.
This is my way now:
public class Preferences extends PreferenceActivity implements OnSharedPreferenceChangeListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.preferences);
}
@Override
protected void onResume() {
super.onResume();
// Set up initial values for all list preferences
Map<String, ?> sharedPreferencesMap = getPreferenceScreen().getSharedPreferences().getAll();
Preference pref;
ListPreference listPref;
for (Map.Entry<String, ?> entry : sharedPreferencesMap.entrySet()) {
pref = findPreference(entry.getKey());
if (pref instanceof ListPreference) {
listPref = (ListPreference) pref;
pref.setSummary(listPref.getEntry());
}
}
// Set up a listener whenever a key changes
getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
}
@Override
protected void onPause() {
super.onPause();
// Unregister the listener whenever a key changes
getPreferenceScreen().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this);
}
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
Preference pref = findPreference(key);
if (pref instanceof ListPreference) {
ListPreference listPref = (ListPreference) pref;
pref.setSummary(listPref.getEntry());
}
}
This works for me, but I'm wondering what is the best solution (performance, stability, scalibility): the one Koem is showing or this one?
My option is to extend ListPreference and it's clean:
ReplyDeletepublic class ListPreferenceShowSummary extends ListPreference {
private final static String TAG = ListPreferenceShowSummary.class.getName();
public ListPreferenceShowSummary(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public ListPreferenceShowSummary(Context context) {
super(context);
init();
}
private void init() {
LogIncoming.e(TAG, "init");
setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference arg0, Object arg1) {
LogIncoming.w(TAG, "display score changed to "+arg1);
arg0.setSummary(getEntry());
return true;
}
});
}
@Override
public CharSequence getSummary() {
return super.getEntry();
}
}
Then you add in your settings.xml:
<yourpackage.ListPreferenceShowSummary
android:key="key" android:title="title"
android:entries="@array/entries" android:entryValues="@array/values"
android:defaultValue="first value"/>
To set the summary of a ListPreference to the value selected in a dialog you could use this code:
ReplyDeletepackage yourpackage;
import android.content.Context;
import android.util.AttributeSet;
public class ListPreference extends android.preference.ListPreference {
public ListPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
protected void onDialogClosed(boolean positiveResult) {
super.onDialogClosed(positiveResult);
if (positiveResult) setSummary(getEntry());
}
protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
super.onSetInitialValue(restoreValue, defaultValue);
setSummary(getEntry());
}
}
and reference the yourpackage.ListPreference object in your preferences.xml remembering to specify there your android:defaultValue as this triggers the call to onSetInitialValue().
If you want you can then modify the text before calling setSummary() to whatever suits your application.
Android documentation says one can use a String formatting marker in getSummary():
ReplyDeleteIf the summary has a String formatting marker in it (i.e. "%s" or "%1$s"), then the current entry value will be substituted in its place.
Simply specifying android:summary="Clean up messages after %s days" in ListPreference xml declaration worked for me.
I've seen all voted answers show how to set the summary with the exact current value, but the asker wanted also something like:
ReplyDelete"Clean up messages after x days"* <- summary where x is the current Preference value
Here is my answer for achieving that
As documentation says about ListPreference.getSummary():
Returns the summary of this ListPreference. If the summary has a String formatting marker in it (i.e. "%s" or "%1$s"), then the current
entry value will be substituted in its place.
However, I tried in several devices and it doesn't seem to work. With some research I found a good solution in this answer. It simply consists of extend every Preference you use and override getSummary() to work as specified by Android documentation.