こんにちは、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のセッションに応募し、 焦って学び直した結果生まれたコードなので学ぶと結果は返ってくると言う証明になりました。 ※セッションは不採択となってしまいましたが、勉強をしなおすきっかけになったので良い経験でした。
筋肉は裏切らない