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を要求しているので、自分の中でFragmentのargumentsにアクセスできます。
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番目の方法で凌ぐ。