おいしい健康 開発者ブログ

株式会社おいしい健康で働くエンジニア・デザイナーが社内の様子をお伝えします。

Android版A.I.からのおすすめ献立を作った時にFlowが便利だった話し

おいしい健康でAndroid/Flutterエンジニアをしているそば屋です。

ここを見てくれてるみなさんならすでに機能を使ってくれている「A.I.からのおすすめ献立」をリリースしました。 機能を実装した時にやっぱりFlow便利だな〜と思ったので紹介します。

画面構成

ホーム画面

f:id:oishi-kenko:20210729120142p:plain:w350
ホーム画面
献立を最大2件提案します。

献立画面

f:id:oishi-kenko:20210729120816p:plain:w350
献立画面(AI)
ホーム画面で選ばれた献立の詳細を見れる画面です。 今回はこちらの画面の実装について書いていきます。

献立画面の実装について

今回のA.I.おすすめ献立を作る前から自身で作った献立を見たり編集するために献立画面は存在していました。 元から処理の流れは

  1. SafeArgsで献立のidを受け取る

  2. APIから献立情報をもらう

  3. 画面表示

となっており、特に面白いことは何もしていません

なので実装としては

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で発表した内容を思い出します。 f:id:oishi-kenko:20210729122538p:plain ※資料はこちらです。

  • SharingStarted.EagerlySharingStarted.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が好きです。