17

How can I modify the summary of a ListPreference to the new "Entry" string selected by the user (not the entry value)

I suppouse its with setOnPreferenceChangeListener() but in

new OnPreferenceChangeListener() {

        @Override
        public boolean onPreferenceChange(Preference preference, Object newValue) {
            return true;
        }
    }

using ((ListPreference)preference).getEntry() I get the old value (because it isn't changed yet) and newValue.toString() returns the entry value (not the entry text displayed to the user)

How can I do it? Thanks in advance

Addev
  • 31,819
  • 51
  • 183
  • 302

14 Answers14

41

Just set the summary value to %s in the xml description.

EDIT: I've tested it on several devices and it doesn't work really. That's strange because according to docs it must work: ListPreference.getSummary().

But it's possible to implement this functionality by oneself. The implementation requires to inherit from the ListPreference class:

public class MyListPreference extends ListPreference {
    public MyListPreference(final Context context) {
        this(context, null);
    }

    public MyListPreference(final Context context, final AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public CharSequence getSummary() {
        final CharSequence entry = getEntry();
        final CharSequence summary = super.getSummary();
        if (summary == null || entry == null) {
             return null;
        } else {
            return String.format(summary.toString(), entry);
        }
    }

    @Override
    public void setValue(final String value) {
        super.setValue(value);
        notifyChanged();
    }
}

As you can see MyListPreference has its own summary field which can contain formatting markers. And when a preference's value changes, Preference.notifyChanged() method is called and it causes MyListPreference.getSummary() method to be called from Preference.onBindView().

P.S.: This approach hasn't been tested sufficiently so it may contain errors.

Michael
  • 53,859
  • 22
  • 133
  • 139
  • What exactly doesn't work? The summary isn't shown at all or its text differs from the selected entry's text? – Michael Aug 11 '11 at 07:36
  • In the summary appears exactly %s not the converted chain – Addev Aug 12 '11 at 12:27
  • I've added another solution to the answer. – Michael Aug 15 '11 at 09:49
  • If I have 2 ListPreferences, one after another, only one of them get it's summary updated. What could be causing that? I assume android:summary="%s" is still neccessary? Without it it doesn't seem to work at all – Jacek Kwiecień Dec 18 '12 at 09:23
  • According to Android source code everything should work fine. Could you please explain step by step what happens. And it will be even better if you provide sources. – Michael Jan 03 '13 at 17:08
  • This is working for me (Galaxy Nexus on 4.2.1). Only catch is it doesn't automatically update when you select a new item. Reloading the settings activity updates the summary text. – Bryan Herbst Feb 08 '13 at 19:59
  • It works on first load (with a default value), but it's not updated until another preference is toggled. It's rebinding too late or something. I added [an example using `OnSharedPreferenceChangeListener`](http://stackoverflow.com/a/15329652/197359). – Alex Mar 11 '13 at 01:00
  • 1
    @Michael `%s` it works, why do you think it doesn't work? which devices did you tested? – user924 Dec 31 '19 at 23:39
  • 1
    @user924 The original answer is from 2011. Things might've changed since that time. – Michael Jan 01 '20 at 16:39
10

Nauman Zubair is right.

The %s implementation is buggy. The view shows the correct value on first load (if a default list value is set), but the view does not update when selecting a list item. You have to toggle a checkbox or some other preference to update the view.

As a workaround, implement OnSharedPreferenceChangeListener to override the summary for the list preference.

/src/apps/app_settings/SettingsActivity.java

package apps.app_settings;

import android.os.Bundle;
import android.preference.PreferenceActivity;

public class SettingsActivity extends PreferenceActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        /* set fragment */
        getFragmentManager().beginTransaction().replace(android.R.id.content, new SettingsFragment()).commit();
    }
}

/src/apps/app_settings/SettingsFragment.java

package apps.app_settings;

import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.os.Bundle;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceFragment;

public class SettingsFragment extends PreferenceFragment implements OnSharedPreferenceChangeListener {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        /* set preferences */
        addPreferencesFromResource(R.xml.preferences);
    }

    @Override
    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
        /* get preference */
        Preference preference = findPreference(key);

        /* update summary */
        if (key.equals("list_0")) {
            preference.setSummary(((ListPreference) preference).getEntry());
        }
    }

    @Override
    public void onResume() {
        super.onResume();
        getPreferenceManager().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
    }

    @Override
    public void onPause() {
        getPreferenceManager().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this);
        super.onPause();
    }
}

/res/xml/preferences.xml

<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
  xmlns:android="http://schemas.android.com/apk/res/android">
  <ListPreference
    android:key="list_0"
    android:title="@string/settings_list_0_title"
    android:summary="%s"
    android:entries="@array/settings_list_0_entries"
    android:entryValues="@array/settings_list_0_entry_values"
    android:defaultValue="@string/settings_list_0_default_value"/>
</PreferenceScreen>

/res/values/strings.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
  <string name="settings_list_0_title">list 0</string>
  <string-array name="settings_list_0_entries">
    <item>item 0</item>
    <item>item 1</item>
    <item>item 2</item>
  </string-array>
  <string-array name="settings_list_0_entry_values">
    <item>0</item>
    <item>1</item>
    <item>2</item>
  </string-array>
  <string name="settings_list_0_default_value">0</string>
</resources>
Community
  • 1
  • 1
Alex
  • 5,909
  • 2
  • 35
  • 25
8

i solved this problem with another and simple solution (https://gist.github.com/brunomateus/5617025):

public class ListPreferenceWithSummary extends ListPreference{

public ListPreferenceWithSummary(Context context, AttributeSet attrs) {
    super(context, attrs);
}

public ListPreferenceWithSummary(Context context) {
    super(context);
}

@Override
public void setValue(String value) {
    super.setValue(value);
    setSummary(value);
}

@Override
public void setSummary(CharSequence summary) {
    super.setSummary(getEntry());
}
}

This worked very fine on my GirgerBeard device. Even when is the first time running your app. Don't forget to provide default value on your xml prefence:

android:defaultValue="default value"

and set default values on your PreferenceActivity or PrefenceFragment:

 PreferenceManager.setDefaultValues(this, R.xml.you pref file, false);
Bruno Mateus
  • 1,727
  • 18
  • 25
  • Beautiful! Tested on Android 2.3.6 using PreferenceActivity and Android 4.1.1 using PreferenceFragment - works perfectly! – Magnus Mar 03 '14 at 21:50
3

I recommend to implement the OnSharedPreferenceChangeListener in your PreferenceFragment or PreferenceActivity instead of Preference.setOnPreferenceChangeListner. Use setSummay to set the new changes. (Do not forget to register and unregister the listener.) This listener is called after the change to the preference has been completed. You should also set a default value in the XML for the preferences.

Nauman Zubair
  • 1,208
  • 15
  • 27
  • 1
    Hope you don't mind, I added [an example for this approach](http://stackoverflow.com/a/15329652/197359). – Alex Mar 11 '13 at 00:54
2

The "%s" solution works for me on android 4.0.3, by writting %s directly in the XML file. The problem is the text is not updated after I changed the value of the preference but it is when I modify another preference of my PreferenceScreen. Maybe some refresh is missing here.

slash33
  • 899
  • 1
  • 11
  • 17
  • Yes, I have the same problem. I think I might need a special 'on preference change listener' to handle this. Just not quite sure how to go about it. – Brian Reinhold Nov 10 '12 at 22:30
  • It works on first load (with a default value), but it's not updated until another preference is toggled. It's rebinding too late or something. I added [an example using `OnSharedPreferenceChangeListener`](http://stackoverflow.com/a/15329652/197359). – Alex Mar 11 '13 at 00:55
2

This is the most simplified way I have implemented. Just take the values given by the onPreferenceChange listener

    ListPreference preference;        
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    preference = (ListPreference)findPreference("myKey");
    preference.setSummary(preferenceColorButtons.getEntry());
    preference.setOnPreferenceChangeListener(new        OnPreferenceChangeListener() {

        @Override
        public boolean onPreferenceChange(Preference preference, Object newValue) {
            ListPreference listPreference = (ListPreference)preference;
            int id = 0;
            for (int i = 0; i < listPreference.getEntryValues().length; i++)
            {
                if(listPreference.getEntryValues()[i].equals(newValue.toString())){
                    id = i;
                    break;
                }
            }
            preference.setSummary(listPreference.getEntries()[id]);
            return true;
        }
    });

}

luisgm
  • 21
  • 1
  • You could just discard the for loop : listPreference.getEntries()[Integer.parseInt(newValue.toString())]; – Nana Ghartey Jul 12 '16 at 12:27
  • @NanaGhartey you're wrong, why do you think `newValue` will be "0", "1", "2" with every developer? It's not array ordered indexes, `newValue` can be any String The best way is to use `indexOf`: `pref.summary = entries[entryValues.indexOf(newValue)]` – user924 Dec 31 '19 at 23:31
1

For all I know:

a) The %s does work on android 4, but not on 2.x.

b) The update is achieved if you set a dummy value in between, see here: https://stackoverflow.com/a/16397539/1854563

Community
  • 1
  • 1
Gunnar Bernstein
  • 6,074
  • 2
  • 45
  • 67
1

There is no need to extend ListPreference or to loop over the entryValues etc

public boolean onPreferenceChange(Preference preference, Object newValue) {
    int i = ((ListPreference)preference).findIndexOfValue(newValue.toString());
    CharSequence[] entries = ((ListPreference)preference).getEntries();
    preference.setSummary(entries[i]);

    return true;
}
peko
  • 11,267
  • 4
  • 33
  • 48
1

I know that it's a very old question, but it's still actual. To have the summary automatically updated you have to call the original preferenceChangeListener:

final OnPreferenceChangeListener listener = preference.getOnPreferenceChangeListener();
preference.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener()
        {
            @Override
            public boolean onPreferenceChange(Preference preference, Object o)
            {
                if (listener !=null)
                    listener .onPreferenceChange(preference, o);

                return true;
            }
        });
P1x
  • 1,690
  • 1
  • 19
  • 24
0

I also faced this problem and I finally found a solution by using the value coming from the listener. In my example below (for a ListPreference), I first get the index of the value in the ListPreference array, then I retrieve the label of the value using this index:

passwordFrequencyLP.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
        @Override
        public boolean onPreferenceChange(Preference preference, Object newValue) {
            int newFrequency = Integer.valueOf(newValue.toString());

            prefs.edit().putInt("settings_key_password_frequency", newFrequency).commit();

            //get the index of the new value selected in the ListPreference array
            int index = passwordFrequencyLP.findIndexOfValue(String.valueOf(newValue));
            //get the label of the new value selected
            String label = (String) passwordFrequencyLP.getEntries()[index];

            passwordFrequencyLP.setSummary(label);

            makeToast(getResources().getString(R.string.password_frequency_saved));
            return true;
        }
    });

This little trick works well, I found many different possible solutions to this problem but only this one worked for me.

Yoann Hercouet
  • 17,894
  • 5
  • 58
  • 85
0

Hi edited the above for EditText if that helps :)

package fr.bmigette.crocoschedulerconsoleandroid;
import android.content.Context;
import android.preference.EditTextPreference;
import android.util.AttributeSet;

/**
 * Created by bastien on 28/07/2015.
 */
public class EditTextPreferenceWithSummary extends EditTextPreference{
    public EditTextPreferenceWithSummary(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public EditTextPreferenceWithSummary(Context context) {
        super(context);
    }

    @Override
    public void setText(String value) {
        super.setText(value);
        setSummary(value);
    }

    @Override
    public void setSummary(CharSequence summary) {
        super.setSummary(getText());
    }
}

And creates the preference.xml like this:

<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >

    <PreferenceCategory
        android:key="pref_key_category_host"
        android:title="@string/title_category_host" >
        <fr.bmigette.crocoschedulerconsoleandroid.EditTextPreferenceWithSummary
            android:defaultValue="@string/default_pref_host_1"
            android:key="pref_key_host_1"
            android:summary="@string/default_pref_host_1"
            android:title="@string/title_pref_host_1" />

        <fr.bmigette.crocoschedulerconsoleandroid.EditTextPreferenceWithSummary
            android:defaultValue="@string/default_pref_port_1"
            android:key="pref_key_port_1"
            android:summary="@string/default_pref_port_1"
            android:title="@string/title_pref_port_1" />

        <fr.bmigette.crocoschedulerconsoleandroid.EditTextPreferenceWithSummary
            android:defaultValue="@string/default_pref_pass_1"
            android:key="pref_key_pass_1"
            android:summary="@string/default_pref_pass_1"
            android:title="@string/title_pref_pass_1" />

    </PreferenceCategory>
</PreferenceScreen>
bmigette
  • 59
  • 1
  • 12
0

There is no need to do those extra coding, i have faced this problem and solved it by a single line.

After many hours looking on all the answers in this and other similar questions, i found out very easiest way to do this without extending ListPreference, you can do it in common extends PreferenceActivity, see the code bellow.

public class UserSettingsActivity extends PreferenceActivity 
{
    ListPreference listpref;

    @Override
    public void onCreate(Bundle savedInstenceState)
    {
        super.onCreate(savedInstenceState);
        addPreferencesFromResource(R.xml.settings);
        listpref = (ListPreference)findPreference("prefDefaultCurrency");

        listpref.setOnPreferenceChangeListener(new OnPreferenceChangeListener() 
        {           
            @Override
            public boolean onPreferenceChange(Preference preference, Object value) {
            // TODO Auto-generated method stub

            listpref.setSummary(listpref.getEntries()[Integer.parseInt(value.toString())]);
            return true;
        }
    });

Thats it, with a single line, all you have to do is get value passed from the onPreferenceChange(Preference preference, Object value) parse it to integer and pass it to listpref.setSummary(listpref.getEntries()[Integer.parseInt(value.toString())]);

Darshan
  • 515
  • 1
  • 3
  • 16
0

Referring to the accepted answer ( @Michael ), if you modify the MyListPreference to add an onPreferenceChangeListener:

public MyListPreference(final Context context, final AttributeSet attrs) {
    super(context, attrs);
    setOnPreferenceChangeListener(this);
}

and have the listener implementation return true:

public boolean onPreferenceChange(Preference preference, Object newValue) {
    // Do other stuff as needed, e.g. setTitle()
    return true;
}

the summary '%s' always resolves to the latest setting. Otherwise, it always has the default setting.

Don't forget to add implements:

public class MyListPreference extends ListPreference implements Preference.OnPreferenceChangeListener
0

Simplest way in Kotlin

findPreference<ListPreference>(getString(R.string.pref_some_key)).apply {
    summary = entry
    setOnPreferenceChangeListener { _, newValue ->
        summary = entries[entryValues.indexOf(newValue)]
        return@setOnPreferenceChangeListener true
    }
}
user924
  • 8,146
  • 7
  • 57
  • 139