• トップ
  • ブログ一覧
  • 【Kotlin】DelegatedProperties(委譲プロパティ) を使って Android SharedPreferences を使いやすくしてみた
  • 【Kotlin】DelegatedProperties(委譲プロパティ) を使って Android SharedPreferences を使いやすくしてみた

    えばたん(エンジニア)えばたん(エンジニア)
    2019.05.15

    IT技術

    DelegatedProperties(委譲プロパティ)をご紹介

     

    江幡さん 江幡さん

    (株)ライトコードの江幡(えばた)です!

    以前より、SharedPreferencesについてこんなことを感じていました。

    「Key や デフォルト値を const(定数) で管理するのわかりづらいなぁ...」「もっと、わかりやすく単純に書く方法はないかなぁ?」

    そう思っていたところ、Kotlin の DelegatedProperties(委譲プロパティ) を使えば、うまく解決できそうだったのでご紹介したいと思います!

    今回は、Kotlin の DelegatedProperties(委譲プロパティ) についてお勉強しましょう!

    DelegatedProperties(委譲プロパティ) とは

    その名の通り、Kotlinの プロパティ 機能を、別のクラスに委譲することができる仕組みです。

    個人的に、「カスタムGetter/Setter を別のクラスに書くもの」のようなものだと思っています。

    委譲プロパティの書き方

    プロパティ定義の右側に by を記載して定義します。

    var myProperty: Int by Hoge()

    上記は、こんなことを定義しています。

    1. 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()

    解説

    江幡さん 江幡さん
    pref()  か nullablePref()  で 委譲先のインスタンスを取得しています。
    ミツオカミツオカ
    引数でデフォルト値を設定できるんですね!
    江幡さん 江幡さん
    委譲先のgetValue() setValue() にて、SharedPreferencesの読み書きを行っています。13、21、30、35行目のようにval key = property.name でプロパティ名が取得できるので、SharedPreferencesの読み書きに必要なKeyとして利用しています。
    ミツオカミツオカ
    なるほど!ここでプロパティ名を取得して、Keyとして使っているのですね!

    さいごに

    プロパティ名をそのままKeyとして使ってくれるので、わざわざconst(定数)を書く必要がなくなりました

    また、デフォルト値もわかりやすい位置に書けるようになりました。

    書こうと思えば SharedPreferences で保存できない型でも定義できてしまうのが少し気になります。

    でも、自分で使うだけなら、問題はないかなぁと思っています。

    (もし忘れていても例外出るので...)

    もし気になるのであれば、fun intPref(): ReadWriteProperty<SharedPref, Int> {/*...省略...*/} という感じで、各型専用の委譲先クラスを作ればいいと思います。

    今回は、 SharedPreferences を対象にしましたが、委譲プロパティは他にも色々と使えそうですね。

    面白い使い方があれば、またご紹介していきます!

    こちらの記事もオススメ!

    featureImg2020.08.14スマホ技術 特集Android開発Android開発をJavaからKotlinへ変えていくためのお勉強DelegatedPropert...

    featureImg2020.07.17ライトコード的「やってみた!」シリーズ「やってみた!」を集めました!(株)ライトコードが今まで作ってきた「やってみた!」記事を集めてみました!※作成日が新し...

    えばたん(エンジニア)

    えばたん(エンジニア)

    おすすめ記事