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

Compose迁移BottomSheet:ViewModel自定义生命周期所有者配置疑问

迁移BottomSheet到Compose的ViewModel所有者配置问题

我正尝试将BottomSheet配置迁移为使用Compose的ModalBottomSheet实现,但存在以下疑问:

  1. 当前ViewModel的视图存储所有者为Fragment本身,如何配置使其使用不同的ViewModel所有者?
  2. 为支持该使用场景需要进行哪些修改?

原有配置代码

BottomSheet Dialog Fragment

class MyBottomSheet: BottomSheetDialogFragment() {

   @Inject
   lateinit var viewModelFactoryProvider: MyViewModelFactoryProvider

   private val myViewModel: MyViewModel by viewModels { viewModelFactoryProvider }

   override fun onCreateView(
       inflater: LayoutInflater, viewGroup: ViewGroup?, bundle: Bundle?
   ): View {
      val binding = DataBindingUtil.inflate<MyBottomSheetBinding>(
          inflater, R.layout.my_bottom_sheet, null, false
      )
      myViewModel.myResponseLiveData.observe(viewModelLifecycleOwner) { myResponse ->
        binding.response = myResponse
        binding.executePendingBindings()
      }
      return binding.root
   }

   companion object {
        @JvmStatic
        fun newInstance(myParams: MyParams): MyBottomSheet =
            MyBottomSheet().apply {
                arguments = bundleOf("myParams" to myParams)
            }
   }
}

数据类定义

@Parcelize
data class MyParams(
   // 参数定义
): Parcelable // 修正拼写错误:Parceable → Parcelable

ViewModel实现

class MyViewModel @Inject constructor(
     @FragmentArgumentsQualifier private val bundle: Bundle, // Fragment arguments
     private val myRepository: MyRepository
): ViewModel() {
       init {
          invokeRepository()
       }

      private val _myResponseLiveData = MutableLiveData<Response>()
      val myResponseLiveData: LiveData<Response> // 修正为public,供外部观察
          get() = _myResponseLiveData

      fun invokeRepository() {
         val myParams = bundle.getParcelable("myParams", MyParams::class.java) ?: return
         viewModelScope.launch(Dispatchers.IO) {
             myRepository.fetchData(myParams).collect {
                 _myResponseLiveData.postValue(it) // 用postValue在IO线程更新LiveData
             }
         }
      }
}

调用Fragment的Activity

class MyActivity: AppCompatActivity() {

     override fun onCreate(savedInstance: Bundle?) {
         AndroidInjection.inject(this) // 注入依赖
         val binding = DataBindingUtil.bind<MyActivityBinding>(findViewById(R.id.my_activity))
         val currentFragment = supportFragmentManager.findFragmentByTag(BOTTOM_SHEET_TAG) as? MyBottomSheet
         if (currentFragment?.isVisible != true) {
             supportFragmentManager.commit {
                 add(R.id.fragmentContainer, MyBottomSheet.newInstance(MyParams()), BOTTOM_SHEET_TAG)
                 setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
                 addToBackStack("bottomSheet")
             }
         }
         setContentView(binding.root)
     }
}

问题解答

1. 配置不同的ViewModel所有者

在Compose中,viewModel()函数默认使用当前Compose节点的ViewModelStoreOwner(通常是宿主Activity或Fragment)。要指定不同的所有者,只需在调用viewModel()时传入viewModelStoreOwner参数:

  • 如果要与宿主Activity共享ViewModel:直接使用LocalContext.current as ComponentActivity作为所有者
  • 如果要与父Fragment共享ViewModel:在Fragment的Compose树中,LocalViewModelStoreOwner.current就是父Fragment实例
  • 如果需要自定义ViewModelStoreOwner:实现ViewModelStoreOwner接口,管理自己的ViewModelStore

2. 所需修改步骤及示例代码

步骤1:迁移到Compose的ModalBottomSheet

替换原BottomSheetDialogFragment为Compose组件,使用ModalBottomSheet和ModalBottomSheetState控制显示状态。

步骤2:调整ViewModel获取逻辑

根据需求指定ViewModel所有者,同时保留依赖注入逻辑。

步骤3:替换DataBinding为Compose状态收集

用collectAsState()替代LiveData的observe方法,将数据流转换为Compose状态。

示例修改后的代码
宿主Activity的Compose实现
class MyActivity : AppCompatActivity() {
    @Inject
    lateinit var viewModelFactoryProvider: MyViewModelFactoryProvider

    override fun onCreate(savedInstanceState: Bundle?) {
        AndroidInjection.inject(this)
        setContent {
            val bottomSheetState = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden)
            val scope = rememberCoroutineScope()

            // 示例:使用Activity作为ViewModel所有者
            val myViewModel: MyViewModel = viewModel(
                viewModelStoreOwner = this,
                factory = viewModelFactoryProvider
            )

            MyActivityScreen(
                onShowBottomSheet = {
                    // 传入参数并显示BottomSheet
                    myViewModel.setParams(MyParams(...))
                    scope.launch { bottomSheetState.show() }
                }
            )

            // ModalBottomSheet实现
            ModalBottomSheet(
                sheetState = bottomSheetState,
                onDismissRequest = {
                    scope.launch { bottomSheetState.hide() }
                }
            ) {
                // BottomSheet内容,直接收集ViewModel的状态
                val response by myViewModel.myResponseLiveData.collectAsState()
                MyBottomSheetContent(response)
            }
        }
    }
}

@Composable
fun MyActivityScreen(onShowBottomSheet: () -> Unit) {
    Button(onClick = onShowBottomSheet) {
        Text("显示BottomSheet")
    }
}

@Composable
fun MyBottomSheetContent(response: Response?) {
    // 渲染BottomSheet内容,根据response更新UI
    Column(modifier = Modifier.padding(16.dp)) {
        response?.let {
            Text("数据:${it.toString()}")
        } ?: Text("加载中...")
    }
}
步骤4:调整ViewModel参数传递(可选)

如果不再依赖Fragment的arguments,可以改用SavedStateHandle传递参数,更符合Compose+ViewModel的最佳实践:

class MyViewModel @Inject constructor(
    private val savedStateHandle: SavedStateHandle,
    private val myRepository: MyRepository
): ViewModel() {
    init {
        invokeRepository()
    }

    private val _myResponseLiveData = MutableLiveData<Response>()
    val myResponseLiveData: LiveData<Response> get() = _myResponseLiveData

    fun invokeRepository() {
        val myParams = savedStateHandle.get<MyParams>("myParams") ?: return
        viewModelScope.launch(Dispatchers.IO) {
            myRepository.fetchData(myParams).collect {
                _myResponseLiveData.postValue(it)
            }
        }
    }

    // 提供设置参数的方法
    fun setParams(params: MyParams) {
        savedStateHandle.set("myParams", params)
        invokeRepository()
    }
}

内容的提问来源于stack exchange,提问作者Kartik

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.12 14:25:12