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

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

献立詳細画面で使っていたFlowの処理をtransformオペレータで書き直した日記

こんにちは、AndroidエンジニアなのかFlutterエンジニアなのか蕎麦屋なのか分からない男、そば屋です。

献立機能を強化しよう!と言うことでこれまでWeb(WebView)限定となっていた「献立検索」を ネイティブ実装しました。

するとどうでしょう、前回の記事に書いた献立詳細画面に もう一パターン別のAPI呼び出しが入ると言うではありませんか

前回同様に

    private val menuResponse = repository.getMenu(menuId).stateIn(viewModelScope, SharingStarted.Lazily, null)
    private val aiResponse = repository.getAiMenu(menuId).stateIn(viewModelScope, SharingStarted.Lazily, null)
    private val searchResponse = repository.getSearchMenu(menuId).stateIn(viewModelScope, SharingStarted.Lazily, null)

のようにSharingStarted.Lazilyにしてしまい。

購読側で

when (献立のタイプ) {
    自分で作った -> 元からの処理(menuResponseを購読)
    AI -> 前回作った処理(aiResponseを購読)
    献立検索 -> 今回作る処理(searchResponseを購読)
}

とすれば別に動作に問題は起こりません

が、購読側のUI処理でEpoxyのControllerにデータを渡すのに 一度、3個に分かれて購読処理を呼び、再度共通のUI処理を呼ぶとエレガントじゃない気がします。

さらにパターンが今後も増える不安を感じる事も事実としてあります。

ふと、DroidKaigiにFlowでセッション応募したことで再勉強を始めた時に書いた記事に transformオペレータと言うものがあった事を思い出しました。これで勝つる

ViewModelの実装をtransformオペレータを使って書き直す!

Before

class ViewModel(val menuId: Int) : ViewModel(), KoinComponent {
    private val repository: Repository by inject()
    private val menuResponse = repository.getMenu(menuId).stateIn(viewModelScope, SharingStarted.Lazily, null)
    private val aiResponse = repository.getAiMenu(menuId).stateIn(viewModelScope, SharingStarted.Lazily, null)
}

After

class ViewModel(val menuId: Int) : ViewModel(), KoinComponent {
    private val repository: Repository by inject()
    private val menuType = MutableStateFlow<MenuType?>(null)

    val menuResponse: StateFlow<Response<MenuResponse>?> = menuType.transform { value ->
        val respnse = when (value) {
            MenuType.AI -> menuRepository.getAiMenu(menuId)
            MenuType.MENU_SEARCH -> menuRepository.getSearchMenu(menuId)
            MenuType.MINE -> menuRepository.getMenu(menuId)
            else -> null
        }
        emit(respnse)
    }.stateIn(viewModelScope, SharingStarted.Eagerly, null)

    fun setMenuType(menuType: MenuType) {
        menuType.value = menuType
    }
}
  • enumで献立の種類を表すMenuTypeを新設
  • MenuTypeをセットするStateFlowであるmenuTypeを新設
  • menuTypeが変更された時に実行されるようにtransformオペレータをセット
  • 献立の種類(MenuType)によって呼び出すAPIを切り替えて結果をemit

Fragment

viewModel.setMenuType(MenuType.AI)

viewModel.menuResponse.flowWithLifecycle(viewLifecycleOwner.lifecycle).onEach {
    // UI処理
}.launchIn(viewLifecycleOwner.lifecycleScope)

FragmentはSafeArgsで受け取った献立の種類をViewModelに教えてあげるだけで、 勝手に適当なAPIを呼び出して結果を返してもらえるので、購読先の分岐もなくなりスリムになりました。

最後に

完全にオシャレかつエレガントに作ることができました。 少し(かなり?)背伸び気味にDroidKaigiのセッションに応募し、 焦って学び直した結果生まれたコードなので学ぶと結果は返ってくると言う証明になりました。 ※セッションは不採択となってしまいましたが、勉強をしなおすきっかけになったので良い経験でした。

筋肉は裏切らない