ListPreference と % の罠


“%” を含む summary を設定すると不可解なエラーが・・・

Android で設定値を扱う時に利用するクラスが Preference です。中でもリスト形式で設定値を選択するのが ListPreference です。
listpreference
上図のように、ListPreference で “%” という文字を含んだ選択肢を扱う場合には注意が必要です。現在の設定値をユーザに示すためには summary を使うことになりますが、xml 上で android:summary=”20%” としたり、コード上で setSummary(“20%”) とした途端、意味の分からないエラーに悩まされるハメに陥ります。実際に発生するエラーは色々なもので、思いもしないところに発生したりするので原因の特定に時間がかかります。

summary は書式文字列(ただし Honycomb 以降)

原因は、summary が書式文字列として取り扱われることにあります。リファレンスsetSummary メソッドの説明は以下のようになっています。

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 when it’s retrieved.

もし、summary が書式文字列を含む場合(例: “%s” や “%1$s” 等)、summary の値を取得する際に該当箇所に現在のエントリ値が代入されます。

これにより、 “20%” のような文字列の “%” は書式の開始と受け取られるため不正な書式となり、エラーが発生します。しかも、xml の読み取り時や setSummary の実行時ではなく、summary が表示される段階でエラーが発生するため原因が非常にわかりにくくなります。

このエラーを避けるためには、“%” を表示したい場合には “%%” と指定します。つまり、”20%” と表示するためには setSummary(“20%%”) とします。

書式文字列が使えて自動的に現在値と置き換えられるのは一見便利そうに思えますが、設定を変更した場合に自動的に表示が更新されるわけではなく、OnSharedPreferenceChangeListener の設定は必要なので、手間が省けるわけではありません。

その上やっかいな事に、リファレンスには特に断りがありませんが、書式文字列として扱われるのは Honycomb 以降で、Gingerbread 以下では単なる文字列として扱われます。つまり、setSummary(“20%%”) とすると、Honycomb 以降では “20%” と表示され、Gingerbread 以下では “20%%” と表示されてしまいます。(この事実は、こちらのサイトで知り、実際にエミュレータで確認しました。)

どのように対処するか

対処方法は以下のようになるかと思います。

  1. summary の初期値については、
    プラットフォーム別のリソースファイル(ここでは、pref_default.xml とします) で定義しておきます。

    [values/pref_default.xml]

    [values-v11/pref_default.xml]

    values-v11 で定義された値は、API レベル 11 以上の場合のみ適用されます。従って Gingerbread(API レベル 9, 10)以下と、Honycomb(API レベル 12, 13) 以上とで異なる値を提供できます。この値を利用して、以下のように summary を設定できます。

    [preferences.xml]

  2. また、設定値が変更された時の対処は、
    onSharedPreferenceChanged にて、以下のように Honycomb 以降の場合には “%” を余分に付加するようなコードを書けば良いでしょう。

※2012/02/16 Preference オブジェクトの取得と使用が離れていたので、findPreference を pref 使用の直前に移動しました。

以上で、どのプラットフォームでも ListPreferencesummary を正しく表示できるようになります。
[参考にしたサイト]