如何在两个ViewModel间传递数据?Fragment与BottomSheet场景实现
用两个ViewModel实现需求的方案
完全可以通过两个职责单一的ViewModel实现你的需求,核心是解决两个ViewModel之间的通信问题,下面是具体实现思路和代码示例:
1. 明确职责划分
- FragmentViewModel:仅负责维护Fragment的UI状态(两个选中的选项),并提供给BottomSheet过滤数据的依据,不处理数据加载逻辑。
- BottomSheetViewModel:负责加载原始数据、根据过滤条件生成可选列表,以及处理用户选中事件,不直接操作Fragment的UI。
2. 用Flow实现ViewModel间通信
使用Jetpack的SharedFlow作为事件传递的载体,实现BottomSheetViewModel向FragmentViewModel传递选中结果。
步骤1:定义基础数据类和枚举
// 选项数据类,根据业务自定义 data class Option(val id: String, val name: String, val parentId: String?) // 标记当前是为哪个TextView选择选项 enum class SelectionTarget { FIRST_OPTION, SECOND_OPTION } // 选中事件类,携带选中的选项和目标 data class OptionSelectedEvent( val option: Option, val target: SelectionTarget )
步骤2:实现FragmentViewModel
import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch class FragmentViewModel : ViewModel() { // 维护两个选项的选中状态,供Fragment观察更新UI private val _firstSelectedOption = MutableStateFlow<Option?>(null) val firstSelectedOption: StateFlow<Option?> = _firstSelectedOption.asStateFlow() private val _secondSelectedOption = MutableStateFlow<Option?>(null) val secondSelectedOption: StateFlow<Option?> = _secondSelectedOption.asStateFlow() // 暴露给BottomSheetViewModel的过滤依据:第一个选中的选项 val firstOptionForFilter = firstSelectedOption.asStateFlow() // 处理BottomSheet传来的选中事件 fun handleOptionSelected(event: OptionSelectedEvent) { viewModelScope.launch { when (event.target) { SelectionTarget.FIRST_OPTION -> _firstSelectedOption.emit(event.option) SelectionTarget.SECOND_OPTION -> _secondSelectedOption.emit(event.option) } } } }
步骤3:实现BottomSheetViewModel
import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.launch class BottomSheetViewModel( private val selectionTarget: SelectionTarget, private val firstOptionFlow: StateFlow<Option?> ) : ViewModel() { // 加载原始数据(这里模拟数据,实际从Repository获取) private val rawOptions = listOf( Option("1", "选项A", null), Option("2", "选项B", null), Option("3", "子选项A1", "1"), Option("4", "子选项A2", "1"), Option("5", "子选项B1", "2") ) // 维护当前显示的选项列表 private val _displayOptions = MutableStateFlow<List<Option>>(emptyList()) val displayOptions: StateFlow<List<Option>> = _displayOptions.asStateFlow() // 发出选中事件的Flow private val _optionSelectedEvent = MutableSharedFlow<OptionSelectedEvent>() val optionSelectedEvent: SharedFlow<OptionSelectedEvent> = _optionSelectedEvent.asSharedFlow() init { // 根据目标和过滤条件生成显示列表 viewModelScope.launch { if (selectionTarget == SelectionTarget.SECOND_OPTION) { // 第二个选项需要根据第一个选中项过滤 combine(firstOptionFlow) { firstOption -> firstOption.firstOrNull()?.let { parent -> rawOptions.filter { it.parentId == parent.id } } ?: emptyList() }.collect { filteredOptions -> _displayOptions.emit(filteredOptions) } } else { // 第一个选项显示所有顶级选项 val topOptions = rawOptions.filter { it.parentId == null } _displayOptions.emit(topOptions) } } } // 用户选中选项时调用,发出事件 fun onOptionSelected(option: Option) { viewModelScope.launch { _optionSelectedEvent.emit( OptionSelectedEvent(option, selectionTarget) ) } } }
步骤4:Fragment中关联两个ViewModel
import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope import kotlinx.coroutines.launch class MainFragment : Fragment() { private lateinit var fragmentViewModel: FragmentViewModel override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { fragmentViewModel = ViewModelProvider(this)[FragmentViewModel::class.java] val view = inflater.inflate(R.layout.fragment_main, container, false) // 第一个TextView点击事件 view.findViewById<TextView>(R.id.tv_first).setOnClickListener { showBottomSheet(SelectionTarget.FIRST_OPTION) } // 第二个TextView点击事件 view.findViewById<TextView>(R.id.tv_second).setOnClickListener { showBottomSheet(SelectionTarget.SECOND_OPTION) } // 观察选中状态,更新UI lifecycleScope.launch { fragmentViewModel.firstSelectedOption.collect { option -> view.findViewById<TextView>(R.id.tv_first).text = option?.name ?: "请选择第一个选项" } } lifecycleScope.launch { fragmentViewModel.secondSelectedOption.collect { option -> view.findViewById<TextView>(R.id.tv_second).text = option?.name ?: "请选择第二个选项" } } return view } private fun showBottomSheet(target: SelectionTarget) { // 创建BottomSheetViewModel,传入目标和过滤Flow val sheetViewModel = ViewModelProvider( this, BottomSheetViewModelFactory(target, fragmentViewModel.firstOptionForFilter) )[BottomSheetViewModel::class.java] // 创建并显示BottomSheet Dialog(这里用自定义BottomSheetDialog示例) val bottomSheet = CustomBottomSheetDialog(requireContext()).apply { setViewModel(sheetViewModel) show() } // 收集BottomSheet的选中事件,交给FragmentViewModel处理 lifecycleScope.launch { sheetViewModel.optionSelectedEvent.collect { event -> fragmentViewModel.handleOptionSelected(event) bottomSheet.dismiss() } } } } // 自定义ViewModelFactory,用来传递参数给BottomSheetViewModel class BottomSheetViewModelFactory( private val target: SelectionTarget, private val firstOptionFlow: StateFlow<Option?> ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun <T : ViewModel> create(modelClass: Class<T>): T { return BottomSheetViewModel(target, firstOptionFlow) as T } }
3. 替代方案:使用回调传递结果
如果你觉得Flow太复杂,也可以通过回调接口实现通信:
- 在BottomSheetViewModel初始化时传入一个回调函数
(OptionSelectedEvent) -> Unit - 用户选中选项时直接调用该回调,Fragment中在创建ViewModel时传入回调,将事件交给FragmentViewModel处理
这种方式更简洁,但Flow的方式更符合响应式编程的规范,也更容易处理生命周期问题。
内容的提问来源于stack exchange,提问作者Khalida Aliyeva
相关产品推荐
相关产品推荐

