Kotlin らしく SharedPreferences をシンプルに扱う Extension を実装する (kotlinx.serialization シリアライズ対応)

> 2020/10/25: kotlinx.serialization 1.0.0 の変更に対応しました

---

Kotlin の委譲プロパティを使って、Android SharedPreferences を無駄なくシンプルに扱えるようにします。

以下の記事の kotlinx.serialization 版です。説明は以下の記事を参照してください。

* [Kotlin らしく SharedPreferences をシンプルに扱う Extension を実装する (Moshi Json シリアライズ対応](https://qiita.com/irgaly/items/4b149068b2f62289e7a5)

# SharedPreferencesExtension.kt

```kotlin
import android.content.SharedPreferences
import kotlinx.serialization.KSerializer
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty

/**
 * Boolean Read Write Delegate
 */
fun SharedPreferences.boolean(defaultValue: Boolean = false, key: String? = null): ReadWriteProperty<Any, Boolean> =
    delegate(defaultValue, key, SharedPreferences::getBoolean, SharedPreferences.Editor::putBoolean)

/**
 * Nullable Boolean Read Write Delegate
 */
fun SharedPreferences.nullableBoolean(key: String? = null): ReadWriteProperty<Any, Boolean?> =
    nullableDelegate(false, key, SharedPreferences::getBoolean, SharedPreferences.Editor::putBoolean)

/**
 * Float Read Write Delegate
 */
fun SharedPreferences.float(defaultValue: Float = 0f, key: String? = null): ReadWriteProperty<Any, Float> =
    delegate(defaultValue, key, SharedPreferences::getFloat, SharedPreferences.Editor::putFloat)

/**
 * Nullable Float Read Write Delegate
 */
fun SharedPreferences.nullableFloat(key: String? = null): ReadWriteProperty<Any, Float?> =
    nullableDelegate(0f, key, SharedPreferences::getFloat, SharedPreferences.Editor::putFloat)

/**
 * Int Read Write Delegate
 */
fun SharedPreferences.int(defaultValue: Int = 0, key: String? = null): ReadWriteProperty<Any, Int> =
    delegate(defaultValue, key, SharedPreferences::getInt, SharedPreferences.Editor::putInt)

/**
 * Nullable Int Read Write Delegate
 */
fun SharedPreferences.nullableInt(key: String? = null): ReadWriteProperty<Any, Int?> =
    nullableDelegate(0, key, SharedPreferences::getInt, SharedPreferences.Editor::putInt)

/**
 * Long Read Write Delegate
 */
fun SharedPreferences.long(defaultValue: Long = 0, key: String? = null): ReadWriteProperty<Any, Long> =
    delegate(defaultValue, key, SharedPreferences::getLong, SharedPreferences.Editor::putLong)

/**
 * Nullable Long Read Write Delegate
 */
fun SharedPreferences.nullableLong(key: String? = null): ReadWriteProperty<Any, Long?> =
    nullableDelegate(0, key, SharedPreferences::getLong, SharedPreferences.Editor::putLong)

/**
 * String Read Write Delegate
 */
fun SharedPreferences.string(defaultValue: String = "", key: String? = null): ReadWriteProperty<Any, String> =
    delegate(defaultValue, key, SharedPreferences::getString, SharedPreferences.Editor::putString)

/**
 * Nullable String Read Write Delegate
 */
fun SharedPreferences.nullableString(key: String? = null): ReadWriteProperty<Any, String?> =
    nullableDelegate("", key, SharedPreferences::getString, SharedPreferences.Editor::putString)

/**
 * Enum Read Write Delegate
 */
fun <T : Enum<T>> SharedPreferences.enum(valueOf: (String) -> T, defaultValue: Lazy<T>, key: String? = null) =
    object : ReadWriteProperty<Any, T> {
        override fun getValue(thisRef: Any, property: KProperty<*>): T {
            return getString(key ?: property.name, null)?.let { valueOf(it) } ?: defaultValue.value
        }

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

/**
 * Nullable Enum Read Write Delegate
 */
fun <T : Enum<T>> SharedPreferences.nullableEnum(valueOf: (String) -> T, key: String? = null) =
    object : ReadWriteProperty<Any, T?> {
        override fun getValue(thisRef: Any, property: KProperty<*>): T? {
            return getString(key ?: property.name, null)?.let { valueOf(it) }
        }

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

/**
 * Kotlinx.Serialization Json String Read Write Delegate
 */
inline fun <reified T : Any> SharedPreferences.json(json: Json, defaultValue: Lazy<T>, key: String? = null) =
    object : ReadWriteProperty<Any, T> {
        override fun getValue(thisRef: Any, property: KProperty<*>): T {
            return getString(key ?: property.name, null)?.let { json.decodeFromString<T>(it) } ?: defaultValue.value
        }

        override fun setValue(thisRef: Any, property: KProperty<*>, value: T) {
            edit().putString(key ?: property.name, json.encodeToString(value)).apply()
        }
    }

/**
 * Nullable Kotlinx.Serialization Json String Read Write Delegate
 */
inline fun <reified T : Any> SharedPreferences.nullableJson(json: Json, key: String? = null): ReadWriteProperty<Any, T?> =
    object : ReadWriteProperty<Any, T?> {
        override fun getValue(thisRef: Any, property: KProperty<*>): T? {
            return getString(key ?: property.name, null)?.let { json.decodeFromString(it) }
        }

        override fun setValue(thisRef: Any, property: KProperty<*>, value: T?) {
            edit().putString(key ?: property.name, value?.let { json.encodeToString(value) }).apply()
        }
    }

/**
 * Kotlinx.Serialization Json String Read Write Delegate
 */
fun <T : Any> SharedPreferences.json(json: Json, serializer: KSerializer<T>, defaultValue: Lazy<T>, key: String? = null) =
    object : ReadWriteProperty<Any, T> {
        override fun getValue(thisRef: Any, property: KProperty<*>): T {
            return getString(key ?: property.name, null)?.let { json.decodeFromString(serializer, it) } ?: defaultValue.value
        }

        override fun setValue(thisRef: Any, property: KProperty<*>, value: T) {
            edit().putString(key ?: property.name, json.encodeToString(serializer, value)).apply()
        }
    }

/**
 * Nullable Kotlinx.Serialization Json String Read Write Delegate
 */
fun <T : Any> SharedPreferences.nullableJson(json: Json, serializer: KSerializer<T>, key: String? = null) =
    object : ReadWriteProperty<Any, T?> {
        override fun getValue(thisRef: Any, property: KProperty<*>): T? {
            return getString(key ?: property.name, null)?.let { json.decodeFromString(serializer, it) }
        }

        override fun setValue(thisRef: Any, property: KProperty<*>, value: T?) {
            edit().putString(key ?: property.name, value?.let { json.encodeToString(serializer, it) }).apply()
        }
    }

private inline fun <T : Any> SharedPreferences.delegate(
    defaultValue: T, key: String?,
    crossinline getter: SharedPreferences.(key: String, defaultValue: T) -> T?,
    crossinline setter: SharedPreferences.Editor.(key: String, value: T) -> SharedPreferences.Editor
) = object : ReadWriteProperty<Any, T> {
    override fun getValue(thisRef: Any, property: KProperty<*>) = getter(key ?: property.name, defaultValue) ?: defaultValue
    override fun setValue(thisRef: Any, property: KProperty<*>, value: T) =
        edit().setter(key ?: property.name, value).apply()
}

private inline fun <T : Any> SharedPreferences.nullableDelegate(
    dummy: T, key: String?,
    crossinline getter: SharedPreferences.(key: String, defaultValue: T) -> T?,
    crossinline setter: SharedPreferences.Editor.(key: String, value: T) -> SharedPreferences.Editor
) = object : ReadWriteProperty<Any, T?> {
    override fun getValue(thisRef: Any, property: KProperty<*>): T? {
        val target = key ?: property.name
        return if (contains(target)) getter(target, dummy) else null
    }

    override fun setValue(thisRef: Any, property: KProperty<*>, value: T?) {
        val target = key ?: property.name
        if (value == null) {
            edit().remove(target).apply()
        } else {
            edit().setter(target, value).apply()
        }
    }
}
```