id:kikuchy です。
ここ半年ほどはiOSの開発をしていて、そこで一緒に仕事をしている id:devorgachem から良いアーキテクチャを教えてもらったので、それをAndroid開発に適用する方法をお話させていただきました。
とは言え、難しいことではありません。
スライド中でお見せしたサンプルのリポジトリはこちら。
要は
Activity/Fragmentにフラグを書くのをやめて、画面がとり得る状態をちゃんと列挙して管理しましょう、というお話です。
もっと言うと
状態を専用クラス(モデル)で管理し、Activity/Fragmentから切り離すととても便利です。
とり得る状態と状態遷移の仕方がわかっているので、
- 所定の操作をした時に、所望の状態になっているか
- 所定の操作をした時に、意図しない状態に陥っていないか
だけ確認できればテストは最低限のテストはできますし(Viewとかは別問題ですからね!)、一部のモデルを再利用することも可能です。
特に、APIで得られたデータを加工して表示するのが主な責務になっているアプリですと、通信状態 ≒ 画面の状態になりがちなので、通信状況を管理できるとコードの見通しが良くなります。
頂いた質問など
発表後にいくつかご質問をいただいだきましたので、共有させていただきます。
Kotlinのsealed classを使用しているのはなぜ?(enumは使わないの?)
一言で言えば、SwiftのAssociated Values付きenumと同じことをしたかったから、です。
Kotlinには特定スコープ内でのみ継承が可能になるsealed classというクラスがあります。
アプリケーションの状態にはデータを付随させたいことが多々あります。
(例えば、「通信完了」状態には、通信の結果得られたレスポンスor失敗の原因が付随するはずです。)
やりたいことは状態を列挙して宣言しておくことなので、たしかにenumでも事足りそうな気がします。
が、JavaやKotlinにおけるenumはプログラム起動時にすべてのインスタンスが生成されてしまう(シングルトンになっている)ので、状態ごとに違う型のデータを付随させることが難しいのです。
(値をNullableで取り扱うことを許容すれば実現可能だと思います)
sealed classとdata class、それに加えてwhen式を使えば、Swiftのenumと同じことを実現できます。
data classを使えばequals()も自動的に用意されるので、テスト時の比較も簡単でSwiftよりも便利です!
LiveDataを使っている理由は?(もしくは、RxLifecycleの使用を勧めている理由は?)
モデルの生存期間がActivity/Fragmentよりも長くなりえることを想定しているためです。
(必ずしも、長くする必要があるわけではありません)
状態を抱えているモデルの生存期間は、Activity/Fragmentのライフサイクルとは独立しています。
特にDIツール(Dagger2など)を使ってシングルトンとして生成されたモデルのインスタンスなどは、Activity/Fragmentより長く生きることになります。
するとよく言われている通り、onNextのタイミングでViewが居なくなっていてNPEを引き起こしたり、Subscriberがリークする原因となるので、適宜、モデルからの通知を止めてやる必要があります。
そのために用意されているのがLiveDataなりRxLifecycleなので、それの使用をおすすめしているという訳です。
ちなみに
モデル部は通常のJava Libraryとして作れるのでJVM上でテスト可能です。(サンプルではそうしています)
テストが速攻で終わって便利です。
Diverseはこれからも新しい提案や試みを発表してゆきます°˖✧◝(⁰▿⁰)◜✧˖°