MultiChoice Preference Widget for Android


Strangely, the Android framework does not include a widget which allows multiple selections in Preference Screens. I needed to add one of these for selecting folders to monitor for new media in PicPush and ended up having to implement it myself. If this is something you need for your Android app, following this tutorial will have you up and running in under 5 minutes.

Source code with example XML files can be downloaded from here.
Thanks to Mathieu Chauvinc for implementing XML attributes for separator and a check all option!

Step 1: Declare stylable attributes for check all and separator characters in attr.xml

A special XML attribute can be used to define which (if any) is the “Check all” button using the yournamespace:checkAll attribute in the preferences.xml.

A special XML attribute can be used to define the separator value you wish to use (for when the references are stored to disk on the device). Using yournamespace:separator the you can create their own choice of separator (without this the code will fall back to default)

In order to make use of the custom attributes in the layout XML file you must declare them as styleable in attr.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="ListPreferenceMultiSelect">
    	<attr format="string" name="checkAll" />
    	<attr format="string" name="separator" />
    </declare-styleable>
</resources>

Step 2: Declare a multi-choice preference widget in your preferences XML file

The first step involves adding the appropriate XML to the file where your preference screen is defined. This can be done as so (You’ll have to change the undefined references to real @string constants):

<com.threefiftynice.android.preference.ListPreferenceMultiSelect android:defaultValue="#ALL#"
android:dependency="@string/pref_auto_scan_image"
    android:key="@string/pref_upload_buckets_image"
    android:title="Image folders to monitor" android:dialogTitle="Image folders to monitor"
    android:summary="Specify which folders should be monitored for images"
    android:entries="@array/pref_upload_buckets_default_entries"
    android:entryValues="@array/pref_upload_buckets_default_values"/>

Step 3: Declare supporting resources for the preferences attributes in

The preferences.xml file is referencing some string and array constants which we need to define in other resources

strings.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
	<string name="pref_upload_buckets_image">upload.buckets_image</string>
</resources>

arrays.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
	<string-array name="pref_upload_buckets_default_entries">
		<item>All</item>
		<item>Test1 Display</item>
		<item>Test2 Display</item>
	</string-array>
	<string-array name="pref_upload_buckets_default_values">
		<item>#ALL#</item>
		<item>Test1 Value</item>
		<item>Test2 Value</item>
	</string-array>
</resources>

Step 4: Create Java class which implements the Multiple Choice List Preference

Now, of course, we need a Java class to back this up. The implementation is straightforward. It extends the ListPreference widget so it can inherit all the XML attributes recognized by it. From there, a few key methods are overridden which are explained below.

Step 4.1: Override onPrepareDialogBuilder (line 44)

onPrepareDialogBuilder is the method responsible for creating the popup dialog which contains all the choices to be displayed. The change from the base class here is simply to set the multi choice entries of the builder rather than the single choice entries. This takes care of the UI.

Step 4.2: Override onDialogClosed (line 69)

When the dialog is closed we need to build a special string to store in the SharedPreferences. The string is made up of the selected entries separated by a special separator string. The separator string i chose is quite weird, it has to be, to avoid conflicts with actual entries.

Step 4.3: Provide a static convenience method for parsing the stored value into a String array (line 62)

When a client reads back the stored value from the SharedPreferences it wont be very useful unless it can be parsed. This can be delegated to our custom implementation via a static method called parseStoredValue. This convenience method returns a String array which contains the user’s choices.

Step 5: You’re Done! Run the code!

Following the above steps should have you in good shape to get up and running. I implemented this code a few days ago, and am writing this tutorial from memory so i may have missed a few small details, however the main bones of the implementation is definitely contained here. Any problems/questions? Leave a comment below.

Walla!

Walla!

, , ,

  1. #1 by Steve M at October 11th, 2009

    I am very novice at java and android.
    I got the menu to appear and allow multiple selections. My question is how can I get access to the stored string[] array.
    I also want to say thank you for this.

  2. #2 by declanshanaghy at January 24th, 2010

    Sorry for the huge delay on getting back to you here.
    The choices are stored in the preferences as a plain old string so read it back using something like this:

    String rawval = prefs.getStirng(KEY)
    String[] selected = ListPreferenceMultiSelect.parseStoredValue(rawval);

  3. #3 by cash advances online at April 20th, 2010

    I took java classes a few years ago and am currently pretty new using android. I am going to try this. Thanks for posting this by the way. But hopefully it will all go smooth.

  4. #4 by Peter at June 19th, 2010

    I have run into a few problems using this code on my HTC Desire.
    The ListPreference onSetInitialValue and setValue implementations are both throwing

    java.lang.ClassCastException: java.lang.Boolean

    Is there an updated version for 2.1?

  5. #5 by Santosh at June 24th, 2010

    Excellent! Thanks for publishing this.

  6. #6 by JRS at July 14th, 2010

    Hi, I’m new to java myself and have a (probably) dumb question.

    In your XML:
    android:entries=”@array/pref_upload_buckets_default_entries” android:entryValues=”@array/pref_upload_buckets_default_values”/>

    where is “pref_upload_buckets_default_entries” actually defined?

    Thanks!

  7. #7 by declanshanaghy at July 14th, 2010

    @array/pref_upload_buckets_default_entries referrs to an array defined in res/values/arrays.xml

    In this array you should put the entries that you want to display on the dialog with checkboxes.

    @array/pref_upload_buckets_default_values defines the values that co-respond to each of the entries.

    P.S.
    The names pref_upload_buckets_* are application specific, you should name you arrays accordingly for your application

  8. #8 by JRS at July 14th, 2010

    OK, thanks. I am trying to build my array list from a list of folders gained through the java code by looking at the SDCard. Stuck on that, but thank you for the quick reply!

    I just don’t know how to pass the array list from java to the listpreference.

  9. #9 by JRS at July 15th, 2010

    Banging my head against a will with this, do you happen to have a short example showing how to populate the initial list and states of the checked boxes from java? Would really be appreciated.

    Thank You!

  10. #10 by declanshanaghy at July 15th, 2010

    I tried to email you an example but it seems the emial address you put in is not valid.

    Please email me: declan@350nice.com

  11. #11 by Leimi at August 3rd, 2010

    Hey, thanks a lot for this :)
    I’d like to know, is there a way to put multiple elements in the “android:DefaultValue” xml attribute so that multiple items are checked by default ?
    Thanks,
    Leimi

  12. #12 by Leimi at August 11th, 2010

    Answering myself :
    In fact we can just put one string containing the values we want separated by the separator from the ListPreferenceMultiSelect.
    For example if I want 50, 80 and 90 selected by default and my separator is “xxx” I will just put :
    android:DefaultValue=”50xxx80xxx90″
    in the xml !
    After that, in the Java code, we use the :
    ListPreferenceMultiSelect.parseStoredValue()
    method !

  13. #13 by Nick at August 24th, 2010

    Curious how you might implement a max selection on the # of checkboxes allowed in this multicheckbox dialog? I can’t quite figure out how to do it. I can determine how many are checked, and ensure that any new checked boxes are not saved, but how do I override the check mark icon from appearing at all. Any thoughts from the poster or others would be appreciated:)

  14. #14 by declanshanaghy at August 25th, 2010

    You would have to override how the dialog is constructed, replacing it with an Adapter that attaches an onClickListener to the checkboxes (to get click events) or in the getView method find the checkbox and hide/disable it as required.

    Tricky but with some poking around i think it could be done.

  15. #15 by pengwang at August 28th, 2010

    i donot appear “all” in the dialog as you like,
    in my array.xml i used

    Alpha Option 01
    Beta Option 02
    Charlie Option 03

    so my dialog only appear three items,donot appear “all ” on the top .why?

  16. #16 by Al at October 3rd, 2010

    What license is this released under>

  17. #17 by Chris Cooper at January 5th, 2011

    Great article but having trouble after installing my app for the very first time. Keep getting force close errors if I try to change one of these settings in my preferences. I’ve tracked it down to the setValue(val) call in the overridden onDialogClosed() method. Works fine when relaunching the app but just crashes the first time it’s run. Any ideas why this might be happening? I’ve spent days trying to figure this out so I thought I’d ask the master!

  18. #18 by declanshanaghy at January 5th, 2011

    Can you email the exception please. declan at 350nice dot com

  19. #19 by Chris Cooper at January 5th, 2011

    Thanks declan, I’ve sent you an email. Hope it makes sense!

  20. #20 by Chris Cooper at January 5th, 2011

    Turns out it was an error in my code not handling all the preference possibilities. Thanks again!

  21. #21 by dle at January 9th, 2011

    Thanks declan, nice info

  22. #22 by Goulou at January 23rd, 2011

    Al :
    What license is this released under>

    Same question : is this code freely re-usable as is?

    May your answer be yes or no, thanks anyway!! :-)

  23. #23 by Ramon M. at February 28th, 2011

    Hi declan,

    Great code but I guess I’ve found a little mistake that made the app crash if you had not selected any value from the list before.

    On line 71 I’ve preceded the line
    String[] vals = parseStoredValue(getValue());
    with the sentence
    if ( getValue() != null ) {
    that embraces all the afterwards treatment of the ‘vals’ variable.

    It works and the app does not crash anymore.

    Greetings,

    Ramon Maria

  24. #24 by DE at March 10th, 2011

    Maybe this is a dumb question but I’m pretty new to Android: I want to know how do you load/show ListPreferenceMultiSelect?

  25. #25 by declanshanaghy at March 10th, 2011

    DE :

    Maybe this is a dumb question but I’m pretty new to Android: I want to know how do you load/show ListPreferenceMultiSelect?

    [ Edit | Delete | Spam ]

    Well….u don’t.
    U need to integrate it into your own code to make use of it.

  26. #26 by DE at March 10th, 2011

    I guess the question is, how would you integrate it? I created a new class file and copied your source over. I’ve tried to use the Dialog class and setting it’s content with the xml layout above and attempted to launch it but the application crashes. Is there a better way for going about this?

  27. #27 by declanshanaghy at March 10th, 2011

    Nice way – Please paste the stack trace.

    Mean way – http://vincnetas.tumblr.com/post/1130542373/stack-trace-or-gtfo

    :-)

  28. #28 by DE at March 10th, 2011

    Here you go:

    D/AndroidRuntime( 1996): Shutting down VM
    W/dalvikvm( 1996): threadid=1: thread exiting with uncaught exception (group=0×40015560)
    E/AndroidRuntime( 1996): FATAL EXCEPTION: main
    E/AndroidRuntime( 1996): java.lang.RuntimeException: Unable to start activity ComponentInfo{com.application.dealspot/com.application.dealspot.DealSpotActivity}: java.lang.NullPointerException
    E/AndroidRuntime( 1996): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1647)
    E/AndroidRuntime( 1996): at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1663)
    E/AndroidRuntime( 1996): at android.app.ActivityThread.access$1500(ActivityThread.java:117)
    E/AndroidRuntime( 1996): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:931)
    E/AndroidRuntime( 1996): at android.os.Handler.dispatchMessage(Handler.java:99)
    E/AndroidRuntime( 1996): at android.os.Looper.loop(Looper.java:123)
    E/AndroidRuntime( 1996): at android.app.ActivityThread.main(ActivityThread.java:3683)
    E/AndroidRuntime( 1996): at java.lang.reflect.Method.invokeNative(Native Method)
    E/AndroidRuntime( 1996): at java.lang.reflect.Method.invoke(Method.java:507)
    E/AndroidRuntime( 1996): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:839)
    E/AndroidRuntime( 1996): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597)
    E/AndroidRuntime( 1996): at dalvik.system.NativeStart.main(Native Method)
    E/AndroidRuntime( 1996): Caused by: java.lang.NullPointerException
    E/AndroidRuntime( 1996): at com.threefiftynice.android.ListPreferenceMultiSelect.(ListPreferenceMultiSelect.java:30)
    E/AndroidRuntime( 1996): at com.threefiftynice.android.ListPreferenceMultiSelect.(ListPreferenceMultiSelect.java:40)
    E/AndroidRuntime( 1996): at com.application.dealspot.DealSpotActivity.onCreate(DealSpotActivity.java:64)
    E/AndroidRuntime( 1996): at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1047)
    E/AndroidRuntime( 1996): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1611)
    E/AndroidRuntime( 1996): … 11 more

  29. #29 by declanshanaghy at March 11th, 2011

    ListPreferenceMultiSelect.java:30 is your culprit. Seems like getEntries() is returning null. Did you setup some entries in the XML?

  30. #30 by DE at March 13th, 2011

    Yes I did. Here’s the xml:

    xmlns:android="http://schemas.android.com/apk/res/android"
    android:defaultValue="#ALL#"
    android:title="Image folders to monitor" android:dialogTitle="Image folders to monitor"
    android:summary="Specify which folders should be monitored for images"
    android:entries="@array/categories"
    android:entryValues="@array/categories_id"/>

    Question, what do I need to do other than instantiating the ListPreferenceMultiSelect class to make it appear?

  31. #31 by declanshanaghy at March 14th, 2011

    Oh i think i see where the disconnect is now.
    You need to instantiate a PreferenceActivity which uses the preferences xml for its configuration.

    See the android examples for loading preferences from xml.

  32. #32 by DE at March 14th, 2011

    I did that and I seem to be encountering other issues. Mind if we continue our discussion over email? I’d really appreciate it. Can I email you on declan@350nice.com?

  33. #33 by DE at March 14th, 2011

    Good news! I got it to work but not quite the way I expected. The solution was I had to wrap the XML for the ListPreferenceMultiSelect within a PreferenceScreen class.

    When I launch the activity to display the preferences a PreferenceScreen is displayed first (http://i.imgur.com/fYyuV.png). I click on the item within the screen and the dialog pops up with the entries to select (http://i.imgur.com/rIJ3z.png). Is there any way to display the dialog directly with the need for a PreferenceScreen? Any hints would be appreciated.

    Thanks!

  34. #34 by declanshanaghy at March 15th, 2011

    You can email if you want.

    There’s no way to use this thing (as is) standalone. Its designed to be integrated into a PreferenceScreen

    You could probably do some hacking to get it to work standalone but be prepared for some work!

  35. #35 by novice101 at March 23rd, 2011

    I know I’m being stupid but how would I attach this ListPreference to a button in another class?

    Sorry, I’m new to android programming

  36. #36 by declanshanaghy at March 25th, 2011

    You cant launch it directly from a button without some additional work. You need to use it from within a PreferenceScreen.

    Here are some docs:
    http://developer.android.com/reference/android/preference/PreferenceScreen.html

  37. #37 by ilh at April 16th, 2011

    Don’t know why something like this isn’t included by default but thanks!

    One thing though is that if you press the cancel button then the dialog doesn’t work quite as expected. If you uncheck an item then the next time you go in to the dialog it is rechecked as it should be, however if you check an item and cancel it is still checked when you go back in. This is due to the way you reapply the checks in restoreCheckedEntries() which only does anything with the saved items which are checked leaving the previously cancelled items still checked as it is still referencing the same boolean array which holds the state of the checks.

    To solve this would mean resetting the boolean array. You reset the boolean array in setEntries but i’ve found that this method never gets called (in 2.2 at least). It’s also reset in the constructor but this is called when the screen holding the ListPreferenceMultiSelect is created, not when actually opening the dialog itself. I’ve moved the resetting of the boolean array to onPrepareDialogBuilder() before the call to restoreCheckedEntries() as that gets called every time you open the dialog and it now works.

    Hope that made sense

  38. #38 by sregg at April 21st, 2011

    Hi,
    First, thank you for your great work.
    However, I would like to add icons on each items of the list.
    I have a multi select preference list to select languages, so I would like to add the corresponding flag on the left of each item.
    Is it possible?
    If so, how?

    Thank you in advance for your help.

  39. #39 by declanshanaghy at April 21st, 2011

    It is possible, however too complicated to explain in a comment post or over email.

    At a high level, how to do it would be to redefine the view that’s used for each line item and attach an image to it aswell as a piece of text.

  40. #40 by RobG at May 13th, 2011

    Very handy code. I had to add a check for null in the parseStoredValue because I didn’t set any defaults.

  41. #41 by rg at May 23rd, 2011

    I’d like to make the first check box function as a “select all”, is it possible?

  42. #42 by declanshanaghy at May 24th, 2011

    It is possible but kinda tricky.
    You will have to override the portion of the PreferenceScreen which creates the views and attach a listener to the first checkbox which can set the other ones checked/unchecked.

    It would be a nice addition to this class.

  43. #43 by Cars at June 14th, 2011

    Do you really think that it may be interested in women?

  44. #44 by Curly at August 4th, 2011

    Thanks for this :-D

  45. #45 by DonVincenzo at August 10th, 2011

    Thanks for sharing this, very helpful … but i have the same question as JRS

    “do you happen to have a short example showing how to populate the initial list and states of the checked boxes from java?”

  46. #46 by David at August 16th, 2011

    Hi!
    I Get This Error

    Binary XML file line #6: Error inflating class ListPreferenceMultiSelect

    Thankx.

  47. #47 by Gary at September 2nd, 2011

    Thank you.

  48. #48 by Tos at November 1st, 2011

    Thank you for the tutorial. I like it !

    I did make a few changes:
    1) make static the following:
    private static String separator;
    static public String[] parseStoredValue(CharSequence val)

    2) if you use parseStoredValue() before the constructor is called, field “separator” is null and a crash will happen. You can avoid this by hard coding a value:
    private static String separator = DEFAULT_SEPARATOR;

  49. #49 by Volodia Krupach at November 23rd, 2011

    Thank you!
    When supplying custom separator “|” it was treated as regex symbol in:
    return ((String)val).split(separator)
    so I changed:
    return ((String)val).split(”\\Q” + separator + “\\E”)
    “\Q” at the beginning and “\E” at the end escape regex symbols.

  50. #50 by mysho at January 12th, 2012

    I used this script for a dynamic preferences
    here is my code:
    ListPreferenceMultiSelect refreshTabPreferences = new ListPreferenceMultiSelect(this);
    refreshTabPreferences.setDefaultValue(”#ALL#”);
    refreshTabPreferences.setKey(”refreshTabs”);
    refreshTabPreferences.setTitle(”Refresh Tabs”);
    refreshTabPreferences.setDialogTitle(”Refresh Tabs”);
    refreshTabPreferences.setSummary(”Auto refresh of tabs”);
    refreshTabPreferences.setEntries(tabsEntries);
    refreshTabPreferences.setEntryValues(tabsEntriesValues);
    firstCategory.addPreference(refreshTabPreferences);

    When I run this code I get this error:
    java.lang.NullPointerException at lines 58 and 69
    58 is: mClickedDialogEntryIndices = new boolean[getEntries().length];

    69 is: public ListPreferenceMultiSelect(Context context) {
    this(context, null);
    }

    Where is problem ? Thanks a lot …

(will not be published)