基于NowInAndroid架构:如何从AppState向ModalBottomSheet传语音文本?
解决方案(遵循Clean Architecture与Compose规范)
1. 调整ViewModel,管理朗读文本状态
在UniversalisViewModel中添加状态流保存当前朗读文本,并提供更新方法,符合UI层状态管理的职责:
@HiltViewModel class UniversalisViewModel @Inject constructor( private val savedStateHandle: SavedStateHandle, val userDataRepository: UserDataRepository, getTopicWithDate: GetUniversalisUseCase, ) : ViewModel() { // 保存当前需朗读的文本,对外暴露只读流 private val _currentVoiceText = MutableStateFlow("") val currentVoiceText: StateFlow<String> = _currentVoiceText.asStateFlow() // 更新朗读文本的方法 fun updateCurrentVoiceText(text: String) { _currentVoiceText.value = text } // 原有uiState等代码保持不变 }
2. 修改Universalis可组合项,同步文本到ViewModel
在Universalis可组合项中,利用LaunchedEffect监听data变化,将getAllForRead()的结果同步到ViewModel:
@Composable fun Universalis( data: Universalis, topicId: String?, userData: UserDataDynamic, viewModel: UniversalisViewModel // 传入ViewModel ) { val itemId = topicId!!.toInt() val onTap = { point: Offset -> } if (data.liturgia!!.liturgiaTypus is Sortable) { data.liturgia!!.liturgiaTypus?.sort() } // 当data实例变化时,自动更新ViewModel中的朗读文本 LaunchedEffect(data) { viewModel.updateCurrentVoiceText(data.getAllForRead()) } // 原有UI渲染代码保持不变 when (itemId) { 1 -> MixtusScreen( data = data.liturgia!!.liturgiaTypus as LHMixtus, calendarTime = data.timeFK, userData = userData, onTap = onTap ) 2 -> OfficiumScreen( data = data.liturgia!!.liturgiaTypus as LHOfficium, calendarTime = data.timeFK, userData = userData, onTap = onTap ) } }
3. 连接AppState与ViewModel状态,控制BottomSheet
在顶层组合中,订阅ViewModel的朗读文本状态,同步到AppState,并处理顶部栏点击逻辑:
@Composable fun UniversalisScreen( viewModel: UniversalisViewModel = hiltViewModel(), appState: AppState = rememberAppState() ) { // 订阅ViewModel中的朗读文本 val currentVoiceText by viewModel.currentVoiceText.collectAsState() // 同步文本到AppState LaunchedEffect(currentVoiceText) { appState.textToRead = currentVoiceText } // 顶部栏按钮逻辑 NiaTopAppBar( // ... 其他配置 readerIcon = NiaIcons.Reader, onReaderClick = { appState.showBottomSheet = true // 此时appState.textToRead已同步为当前页面的朗读文本 }, ) // 展示ModalBottomSheet if (appState.showBottomSheet) { ModalBottomSheet(onDismissRequest = { appState.showBottomSheet = false }) { TextToSpeechScreen(appState.textToRead) } } // 渲染Universalis内容 val uiState by viewModel.uiState.collectAsState() when (uiState) { is UniversalisUiState.UniversalisData -> { val uiData = uiState as UniversalisUiState.UniversalisData // 根据selectedTopicId筛选当前显示的Universalis实例 val currentUniversalis = uiData.topics.firstOrNull { it.id == uiData.selectedTopicId?.toInt() } currentUniversalis?.let { Universalis( data = it, topicId = uiData.selectedTopicId, userData = // 从userDataRepository获取用户数据 viewModel = viewModel ) } } UniversalisUiState.Loading -> LoadingScreen() UniversalisUiState.Error -> ErrorScreen() UniversalisUiState.Empty -> EmptyScreen() } }
规范说明
- Clean Architecture:ViewModel作为UI层状态中枢,负责接收UI层的文本更新请求并维护状态,业务方法
getAllForRead()属于领域层逻辑,仅在可组合项中调用后传递给ViewModel,符合单向数据流原则。 - Compose规范:使用
LaunchedEffect处理副作用(同步文本到ViewModel),通过StateFlow和collectAsState实现状态订阅,保持可组合项的无状态性,仅依赖参数传递数据。
内容的提问来源于stack exchange,提问作者A. Cedano
相关产品推荐
相关产品推荐

