そのうちちゃんと記事書きます。
だれ
- @kikuchy
- 株式会社ミクシィ -> 株式会社Diverse
- Android書いてるおじさん
前回、KotlinでDSL作るはなしをした者です。
Delegated Propertyとは?
- クラスのプロパティ(メンバ)のsetter/getter処理を別のオブジェクトに移譲する機能
- 特定のメソッドさえ実装していれば、移譲先オブジェクトになれる
何がうれしいの?
遅延処理ができる!!!!!!
// Activityの中
lateinit var hoge:String
...
override fun onCreate(...) {
// Fragment の arguments についても同様の悩みが
hoge = intent.getStringExtra("hoge") ?: ""
}
コンストラクタで初期化できないばっかりに、
val
の恩恵を受けられない!
(lateinit
は var
のみ使用可能)
ボイラープレートを大幅に減らすことができる!!!
// Fragmentの中
var listener: HogeFragmentActionListener? = null
override onAttach(...) {
if (activity is HogeFragmentActionListener) {
listener = activity
} else {
throw Exception("""
ActivityはHogeFragmentActionListenerを実装してないとだめ
""")
}
}
これFragmentにいちいち書かなきゃダメ????
Delegated Propertiesで解決
使い方 (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)
}
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?
にすると、どのクラスのプロパティでも使用可能。