Compose迁移BottomSheet:ViewModel自定义生命周期所有者配置疑问
迁移BottomSheet到Compose的ViewModel所有者配置问题
我正尝试将BottomSheet配置迁移为使用Compose的ModalBottomSheet实现,但存在以下疑问:
- 当前ViewModel的视图存储所有者为Fragment本身,如何配置使其使用不同的ViewModel所有者?
- 为支持该使用场景需要进行哪些修改?
原有配置代码
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
相关产品推荐
相关产品推荐

