SharedPreferenceをDelegatedPropertiesでスリムに[Google I/O Android Appより]

本記事はand factory Advent Calendar 2019 21日目の記事です！

こんにちは。Androidエンジニアの[@nabetaro_jp](https://twitter.com/nabetaro_jp)です。[The Google I/O 2019 Android App](https://github.com/google/iosched)のDataをLocalに保存するレイヤーのコードからSharedPreferencesに値を保存する部分で参考になる部分のメモとしてコードの内容を、簡略化してまとめたいと思います。


# PreferenceStorage

Preferenceに保存する値のメンバーを定義するインターフェース

```kotlin:PreferenceStorage.kt

interface PreferenceStorage {
    var id: Int
    var name: String?
    var isSubscribed: Boolean
}
```

# ReadWritePropertyを継承したクラス：StringPreference, IntPreference, BooleanPreference

ReadWritePropertyとは読み取りや書き込みのプロパティのproperty delegatesを実装するのに使用できるインターフェースです。便宜上のみの提供なので、基本このインターフェースを拡張する必要はないのですが、データの永続化のために拡張している模様です。

```
interface ReadWriteProperty<in R, T>
```
参考：https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.properties/-read-write-property/index.html


```kotlin:Preference.kt
class BooleanPreference(
    private val preferences: Lazy<SharedPreferences>,
    private val name: String,
    private val defaultValue: Boolean
) : ReadWriteProperty<Any, Boolean> {

    @WorkerThread
    override fun getValue(thisRef: Any, property: KProperty<*>): Boolean {
        return preferences.value.getBoolean(name, defaultValue)
    }

    override fun setValue(thisRef: Any, property: KProperty<*>, value: Boolean) {
        preferences.value.edit { putBoolean(name, value) }
    }
}

class StringPreference(
    private val preferences: Lazy<SharedPreferences>,
    private val name: String,
    private val defaultValue: String?
) : ReadWriteProperty<Any, String?> {

    @WorkerThread
    override fun getValue(thisRef: Any, property: KProperty<*>): String? {
        return preferences.value.getString(name, defaultValue)
    }

    override fun setValue(thisRef: Any, property: KProperty<*>, value: String?) {
        preferences.value.edit { putString(name, value) }
    }
}

class IntPreference(
    private val preferences: Lazy<SharedPreferences>,
    private val name: String,
    private val defaultValue: Int = 0
) : ReadWriteProperty<Any, Int> {

    @WorkerThread
    override fun getValue(thisRef: Any, property: KProperty<*>): Int {
        return preferences.value.getInt(name, defaultValue)
    }

    override fun setValue(thisRef: Any, property: KProperty<*>, value: Int) {
        preferences.value.edit { putInt(name, value) }
    }
}
```


# SharedPreferenceStorage : PreferenceStorage

```kotlin:SharedPreferenceStorage.kt

@Singleton
class SharedPreferenceStorage @Inject constructor(context: Context) : PreferenceStorage {

    private val prefs: Lazy<SharedPreferences> = lazy {
        // Lazy to prevent IO access to main thread.
        context.applicationContext.getSharedPreferences(
            PREFS_NAME, MODE_PRIVATE
        )
    }

    override var id: Int by IntPreference(prefs, PREF_ID)
    override var name: String? by StringPreference(prefs, PREF_NAME, "")
    override var isSubscribed: Boolean by BooleanPreference(prefs, PREF_IS_SUBSCRIBED, false)

    companion object {
        const val PREFS_NAME = "my_preference"
        const val PREF_ID = "pref_id"
        const val PREF_NAME = "pref_name"
        const val PREF_IS_SUBSCRIBED = "pref_is_subscribed"
    }

}
```

注目したのはここ👀

```
override var name: String? by StringPreference(prefs, PREF_NAME, "")
```


`DelegatedProperties`(委譲プロパティ)ですね。`by`を記載することで nameへの読み書き、つまりGetter/Setterの処理を byで指定している StringPreferenceで行うよ〜　ってことですね。

参考：https://sys1yagi.gitbooks.io/anatomy-kotlin/content/%E3%83%87%E3%83%AA%E3%82%B2%E3%83%BC%E3%83%88%E3%83%97%E3%83%AD%E3%83%91%E3%83%86%E3%82%A3%E3%81%AE%E6%AD%A3%E4%BD%93.html

# 終わり
ボイラープレートも減ってスッキリとprefに保存する処理がかけることでしょう。 Kotlinに感謝ですね〜👀