Button的OnClick事件无法跳转至其他屏幕问题求助
按钮点击无法跳转至CounterEditScreen的问题排查与解决
我给按钮添加了OnClick事件,想让它跳转至CounterEditScreen,但功能失效。代码逻辑和另一组正常跳转的代码一致,求解决方案。
相关代码
Navigation.kt
@RequiresApi(Build.VERSION_CODES.TIRAMISU) @Composable fun Navigation(navController: NavHostController) { NavHost(navController = navController, startDestination = Routes.TASKSLIST.route) { composable( Routes.TASKSLIST.route ) { TasksListScreen(onNavigate = { navController.navigate(it.route) }) } composable( route = Routes.ADDEDITTASK.route + "?taskId={taskId}", arguments = listOf(navArgument(name = "taskId") { type = NavType.IntType defaultValue = -1 }) ) { AddEditScreen( onPopBackStack = { navController.popBackStack() }) } composable( Routes.COUNTER.route ) { CounterScreen(onNavigate = { navController.navigate(it.route) }) } composable( route = Routes.COUNTEREDIT.route ) { CounterEditScreen( onPopBackStack = { navController.popBackStack() } ) } } }
CounterViewModel.kt
class CounterViewModel : ViewModel() { private val _uiEvent = Channel<UiEvent>() val uiEvent = _uiEvent.receiveAsFlow() fun onEvent(event: CounterEvent) { when (event) { CounterEvent.OnButtonSwitch -> TODO() is CounterEvent.OnCounterNameChange -> TODO() is CounterEvent.OnCounterReset -> { viewModelScope.launch(Dispatchers.IO) { sendUiEvent(UiEvent.Navigate(Routes.COUNTEREDIT.route)) } } is CounterEvent.OnDrawerNavigationClick -> { sendUiEvent(UiEvent.Navigate(event.navigationItem.route)) } } } private fun sendUiEvent(event: UiEvent) { viewModelScope.launch(Dispatchers.IO) { _uiEvent.send(event) } } }
CounterEvent.kt
sealed class CounterEvent { object OnCounterNameChange : CounterEvent() data class OnDrawerNavigationClick(val navigationItem: NavigationItem) : CounterEvent() object OnCounterReset : CounterEvent() object OnButtonSwitch : CounterEvent() }
Counter.kt
@OptIn(ExperimentalMaterial3Api::class) @Composable fun CounterScreen( modifier: Modifier = Modifier, onNavigate: (UiEvent.Navigate) -> Unit, ) { val viewModel = CounterViewModel() val snackbarHostState = remember { SnackbarHostState() } val coroutineScope = rememberCoroutineScope() val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed) var selectedItemIndex by rememberSaveable { mutableIntStateOf(0) } val days by rememberSaveable { mutableIntStateOf(0) } val counterName by rememberSaveable { mutableStateOf("Cold showers") } LaunchedEffect(key1 = true) { viewModel.uiEvent.collectLatest { event -> when (event) { is UiEvent.Navigate -> { onNavigate(event) } else -> Unit } } } ModalNavigationDrawer(drawerContent = { ModalDrawerSheet { items.forEachIndexed { index, item -> Spacer(modifier = Modifier.height(12.dp)) NavigationDrawerItem( label = { Text(item.title) }, selected = index == selectedItemIndex, onClick = { viewModel.onEvent(CounterEvent.OnDrawerNavigationClick(item)) selectedItemIndex = index coroutineScope.launch { drawerState.close() } }, icon = { Icon( imageVector = if (index == selectedItemIndex) { item.selectedIcon } else item.unselectedIcon, contentDescription = item.title ) }, modifier = Modifier.padding(NavigationDrawerItemDefaults.ItemPadding) ) } } }, drawerState = drawerState) { Scaffold( snackbarHost = { SnackbarHost(snackbarHostState) { data -> Snackbar( shape = RoundedShapes.medium, actionColor = MaterialTheme.colorScheme.primary, contentColor = MaterialTheme.colorScheme.background, snackbarData = data ) } }, topBar = { TopAppBar( title = { Text("Day counter") }, colors = TopAppBarDefaults.topAppBarColors( containerColor = MaterialTheme.colorScheme.primary, titleContentColor = MaterialTheme.colorScheme.background ), navigationIcon = { IconButton(onClick = { coroutineScope.launch { drawerState.open() } }) { Icon( imageVector = Icons.Default.Menu, contentDescription = "Menu" ) } } ) }, ) { padding -> Column( horizontalAlignment = Alignment.CenterHorizontally, modifier = modifier .fillMaxSize() .padding(padding) .padding(horizontal = 24.dp) ) { Spacer(modifier = Modifier.height(32.dp)) Text( text = "$counterName\ndays passed: $days", style = MaterialTheme.typography.headlineMedium, modifier = modifier.clickable { viewModel.onEvent(CounterEvent.OnCounterNameChange) }, textAlign = TextAlign.Center ) Spacer(modifier = Modifier.height(32.dp)) Button( onClick = { viewModel.onEvent(CounterEvent.OnButtonSwitch) }, modifier = modifier.fillMaxWidth() ) { Text(text = "Start") } Spacer(modifier = Modifier.height(4.dp)) Button( onClick = { viewModel.onEvent(CounterEvent.OnCounterReset) }, modifier = modifier.fillMaxWidth() ) { Text(text = "Reset") } } } } }
问题排查与解决
核心问题:ViewModel实例化错误
在CounterScreen中,直接通过val viewModel = CounterViewModel()创建ViewModel实例,会导致每次重组时生成新的ViewModel,之前收集的uiEvent流失效,点击按钮发送的事件无法被正确接收。
修正方法:使用Compose的ViewModel API获取实例,确保其在屏幕生命周期内唯一:
// 替换原ViewModel实例化代码 val viewModel = viewModel<CounterViewModel>()
其他优化与检查点
- 路由字符串一致性:确认
Routes.COUNTEREDIT.route的值与Navigation中注册的路由完全一致,无拼写错误。 - 协程调度器简化:ViewModel的
viewModelScope默认使用Dispatchers.Main.immediate,无需指定Dispatchers.IO,可简化发送逻辑:// 修改CounterViewModel中的对应代码 is CounterEvent.OnCounterReset -> { sendUiEvent(UiEvent.Navigate(Routes.COUNTEREDIT.route)) } private fun sendUiEvent(event: UiEvent) { viewModelScope.launch { _uiEvent.send(event) } } - LaunchedEffect的key优化:将viewModel作为LaunchedEffect的key,确保ViewModel重新创建时能重新收集事件流:
LaunchedEffect(key1 = viewModel) { viewModel.uiEvent.collectLatest { event -> when (event) { is UiEvent.Navigate -> { onNavigate(event) } else -> Unit } } }
内容的提问来源于stack exchange,提问作者Juncu
相关产品推荐
相关产品推荐

