
【Kotlin】DelegatedProperties(委譲プロパティ) を使って Android SharedPreferences を使いやすくしてみた
2021.12.20
DelegatedProperties(委譲プロパティ)をご紹介

(株)ライトコードの江幡(えばた)です!
以前より、SharedPreferencesについてこんなことを感じていました。
「Key や デフォルト値を const(定数) で管理するのわかりづらいなぁ...」「もっと、わかりやすく単純に書く方法はないかなぁ?」
そう思っていたところ、Kotlin の DelegatedProperties(委譲プロパティ) を使えば、うまく解決できそうだったのでご紹介したいと思います!
今回は、Kotlin の DelegatedProperties(委譲プロパティ) についてお勉強しましょう!
DelegatedProperties(委譲プロパティ) とは
その名の通り、Kotlinの プロパティ 機能を、別のクラスに委譲することができる仕組みです。
個人的に、「カスタムGetter/Setter を別のクラスに書くもの」のようなものだと思っています。
委譲プロパティの書き方
プロパティ定義の右側に by を記載して定義します。
var myProperty: Int by Hoge()
上記は、こんなことを定義しています。
- myProperty への読み書き(Getter/Setter)の処理を、by の右側の Hoge で行うようにする
このときの Hoge は ReadWriteProperty を継承している必要があります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | // R: プロパティが定義されているクラス // T: プロパティの型 class Hoge<R, T> : ReadWriteProperty<R, T> { /** * @param thisRef: プロパティが定義されているクラスのインスタンス * @param property: プロパティ名 等のプロパティの情報が取得できる */ override fun getValue(thisRef: R, property: KProperty<*>): T { TODO("myPropertyの読み込み時の処理を書く") } /** * @param value: プロパティにsetしようとしている値 */ override fun setValue(thisRef: R, property: KProperty<*>, value: T) { TODO("myPropertyへの書き込み時の処理を書く") } } |
これで myProperty のget時には getValue() が呼ばれ、set時には setValue() が呼ばれるようになります!
SharedPreferences を使いやすくしてみた
この委譲プロパティを用いて、SharedPreferencesを使いやすくしたソースコードは以下の通りです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | class SharedPref(context: Context) { private val appPref: SharedPreferences = context.getSharedPreferences("app_pref", Context.MODE_PRIVATE) // totalLaunchCount や lastLaunchTime への読み書きがそのまま SharedPreferences への読み書きになる var totalLaunchCount: Int by pref(default = 0) var lastLaunchTime: Long? by nullablePref() private fun <T : Any> pref(default: T) = object : ReadWriteProperty<SharedPref, T> { @Suppress("UNCHECKED_CAST") override fun getValue(thisRef: SharedPref, property: KProperty<*>): T { val key = property.name return (appPref.all[key] as? T) ?: run { put(key, default) default } } override fun setValue(thisRef: SharedPref, property: KProperty<*>, value: T) { val key = property.name put(key, value) } } private fun <T : Any?> nullablePref() = object : ReadWriteProperty<SharedPref, T?> { @Suppress("UNCHECKED_CAST") override fun getValue(thisRef: SharedPref, property: KProperty<*>): T? { val key = property.name return appPref.all[key] as? T? } override fun setValue(thisRef: SharedPref, property: KProperty<*>, value: T?) { val key = property.name put(key, value) } } private fun <T : Any?> put(key: String, value: T?) { val editor = appPref.edit() when (value) { is Int -> editor.putInt(key, value) is Long -> editor.putLong(key, value) is Float -> editor.putFloat(key, value) is String -> editor.putString(key, value) is Boolean -> editor.putBoolean(key, value) is Set<*> -> editor.putStringSet(key, value.map { it as String }.toSet()) null -> editor.remove(key) else -> throw IllegalArgumentException("用意されていない型") } editor.apply() } } |
使い方
6、7行目のプロパティ(totalLaunchCount や lastLaunchTime)への読み書きが、
そのまま、プロパティ名をKey とした ShardPreferences への読み書きになります。
新たにSharedPreferencesの項目を追加したい場合は、以下のように、書くだけでOKです。
・non-nullの場合
var プロパティ名: Int by pref(default = 0)
・nullableの場合
var プロパティ名: Int? by nullablePref()
解説




さいごに
プロパティ名をそのままKeyとして使ってくれるので、わざわざconst(定数)を書く必要がなくなりました。
また、デフォルト値もわかりやすい位置に書けるようになりました。
書こうと思えば SharedPreferences で保存できない型でも定義できてしまうのが少し気になります。
でも、自分で使うだけなら、問題はないかなぁと思っています。
(もし忘れていても例外出るので...)
もし気になるのであれば、 fun intPref(): ReadWriteProperty<SharedPref, Int> {/*...省略...*/} という感じで、各型専用の委譲先クラスを作ればいいと思います。
今回は、 SharedPreferences を対象にしましたが、委譲プロパティは他にも色々と使えそうですね。
面白い使い方があれば、またご紹介していきます!
こちらの記事もオススメ!
書いた人はこんな人
