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

    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ライトコード的「やってみた!」シリーズ「やってみた!」を集めました!(株)ライトコードが今まで作ってきた「やってみた!」記事を集めてみました!※作成日が新し...

    ライトコードでは、エンジニアを積極採用中!

    ライトコードでは、エンジニアを積極採用しています!社長と一杯しながらお話しする機会もご用意しております。そのほかカジュアル面談等もございますので、くわしくは採用情報をご確認ください。

    採用情報へ

    おすすめ記事

    エンジニア大募集中!

    ライトコードでは、エンジニアを積極採用中です。

    特に、WEBエンジニアとモバイルエンジニアは是非ご応募お待ちしております!

    また、フリーランスエンジニア様も大募集中です。

    background