You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在两个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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 07:30:51