Slide 1

Slide 1 text

イベントをどう管理するか Shibuya.apk #50 mikan(一瀬喜弘)

Slide 2

Slide 2 text

自己紹介 object Mikan { val name = 一瀬喜弘 val from = 長崎 val company = カラビナテクノロジー株式会社 val work = Engineer.Android val volunteer = DroidKaigi val hobby = listOf( "漫画", "アニメ", "ゲーム", "折り紙", "OSS開発・コントリビュート", ) }

Slide 3

Slide 3 text

今日お話しする内容

Slide 4

Slide 4 text

Channel vs. SharedFlow vs. StateFlow エラーや​ 通信結果などの​ イベントを​ どこで​ 管理するのか

Slide 5

Slide 5 text

結論 エラーや​ 通信結果などの​ イベントを​ どこで​ 管理すべきか?

Slide 6

Slide 6 text

それぞれの​ メリデメを​ 把握したうえで​ 適当な​ ものを​ 選んでください

Slide 7

Slide 7 text

どれを​ 使うかは​ 所詮手段でしかないです 取り扱おうと​ している​ イベントの​ 重要度だったり、​ どう​ ハンドリングされるかを​ きちんと​ 把握しないと​ 適切な​ ホルダーは​ 選べません

Slide 8

Slide 8 text

これから​ メリデメを​ 紹介するので、​ 適した​ 手段を​ 検討できるようになって​ もらえれば​ 幸いです

Slide 9

Slide 9 text

イベントとは

Slide 10

Slide 10 text

イベントとは ユーザー操作によってUI側から発生し、ViewModelに 伝達したい情報

Slide 11

Slide 11 text

No content

Slide 12

Slide 12 text

けど​ ViewModelから​ UIに​ イベントを​ 送信したい​ ときも​ ありますよね

Slide 13

Slide 13 text

ex. 1. 通信が完了したら画面遷移する 2. 通信が失敗したらエラーダイアログを表示する 3. SharedPreferencesからデータを削除したらトーストを表示す る

Slide 14

Slide 14 text

No content

Slide 15

Slide 15 text

ViewModelから​ UIに​ イベントを​ 伝達する​ ためにはなにかしら​ データホルダーが​ 必要に​ なる

Slide 16

Slide 16 text

Channel? SharedFlow? StateFlow? 戦争が始まる

Slide 17

Slide 17 text

それぞれについて実装方法をみ ていきます

Slide 18

Slide 18 text

Channel データの持ち方 イベントの発火 // ViewModel private val _message = Channel() val message = _message.receiveAsFlow() viewModelScope.launch { _message.send("message...") }

Slide 19

Slide 19 text

Channel イベントの消費 // Fragment viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.message.collect { showSnackBar(it) } } }

Slide 20

Slide 20 text

Channel イベントの消費 // Compose val lifecycle = LocalLifecycleOwner.current.lifecycle LaunchedEffect(Unit) { lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.message.collect { showSnackBar(it) } } }

Slide 21

Slide 21 text

Channel メリット collectorがいない間に発生したイベントをバッファリングできる(demo) デメリット イベントの​ 消費中に​ coroutine が​ キャンセルされたら​ どうしますか?​ (demo)

Slide 22

Slide 22 text

Channel メリット collectorがいない間に発生したイベントをバッファリングできる(demo) デメリット イベントの​ 消費中に​ coroutine が​ キャンセルされたら​ どうしますか?​ (demo) → バッファリングの挙動とバックグラウンド時のキャンセルが絡み合うとイベン トハンドリングの振る舞いがどんどん予測困難になる

Slide 23

Slide 23 text

SharedFlow データの持ち方 イベントの発火 // ViewModel private val _message = MutableSharedFlow() val message = _message.asSharedFlow() viewModelScope.launch { _message.emit("message...") }

Slide 24

Slide 24 text

SharedFlow イベントの消費 Channelのときと同じ // Fragment viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.message.collect { showSnackBar(it) } } }

Slide 25

Slide 25 text

SharedFlow イベントの消費 Channelのときと同じ // Compose val lifecycle = LocalLifecycleOwner.current.lifecycle LaunchedEffect(Unit) { lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.message.collect { showSnackBar(it) } } }

Slide 26

Slide 26 text

SharedFlow メリット デメリットに目を瞑れば、シンプルにイベントを実装できる デメリット collectorがいないとイベントは垂れ流されてロストする(demo) ex. 構成変更が発生するとUIが再構成されるので一瞬collectorがいなくなっ て、その間に発生したイベントはロストする イベントを発火するときに、subscriptionCountが1以上になっていること をチェックすることで回避可能だが。 。 replayCacheをいくらか設けて回避するという手もある

Slide 27

Slide 27 text

SharedFlow メリットともデメリットとも取れる collectorが複数いた場合に、1つのイベントが同時に複数回消費されることに なる(demo) ハンドリングが同じ場合は期待しない挙動になる可能性がある ハンドリングが異なる場合は有用な可能性がある このことを考慮して設計してあるならヨシ → Channelだと複数のcollectorに同じ情報を伝達できない

Slide 28

Slide 28 text

StateFlow データの持ち方 イベントの発火 // ViewModel // 初期値が必要だがイベントに初期値などないのでnullで表現する private val _message = MutableStateFlow(null) val message = _message.asStateFlow() _message.value = "message..."

Slide 29

Slide 29 text

StateFlow // Fragment viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.message.collect { if (it != null) { showSnackBar(it) // イベントをハンドリングしたあとは明示的に消費する viewModel.consumeMessage() } } } } // ViewModel fun consumeMessage() { _message.value = null }

Slide 30

Slide 30 text

StateFlow // Compose val message by viewModel.message.collectAsStateWithLifecycle() LaunchedEffect(message) { message?.let { onMessageReceive(it) viewModel.consumeMessage() } }

Slide 31

Slide 31 text

StateFlow メリット イベント処理中にcollectorがキャンセルされてたとしても、復帰後に最新のイ ベントは残っているので再ハンドリング可能 デメリット イベントの消費をプログラミングしないといけない 明示的にイベントを消し込み イベントを消費したとは? 論理的には消費したが、物理的に消費してない状態が成立する(demo)

Slide 32

Slide 32 text

まとめ Channel ⭕ トーストにメッセージを表示するなどの単純なイベントに向いている ⭕ collect前にイベントが発火する可能性がある場合に向いている ❌ イベントが消失する可能性があるので、重要なイベントには向いてない SharedFlow ⭕ トーストにメッセージを表示するなどの単純なイベントに向いている ⭕ バッファリングとか考えなくてよいのでシンプル ❌ イベントが消失する可能性があるので、重要なイベントには向いてない StateFlow ❌ 単純なイベントについては過度 ⭕ イベントの処理を保証しないといけない場合は向いている ⭕ 重要なイベントに向いている

Slide 33

Slide 33 text

ご清聴ありがとうございました