算譜王におれはなる!!!!

偏りはあると思うけど情報技術全般についてマイペースに書くよ。

KotlinでFragmentのargumentsをいい感じに読み取りたい

夢と魔法の待ち時間というAndroidアプリを趣味で作っていて、これを100% Kotlinに移行するためここ最近ごりごりコードを書います。

1つのフラグメントで両パーク(陸と海)に対応するようにしていて、そんな感じのフラグメントがいくつかあります。 例えばアトラクション一覧のフラグメントとか、グリーティング一覧のフラグメントとか。 どちらのパークかはargumentsで与えられるという寸法です。

class AttractionFragment: Fragment() {
    val park: Park
    get() = arguments.getSerializable("park") as Park

    // 省略
}
class GreetingFragment: Fragment() {
    val park: Park
    get() = arguments.getSerializable("park") as Park

    // 省略
}

だいたいこんな感じでparkの部分が完全に一致。 すっきり記述できる方法はないかなーと考えました(BaseFragmentとかの導入は却下)。

ScalaのSelf Typeみたいなやつ (失敗)

失敗というか既に廃止された言語機能です。 次のコードのように、インタフェースが自分の実装されるクラスを指定できる感じです。 WithParkインタフェースはFragmentを要求しているので、自分の中でFragmentargumentsにアクセスできます。

interface WithPark: Fragment {
    val park: Park
    get() = arguments.getSerializable("park") as Park
}

class AttractionFragment: Fragment(), WithPark {
    fun とあるメソッド() {
        println(park) // パークをargumentsから取れる
    }
}

インタフェースだけで頑張る (成功)

話の順序的に真ん中に成功例を載せます。

argumentsを抽象プロパティとして提供するインタフェースにすれば、それっぽくなります。

interface WithPark {
    fun getArguments(): Bundle

    val park: Park
    get() = getArguments().getSerializable("park") as Park
}

class AttractionFragment: Fragment(), WithPark {
    fun とあるメソッド() {
        println(park) // パークをargumentsから取れる
    }
}

問題があるとすればFragment以外がWithParkになれる点です。 いや、こっちの方がより抽象的でいいのかな。でもスコープというか使用できる範囲は狭いほうがいいような。。

拡張プロパティで頑張る (失敗)

最後にまた失敗例です。

今回はWithParkインタフェースは、拡張プロパティのための印です。

interface WithPark

val <T> T.park: String where T: Fragment, T: WithPark
get() = argument.getString("park") as Park

class AttractionFragment: Fragment(), WithPark {
    fun とあるメソッド() {
        println(park) // なぜかここでコンパイルエラー
    }
}

拡張プロパティparkは、FragmentかつWithParkな型をレシーバに取ります。 これにより最初に挙げた例のSelf Typeのようなことができます。 WithParkは誰でも実装できますが、それがFragment以外だとparkプロパティを呼び出せません。

しかし何故か呼び出し箇所でコンパイルエラーになってしまいます。 数日前に同じ問題に直面した人がチケット上げてました。

https://youtrack.jetbrains.com/issue/KT-9630

(僕の中での)結論

3番目の方法ができるのを待って、それまでは2番目の方法で凌ぐ。