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

Jetpack Compose中如何通过ViewModel实现页面导航?

实现ViewModel触发导航的方案

核心思路是ViewModel不直接持有NavController,而是通过事件流通知UI层执行导航操作,符合MVVM架构的职责分离原则,避免耦合和内存泄漏。

步骤1:修改ViewModel添加导航事件流

在HomeViewModel中添加SharedFlow用于发送一次性导航事件(推荐用SharedFlow而非StateFlow,因为导航是一次性操作,无需保留状态):

class HomeViewModel : ViewModel(){
    private var _uiState = MutableStateFlow(HomeData())
    var uiState : StateFlow<HomeData> = _uiState.asStateFlow()

    // 定义导航事件流,通知UI跳转到Chat页面
    private val _navigateToChat = MutableSharedFlow<Unit>()
    val navigateToChat = _navigateToChat.asSharedFlow()

    fun ChangeName() {
        _uiState.value = HomeData(UserName = "hello there")
        // 发送导航事件
        viewModelScope.launch {
            _navigateToChat.emit(Unit)
        }
    }
}

步骤2:在HomeScreen中收集导航事件并处理跳转

在HomeScreen中用LaunchedEffect收集ViewModel的导航事件,收到事件后调用NavController执行导航:

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun HomeScreen(
    homeViewModel: HomeViewModel = viewModel(),
    navController: NavController
){
    val homeViewState by homeViewModel.uiState.collectAsState()
    
    // 监听导航事件,触发页面跳转
    LaunchedEffect(key1 = homeViewModel) {
        homeViewModel.navigateToChat.collect {
            navController.navigate("Chat")
        }
    }

    Scaffold (
        topBar = {
            TopAppBar(title = {
                Text(text = "Chat Wallet", style = MaterialTheme.typography.titleMedium)
            }, colors = TopAppBarDefaults.mediumTopAppBarColors(
                containerColor = MaterialTheme.colorScheme.primary,
                titleContentColor = MaterialTheme.colorScheme.background
            ))
        }
    ){ paddingValues -> 
        Box(modifier = Modifier.padding(paddingValues).fillMaxSize(), contentAlignment = Alignment.Center) {
            IconButton(
                onClick = { homeViewModel.ChangeName() }, // 改为调用ViewModel的ChangeName方法
                colors = IconButtonDefaults.iconButtonColors(containerColor = MaterialTheme.colorScheme.primary)
            ) {
                Icon(
                    painter = painterResource(id = R.drawable.baseline_chat_24), 
                    contentDescription = "跳转到聊天页面", 
                    tint = MaterialTheme.colorScheme.background
                )
            }
        }
    }
}

步骤3:保持原有导航宿主代码不变

NavigationHoster和MainActivity无需修改,保留你原本的实现:

@Composable
fun NavigationHoster(){
    val navController = rememberNavController();
    
    NavHost(navController = navController, startDestination = "Home"){
        composable("Home"){ HomeScreen(navController = navController)}
        composable("Chat"){ ChatScreen()}
    }
}

MainActivity代码

Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
    NavigationHoster()
}

方案说明

  • 解耦设计:ViewModel仅负责业务逻辑(修改用户名)和发送事件通知,不依赖UI层的NavController,避免了ViewModel与UI组件的耦合。
  • 生命周期安全:viewModelScope会在ViewModel销毁时自动取消协程;LaunchedEffect在Composable进入组合时启动,离开时自动取消,避免内存泄漏。
  • 一次性事件处理:SharedFlow适合处理导航这类一次性事件,不会像StateFlow那样在Composable重组时重复触发导航。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 17:35:14