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

Jetpack Compose应用开发:解决Composable调用上下文错误与页面跳转传参问题

解决方案

先来说第一个错误的核心原因:你在clickable的回调里直接调用了DetailsScreen这个Composable函数,但clickable的lambda并不是Composable上下文——只有被@Composable注解标记的函数内部,才允许调用其他Composable函数,这就是你看到Composables can only be invoked from the context of a composable context报错的根源。

同时你的页面跳转逻辑也不符合Jetpack Compose的规范,Compose的页面跳转必须依赖Navigation组件实现,而不是直接实例化Composable。下面是完整的修复步骤:


1. 添加详情页的导航路由

首先扩展你的路由定义,新增带参数的详情页路由(推荐单独拆分路由类,避免和底部导航项混淆):

// 统一管理所有页面路由
sealed class ScreenRoutes(val route: String) {
    object Popular : ScreenRoutes("popular")
    object Playing : ScreenRoutes("playing")
    object Top : ScreenRoutes("top")
    object Upcoming : ScreenRoutes("upcoming")
    // 带参数的详情页路由,{movieId}是参数占位符
    object Details : ScreenRoutes("details/{movieId}")
}

// 底部导航项继承路由类,保持原有功能
sealed class BarItems(route: String, var icon: Int, var title: String) : ScreenRoutes(route) {
    object Popular : BarItems("popular", R.drawable.ic_baseline_remove_red_eye_24,"Popular")
    object Playing : BarItems("playing",R.drawable.ic_baseline_remove_red_eye_24,"Playing")
    object Top : BarItems("top",R.drawable.ic_baseline_remove_red_eye_24,"Top")
    object Upcoming : BarItems("upcoming",R.drawable.ic_baseline_remove_red_eye_24,"Upcoming")
}

2. 在导航宿主中配置详情页节点

修改ScreenNavigation函数,添加详情页的路由配置,并接收传递的movieId参数:

@OptIn(ExperimentalCoilApi::class)
@Composable
fun ScreenNavigation(navController: NavHostController,movieViewModel: MovieViewModel){
    NavHost(navController = navController, startDestination = ScreenRoutes.Popular.route){
        composable(route = ScreenRoutes.Popular.route){
            PopularScreen(movies = movieViewModel.getPopular(), movieViewModel = movieViewModel)
        }
        composable(route = ScreenRoutes.Playing.route){
            PlayingScreen(movies = movieViewModel.getPlaying())
        }
        composable(route = ScreenRoutes.Top.route){
            TopRatedScreen(movies = movieViewModel.getTopRated())
        }
        composable(route = ScreenRoutes.Upcoming.route){
            UpcomingScreen(movies = movieViewModel.getUpcoming())
        }
        // 配置详情页路由,声明参数类型
        composable(
            route = ScreenRoutes.Details.route,
            arguments = listOf(
                navArgument("movieId") {
                    type = NavType.IntType // 假设你的movieId是Int类型,根据实际数据类型调整
                }
            )
        ) { backStackEntry ->
            // 从导航栈中取出传递的movieId
            val movieId = backStackEntry.arguments?.getInt("movieId") ?: 0
            // 渲染详情页并传递参数
            DetailsScreen(movieViewModel = movieViewModel, movieId = movieId)
        }
    }
}

3. 修正PopularScreen的点击跳转逻辑

在PopularScreen中通过LocalNavController获取导航控制器(无需手动传递参数,更简洁),并修改点击事件为导航跳转:

@Composable
fun PopularScreen(
    movies: Flow<PagingData<Movie>>, // 假设你的实体类是Movie
    movieViewModel: MovieViewModel
) {
    // 从Compose上下文获取导航控制器
    val navController = LocalNavController.current
    val lazyPopularMoviesItems = movies.collectAsLazyPagingItems()
    
    LazyVerticalGrid(cells = GridCells.Fixed(2)) {
        items(lazyPopularMoviesItems.itemCount) { index ->
            lazyPopularMoviesItems[index]?.let { movie ->
                Card(
                    elevation = 8.dp,
                    modifier = Modifier
                        .height(200.dp)
                        .padding(10.dp)
                        .clickable {
                            // 导航到详情页,替换路由中的占位符为实际movieId
                            navController.navigate("details/${movie.id}")
                        }
                        .clip(RoundedCornerShape(8.dp))
                ) {
                    // 原有的Card内容保持不变...
                }
            }
        }
    }
}

4. 确保DetailsScreen正确接收参数

最后确认你的DetailsScreen能正确接收movieId并调用对应API:

@Composable
fun DetailsScreen(movieViewModel: MovieViewModel, movieId: Int) {
    // 调用ViewModel中根据ID获取详情的API
    val movieDetails by movieViewModel.getMovieDetails(movieId).collectAsState()
    
    // 渲染详情页面内容...
}

关键知识点回顾

  • Composable上下文限制:普通回调(如clickable)不属于Composable上下文,不能直接调用Composable函数,必须通过Navigation组件实现页面跳转。
  • Navigation参数传递:通过路由占位符声明参数,跳转时替换为实际值,目标页面从导航栈的arguments中提取参数。
  • LocalNavController的优势:无需在多个Composable之间传递导航控制器实例,直接从上下文获取,简化代码结构。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.28 15:54:08