【Kotlin】DelegatedProperties(委譲プロパティ) を使って Android SharedPreferences を使いやすくしてみた
IT技術
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// R: プロパティが定義されているクラス
2// T: プロパティの型
3class Hoge<R, T> : ReadWriteProperty<R, T> {
4 /*
5 * @param thisRef: プロパティが定義されているクラスのインスタンス
6 * @param property: プロパティ名 等のプロパティの情報が取得できる
7 */
8 override fun getValue(thisRef: R, property: KProperty<*>): T {
9 TODO("myPropertyの読み込み時の処理を書く")
10 }
11
12 /**
13 * @param value: プロパティにsetしようとしている値
14 */
15 override fun setValue(thisRef: R, property: KProperty<*>, value: T) {
16 TODO("myPropertyへの書き込み時の処理を書く")
17 }
18}
これで myProperty のget時には getValue() が呼ばれ、set時には setValue() が呼ばれるようになります!
SharedPreferences を使いやすくしてみた
この委譲プロパティを用いて、SharedPreferencesを使いやすくしたソースコードは以下の通りです。
1class SharedPref(context: Context) {
2
3 private val appPref: SharedPreferences = context.getSharedPreferences("app_pref", Context.MODE_PRIVATE)
4
5 // totalLaunchCount や lastLaunchTime への読み書きがそのまま SharedPreferences への読み書きになる
6 var totalLaunchCount: Int by pref(default = 0)
7 var lastLaunchTime: Long? by nullablePref()
8
9 private fun <T : Any> pref(default: T) = object : ReadWriteProperty<SharedPref, T> {
10
11 @Suppress("UNCHECKED_CAST")
12 override fun getValue(thisRef: SharedPref, property: KProperty<*>): T {
13 val key = property.name
14 return (appPref.all[key] as? T) ?: run {
15 put(key, default)
16 default
17 }
18 }
19
20 override fun setValue(thisRef: SharedPref, property: KProperty<*>, value: T) {
21 val key = property.name
22 put(key, value)
23 }
24 }
25
26 private fun <T : Any?> nullablePref() = object : ReadWriteProperty<SharedPref, T?> {
27
28 @Suppress("UNCHECKED_CAST")
29 override fun getValue(thisRef: SharedPref, property: KProperty<*>): T? {
30 val key = property.name
31 return appPref.all[key] as? T?
32 }
33
34 override fun setValue(thisRef: SharedPref, property: KProperty<*>, value: T?) {
35 val key = property.name
36 put(key, value)
37 }
38 }
39
40 private fun <T : Any?> put(key: String, value: T?) {
41 val editor = appPref.edit()
42 when (value) {
43 is Int -> editor.putInt(key, value)
44 is Long -> editor.putLong(key, value)
45 is Float -> editor.putFloat(key, value)
46 is String -> editor.putString(key, value)
47 is Boolean -> editor.putBoolean(key, value)
48 is Set<*> -> editor.putStringSet(key, value.map { it as String }.toSet())
49 null -> editor.remove(key)
50 else -> throw IllegalArgumentException("用意されていない型")
51 }
52 editor.apply()
53 }
54}
使い方
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 を対象にしましたが、委譲プロパティは他にも色々と使えそうですね。
面白い使い方があれば、またご紹介していきます!
こちらの記事もオススメ!
2020.08.14スマホ技術 特集Android開発Android開発をJavaからKotlinへ変えていくためのお勉強DelegatedPropert...
2020.07.17ライトコード的「やってみた!」シリーズ「やってみた!」を集めました!(株)ライトコードが今まで作ってきた「やってみた!」記事を集めてみました!※作成日が新し...
ライトコードでは、エンジニアを積極採用中!
ライトコードでは、エンジニアを積極採用しています!社長と一杯しながらお話しする機会もご用意しております。そのほかカジュアル面談等もございますので、くわしくは採用情報をご確認ください。
採用情報へ
おすすめ記事
immichを知ってほしい
2024.10.31