Android MVI架构下SharedFlow更新数据仅一个生效的问题
问题描述
在采用MVI架构的Android应用中,尝试使用StateFlow(代码中实际使用的是StateFlow而非描述中的SharedFlow)更新数据,应用包含两个Spinner,数据在ViewModel中填充。编写代码后发现仅能更新其中一组数据:
ViewModel代码:
class MyViewModel @Inject constructor(private val repository: DetailRepository) : ViewModel() { private val _state = MutableStateFlow<MyState>(MyState.Idle) val state: StateFlow<MyState> get() = _state fun handleIntent(intent: MyIntent) { when (intent) { is MyIntent.CategoriesList -> fetchingCategoriesList() is MyIntent.PriorityList -> fetchingPrioritiesList() } } private fun fetchingCategoriesList() { val data = mutableListOf(Car, Animal, Color, Food) _state.value = DetailState.CategoriesData(data) } private fun fetchingPrioritiesList() { val data = mutableListOf(Low, Normal, High) _state.value = DetailState.PriorityData(data) } }
Fragment中填充Spinner的代码:
lifecycleScope.launch { //Send viewModel.handleIntent(MyIntent.CategoriesList) viewModel.handleIntent(MyIntent.PriorityList) //Get viewModel.state.collect { state -> when (state) { is DetailState.Idle -> {} is DetailState.CategoriesData -> { categoriesList.addAll(state.categoriesData) categorySpinner.setupListWithAdapter(state.categoriesData) { itItem -> category = itItem } Log.e("DetailLog","1") } is DetailState.PriorityData -> { prioritiesList.addAll(state.prioritiesData) prioritySpinner.setupListWithAdapter(state.prioritiesData) { itItem -> priority = itItem } Log.e("DetailLog","2") } } } }
运行应用时,Logcat仅显示数字2,不会执行is DetailState.CategoriesData分支;但注释掉viewModel.handleIntent(MyIntent.PriorityList)后,Logcat会显示数字1。
问题原因
你使用的StateFlow是状态容器型流,核心特性是始终保留并对外暴露最新的状态值。当连续调用handleIntent(CategoriesList)和handleIntent(PriorityList)时,ViewModel会先后将CategoriesData和PriorityData赋值给_state.value——后者会直接覆盖前者的状态。而collect是从StateFlow当前的最新状态开始接收数据,因此只会收到最后设置的PriorityData,自然只会执行对应分支的日志输出。
解决方案
方案1:重构状态类为单一聚合状态(推荐,符合MVI核心思想)
MVI的状态应该是单一、完整、不可变的,将两个列表的数据合并到同一个状态类中,而非使用互斥的子类:
1. 修改状态类
sealed class MyState { object Idle : MyState() data class Loaded( val categories: List<YourCategoryType>, // 替换为实际数据类型 val priorities: List<YourPriorityType> // 替换为实际数据类型 ) : MyState() }
2. 更新ViewModel逻辑
class MyViewModel @Inject constructor(private val repository: DetailRepository) : ViewModel() { private val _state = MutableStateFlow<MyState>(MyState.Idle) val state: StateFlow<MyState> get() = _state // 一次性加载所有数据 fun loadAllData() { val categories = mutableListOf(Car, Animal, Color, Food) val priorities = mutableListOf(Low, Normal, High) _state.value = MyState.Loaded(categories, priorities) } // 分开触发更新时,通过copy保证状态不可变 fun fetchingCategoriesList() { val categories = mutableListOf(Car, Animal, Color, Food) _state.value = when(val currentState = _state.value) { is MyState.Idle -> MyState.Loaded(categories, emptyList()) is MyState.Loaded -> currentState.copy(categories = categories) } } fun fetchingPrioritiesList() { val priorities = mutableListOf(Low, Normal, High) _state.value = when(val currentState = _state.value) { is MyState.Idle -> MyState.Loaded(emptyList(), priorities) is MyState.Loaded -> currentState.copy(priorities = priorities) } } }
3. 调整Fragment的collect逻辑
lifecycleScope.launch { // 可选择一次性加载或分开触发 viewModel.fetchingCategoriesList() viewModel.fetchingPrioritiesList() // 或者直接调用 viewModel.loadAllData() viewModel.state.collect { state -> when (state) { is MyState.Idle -> {} is MyState.Loaded -> { // 更新分类Spinner categoriesList.addAll(state.categories) categorySpinner.setupListWithAdapter(state.categories) { category = it } Log.e("DetailLog","1") // 更新优先级Spinner prioritiesList.addAll(state.priorities) prioritySpinner.setupListWithAdapter(state.priorities) { priority = it } Log.e("DetailLog","2") } } } }
方案2:用SharedFlow发送一次性事件
如果两个列表的加载属于一次性事件而非持久状态,可以将状态流替换为SharedFlow:
1. 定义事件类与ViewModel逻辑
sealed class MyEvent { data class CategoriesData(val categoriesData: List<YourCategoryType>) : MyEvent() data class PriorityData(val prioritiesData: List<YourPriorityType>) : MyEvent() } class MyViewModel @Inject constructor(private val repository: DetailRepository) : ViewModel() { private val _events = MutableSharedFlow<MyEvent>(replay = 0) val events: SharedFlow<MyEvent> get() = _events fun handleIntent(intent: MyIntent) { when (intent) { is MyIntent.CategoriesList -> fetchingCategoriesList() is MyIntent.PriorityList -> fetchingPrioritiesList() } } private fun fetchingCategoriesList() { val data = mutableListOf(Car, Animal, Color, Food) viewModelScope.launch { _events.emit(MyEvent.CategoriesData(data)) } } private fun fetchingPrioritiesList() { val data = mutableListOf(Low, Normal, High) viewModelScope.launch { _events.emit(MyEvent.PriorityData(data)) } } }
2. 调整Fragment的collect逻辑
lifecycleScope.launch { viewModel.handleIntent(MyIntent.CategoriesList) viewModel.handleIntent(MyIntent.PriorityList) viewModel.events.collect { event -> when (event) { is MyEvent.CategoriesData -> { categoriesList.addAll(event.categoriesData) categorySpinner.setupListWithAdapter(event.categoriesData) { category = it } Log.e("DetailLog","1") } is MyEvent.PriorityData -> { prioritiesList.addAll(event.prioritiesData) prioritySpinner.setupListWithAdapter(event.prioritiesData) { priority = it } Log.e("DetailLog","2") } } } }
内容的提问来源于stack exchange,提问作者Dr.KeyOk
相关产品推荐
相关产品推荐

