Takuji->find;

株式会社はてなでアプリケーションエンジニアやってます、技術的な記事を書いているつもり

KotlinのprovideDelegate operatorについて

こんばんは、Kotlin大好きAndroidアプリエンジニアの id:takuji31 です。

※これは はてなエンジニア Advent Calendar 2018 21日目の記事です。

provideDelegate operatorについて

provideDelegate operatorはKotlin 1.1からあるオペレーターで、プロパティーの移譲オブジェクトを生成することができます。

Delegated Properties - Kotlin Programming Language

このオペレーターを挟むことで、Delegated property生成時にロジックを実行することができ、例えば以下のようなことができます。

  • プロパティーの親のオブジェクトに何か操作をする
  • プロパティーの生成のパラメーターを検証する

使い方

まずはプロパティーになるclassを定義しましょう。

読み書きできるプロパティーを作るには kotlin.properties.ReadWriteProperty を実装したclassが必要です。

ここではAndroidのSharedPreferencesを操作するStringのpropertyを定義します。

class SharedPreferencesProperty(private val key: String, private val defaultValue: String?) :
    ReadWriteProperty<PreferencesModel, String?> {
    override fun getValue(thisRef: PreferencesModel, property: KProperty<*>): String? {
        return thisRef.sharedPreferences.getString(key, defaultValue)
    }

    override fun setValue(thisRef: PreferencesModel, property: KProperty<*>, value: String?) {
        thisRef.sharedPreferences.edit().putString(key, value).apply()
    }
}

次に provideDelegate をもつclassを定義します。

SharedPreferencesに値を出し入れするにはキーを保持する必要がありますが、必ずしもキーがプロパティー名と同じではありません。

そこでプロパティーの移譲オブジェクトにキーを渡せるようにします。

もちろんキーは空文字だと困るので検証したいですね。

class SharedPreferencesPropertyProvider(
    private val key: String? = null,
    private val defaultValue: String? = null
) {
    operator fun provideDelegate(
        thisRef: PreferencesModel,
        prop: KProperty<*>
    ): ReadWriteProperty<PreferencesModel, String?> {
        val propertyName = prop.name
        val key = key ?: propertyName

        require(key.isNotEmpty()) { "Key cannot be empty" }

        return SharedPreferencesProperty(key, defaultValue)
    }
}

最後にこのプロパティーを使うclassを定義します。

open class PreferencesModel(context: Context, name: String) {
    internal val sharedPreferences: SharedPreferences = context.getSharedPreferences(name, Context.MODE_PRIVATE)
    protected fun stringPreferences(key: String? = null, defaultValue: String? = null) = SharedPreferencesPropertyProvider(key, defaultValue)
}

class UserPreferences(context: Context) : PreferencesModel(context, "user") {
    var name: String? by stringPreferences()
    var profile: String? by stringPreferences("profile")
}

あとはこの UserPreferencesインスタンスのプロパティーをset/getするとSharedPreferencesを操作できます。

UserPreferences.profile にはキーの値を渡していますが、例えばこの値を空文字にするとインスタンス生成時に例外が発生するようになります。

正しくインスタンス生成ができるかはテストでチェックしてやるとよいでしょう。

最後に

provideDelegate メソッドを使うとDelegated propertyを更に柔軟にすることができるでしょう。

明日の担当は id:tkzwtks さんです。