おいしい健康でAndroid/Flutterエンジニアをしているそば屋です。
ここを見てくれてるみなさんならすでに機能を使ってくれている「A.I.からのおすすめ献立」をリリースしました。 機能を実装した時にやっぱりFlow便利だな〜と思ったので紹介します。
画面構成
ホーム画面
献立を最大2件提案します。
献立画面
ホーム画面で選ばれた献立の詳細を見れる画面です。 今回はこちらの画面の実装について書いていきます。
献立画面の実装について
今回のA.I.おすすめ献立を作る前から自身で作った献立を見たり編集するために献立画面は存在していました。 元から処理の流れは
SafeArgsで献立のidを受け取る
APIから献立情報をもらう
画面表示
となっており、特に面白いことは何もしていません
なので実装としては
ViewModel
class ViewModel(val menuId: Int) : ViewModel(), KoinComponent { private val repository: Repository by inject() private val menuResponse = repository.getMenu(menuId).stateIn(viewModelScope, SharingStarted.Eagerly, null) }
Fragment
viewModel.menuResponse.flowWithLifecycle(viewLifecycleOwner.lifecycle).onEach { // UI処理 }.launchIn(viewLifecycleOwner.lifecycleScope)
このような形になっていました。
実装担当した直後はAI献立かどうかだけ判定して画面の作りを変えればいいだけだと思い込んでいたのですが、 既存の献立取得APIだとA.I.の献立が取特できないと知りビックリです。
- 遷移元によって別のAPIを呼ばなければならない
- できれば栄養価のグラフ表示やレシピのGrid表示は既存の物をそのまま使いたい
- なんかオシャレに書きたい
自分の頭の中で思いついた条件は上記です。
ふと、以前yumemi.apk#3で発表した内容を思い出します。 ※資料はこちらです。
SharingStarted.Eagerly
をSharingStarted.Lazily
にする- 既存の献立とA.I.献立用の両方のFlowを用意しておく
- 購読するのは対象の片方(ついでにmapオペレーターで型を変換してあげる)
とすれば勝つると閃いたわけです。
ViewModel
class ViewModel(val menuId: Int) : ViewModel(), KoinComponent { private val repository: Repository by inject() private val menuResponse = repository.getAiMenu(menuId).stateIn(viewModelScope, SharingStarted.Lazily, null) private val aiResponse = repository.getMenu(menuId).stateIn(viewModelScope, SharingStarted.Lazily, null) }
このようにAIと既存の献立両方を用意 ※SharingStarted.Lazilyなので購読されるまで実行されない
Fragment
if (isAi) { viewModel.menuResponse.flowWithLifecycle(viewLifecycleOwner.lifecycle).onEach { // UI処理 }.launchIn(viewLifecycleOwner.lifecycleScope) } else { viewModel.aiResponse.flowWithLifecycle(viewLifecycleOwner.lifecycle).onEach { // UI処理 }.launchIn(viewLifecycleOwner.lifecycleScope) }
Fragmentで購読する対象を絞ることで無駄なリクエストを発行せず、既存のUI処理を使って実装ができます。
最後に
無理してFlowを使わずcoroutineやRxで取得元を変えても良いと思いますが、 なんとなくオシャレな書き方に見えるので個人的にはFlowが好きです。