SharedPreferences の putStringSet()メソッドの不具合

Stringのリストがうまく永続化できない!

SharedPreferences を用いて Stringのリストを保存しようと思い下記のようにした。

// プリファレンスから、保存している値を得る
val pref = getSharedPreferences("prefs", Context.MODE_PRIVATE)
val stringSet = pref.getStringSet("key1", mutableSetOf())

// 適当な値をリストに追加
stringSet.add(stringSet.size.toString())

// プリファレンスに保存する
pref.edit().putStringSet("key1", stringSet).apply()

ところが次のような不思議な現象に遭遇した。

  • アプリの動作中は問題ない
  • アプリをいったん終了し、再度立ち上げると、一番最初に記録した値のみが再現され、その後に追加した値はなかったことになっていた

どうやら、(環境により異なるが)
/data/data/(applicationId)/shared_prefs
辺りに生成される「prefs.xml」(SharedPreferencesの実態)が最初の書き込み以降更新されていないようなのだ。
putString() とか putInt() で同じようにしたときには意図したとおりに動作するのに、なぜか putStringSet() ではそのようになる。

これはバグなのだろうか?調べてみたところJavaでも同じことが起こっているようだ。

解決策として、いくつか考えてみた。

putString()等でダミーを一緒に書き込む

// プリファレンスに保存する
pref.edit().putString("dummy", "dummy")
pref.edit().putStringSet("key1", stringSet).apply()

これは putString() ならうまくxmlファイルの更新がかかるのでそれを利用した。冗長なデータを書き込むのであまり気分はよくない。

一旦、全てをクリアしてしまう

// プリファレンスに保存する
pref.edit().clear()
pref.edit().putStringSet("key1", stringSet).apply()

気分一新、全ての内容をクリアしてから再度上書きする。この場合はこれでもなんとかなってしまうのだが、このデータベースに他の情報が一緒に記録されていれば(というかその可能性は高い)目も当てられないことになる。

該当する情報だけ一旦消してから書き直す

// プリファレンスに保存する
pref.edit().remove("key1").apply()
pref.edit().putStringSet("key1", stringSet).apply()

該当情報だけ消して書き直す。一番角が立たない方法だろう。
remove() で消去したあと、apply() するのがポイント!これを忘れるとやっぱり最初の現象と同じになってしまう。