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

Button的OnClick事件无法跳转至其他屏幕问题求助

按钮点击无法跳转至CounterEditScreen的问题排查与解决

我给按钮添加了OnClick事件,想让它跳转至CounterEditScreen,但功能失效。代码逻辑和另一组正常跳转的代码一致,求解决方案。

相关代码

@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>()

其他优化与检查点

  1. 路由字符串一致性:确认Routes.COUNTEREDIT.route的值与Navigation中注册的路由完全一致,无拼写错误。
  2. 协程调度器简化: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)
        }
    }
    
  3. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 22:54:53