Delegated Properties で遊ぼう（スライド版）

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

# だれ

* ![icon_128.png](https://qiita-image-store.s3.amazonaws.com/0/41896/61f2aebd-92e0-0145-a44a-da8e4aed010f.png) @kikuchy
* 株式会社ミクシィ -> 株式会社Diverse
* Android書いてるおじさん


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

:point_right: [3分で作る Kotlin Friendly な API](http://www.slideshare.net/HiroshiKikuchi/3-kotlin-friendly-api-59982998)

----

# Delegated Propertyとは？

* クラスのプロパティ（メンバ）のsetter/getter処理を別のオブジェクトに移譲する機能
* 特定のメソッドさえ実装していれば、移譲先オブジェクトになれる

----


# 何がうれしいの？


----

# 遅延処理ができる！！！！！！

----


```kotlin
// Activityの中

lateinit var hoge:String

...

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

コンストラクタで初期化できないばっかりに、
`val` の恩恵を受けられない！ :anger: :anger: :anger: 

（`lateinit` は `var` のみ使用可能）

----

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


----

```kotlin
// 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

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

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

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

----

# 使い方 (2)

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

```kotlin
class R {
    // HogeDelegate が getValueを実装しているとして
    val hoge: T by HogeDelegate()

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

...
```

----

# ✧＼\ ٩( 'ω' )و /／✧


----

# 今すぐ使える例

----

# 標準の `lazy`

プロパティが初めて呼ばれた時にブロック内が実行される。
以降の呼び出しでは、ブロックで返した値が使われる。

```kotlin
val layoutManager: LinearLayoutManager by lazy {
    LinearLayoutManager(this)
}
```

----

# 標準の `Map<K, V>`

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

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

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

...
```

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

----

# [Kotter Knife](https://github.com/JakeWharton/kotterknife)

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

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

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

----

# 応用

----

# Intent, Bundleからの取得

```kotlin
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)

```kotlin
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)

```kotlin
private val loginListener: LoginListener
        by listenerOfFragment()

...

loginListener.login(id, password)
```

----

# 遊んでみる

----

# おみくじ

```kotlin
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](https://qiita-image-store.s3.amazonaws.com/0/41896/61f2aebd-92e0-0145-a44a-da8e4aed010f.png) @kikuchy