Kotlin

Delegated Properties で遊ぼう(スライド版)

More than 1 year has passed since last update.

そのうちちゃんと記事書きます。


だれ



  • icon_128.png @kikuchy

  • 株式会社ミクシィ -> 株式会社Diverse

  • Android書いてるおじさん

前回、KotlinでDSL作るはなしをした者です。

:point_right: 3分で作る Kotlin Friendly な API



Delegated Propertyとは?


  • クラスのプロパティ(メンバ)のsetter/getter処理を別のオブジェクトに移譲する機能

  • 特定のメソッドさえ実装していれば、移譲先オブジェクトになれる



何がうれしいの?



遅延処理ができる!!!!!!


// Activityの中

lateinit var hoge:String

...

override fun onCreate(...) {
// Fragment の arguments についても同様の悩みが
hoge = intent.getStringExtra("hoge") ?: ""
}

コンストラクタで初期化できないばっかりに、

val の恩恵を受けられない! :anger: :anger: :anger:

lateinitvar のみ使用可能)



ボイラープレートを大幅に減らすことができる!!!


// Fragmentの中

var listener: HogeFragmentActionListener? = null

override onAttach(...) {
if (activity is HogeFragmentActionListener) {
listener = activity
} else {
throw Exception("""
ActivityはHogeFragmentActionListenerを実装してないとだめ
"""
)
}
}

これFragmentにいちいち書かなきゃダメ????



Delegated Propertiesで解決 :muscle:



使い方 (1)


  • 必要なメソッドを実装


    • Extension で実装させてもOK



// 移譲元クラスが R、プロパティの型が T の場合

operator fun getValue(
thisRef: R, property: KProperty<*>): T

// var 宣言したプロパティに使う場合は setValue も必要
operator fun setValue(
thisRef: R, property: KProperty<*>, value: T)



使い方 (2)



  • by キーワードで移譲するインスタンスを設定

class R {

// HogeDelegate が getValueを実装しているとして
val hoge: T by HogeDelegate()

// FugaDelegate が getValue/setValueを実装しているとして
var fuga: T by FugaDelegate()

...



✧\\ ٩( 'ω' )و //✧



今すぐ使える例



標準の lazy

プロパティが初めて呼ばれた時にブロック内が実行される。

以降の呼び出しでは、ブロックで返した値が使われる。

val layoutManager: LinearLayoutManager by lazy {

LinearLayoutManager(this)
}



標準の Map<K, V>

Map<K, V> には getValue のExtensionがあるので、そのまま移譲先になれる。

class Hoge(val map: Map<String, String?>) {

// map["fuga"] の値が返る
val fuga: String? by map

...

MutableMap<K, V> には setValue もあるので、 var の移譲先になれる。



Kotter Knife

ButterKnifeをKotlinらしく実現した、Jake神謹製ライブラリ

val userName: EditText by bindView(R.id.user_name)

ktファイル1つというシンプルさなので、Delegated Propertyの勉強にも最適。



応用



Intent, Bundleからの取得

class IntentStringDelegate {

operator fun getValue(
thisRef: Activity, property: KProperty<*>): String?
= thisRef.intent.getStringExtra(property.name)
}

...

val hoge: String? by IntentStringDelegate()

...

// textView.text = intent.getString("hoge")
textView.text = hoge



Fragmentのリスナー取得 (1)

inline fun <reified T> listenerOfFragment(): ReadOnlyProperty<Fragment, T>

= object : ReadOnlyProperty<Fragment, T> {
private var value: T? = null

override fun getValue(thisRef: Fragment, property: KProperty<*>): T {
if (value == null)
value = if (thisRef.parentFragment != null && thisRef.parentFragment is T) {
thisRef.parentFragment as T
} else {
try {
thisRef.context as T
} catch (e: ClassCastException) {
throw ClassCastException("${thisRef.context.toString()} or parent Fragment must implement ${T::class.simpleName}")
}
}
@Suppress("UNCHECKED_CAST")
return value as T
}
}



Fragmentのリスナー取得 (2)

private val loginListener: LoginListener

by listenerOfFragment()

...

loginListener.login(id, password)



遊んでみる



おみくじ

class OmikujiProperty{

private val random = Random()

operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return when(random.nextInt(4)) {
0 -> "大吉"
1 -> "中吉"
2 -> "小吉"
else -> "凶"
}
}
}

class Application {
val omikuji: String by OmikujiProperty()

}
fun main(args: Array<String>) {
val app = Application()
print(app.omikuji)
}

:point_down:

kikuchy omikuji_kotlin $ ./gradlew :run

(略)
大吉
BUILD SUCCESSFUL

Total time: 6.161 secs

kikuchy omikuji_kotlin $ ./gradlew :run
(略)

BUILD SUCCESSFUL

Total time: 6.1 secs

kikuchy omikuji_kotlin $ ./gradlew :run
(略)

BUILD SUCCESSFUL

Total time: 5.993 secs

kikuchy omikuji_kotlin $ ./gradlew :run
(略)
大吉
BUILD SUCCESSFUL

Total time: 5.988 secs

kikuchy omikuji_kotlin $ ./gradlew :run
(略)
小吉
BUILD SUCCESSFUL

Total time: 5.955 secs



Tips


  • 以前は特定インターフェースを継承したオブジェクトが移譲先になれた


    • ReadOnlyProperty

    • ReadWriteProperty




  • getValue / setValue の第一引数 thisRef の型を指定すると、指定したクラスのプロパティにしか使えない。

  • 逆に Any? にすると、どのクラスのプロパティでも使用可能。



質問?



icon_128.png @kikuchy