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

如何在Jetpack Compose中正确实现ApolloGraphQL查询?

适配Jetpack Compose的Apollo GraphQL查询实现方案

问题重现

你在基于Compose Navigation的底部导航App中,尝试在HomeScreen Composable内直接调用Apollo Client的execute()方法执行查询:

fun HomeScreen() {
    Text(text = "Home")
    val response = apolloClient.query(LaunchListQuery()).execute()
    Log.d("LaunchList", "Success ${response.data}")
}

触发错误提示:

Suspend function 'execute' should be called only from a coroutine or another suspend function

官方教程基于Fragment实现,但你需要纯Composable方案,已尝试按钮触发协程的写法,现提供标准化实现方式。


正确实现方式

1. 页面初始化自动加载数据(用LaunchedEffect)

如果需要进入页面就触发查询,用LaunchedEffect——它会绑定Composable生命周期,在首次组合或指定Key变化时启动协程:

@Composable
fun HomeScreen(apolloClient: ApolloClient) {
    // 存储查询结果与错误状态,用于驱动UI更新
    val launchListState = remember { mutableStateOf<List<Launch>?>(null) }
    val errorState = remember { mutableStateOf<String?>(null) }

    LaunchedEffect(Unit) {
        try {
            val response = apolloClient.query(LaunchListQuery()).execute()
            if (response.hasErrors()) {
                errorState.value = response.errors?.joinToString { it.message }
            } else {
                launchListState.value = response.data?.launches
                Log.d("LaunchList", "Success ${response.data}")
            }
        } catch (e: Exception) {
            errorState.value = e.localizedMessage ?: "数据加载失败"
            Log.e("LaunchList", "Fetch error", e)
        }
    }

    // UI展示逻辑
    Column(modifier = Modifier.fillMaxSize()) {
        Text(text = "Home")
        errorState.value?.let {
            Text(text = "错误:$it", color = Color.Red, modifier = Modifier.padding(16.dp))
        }
        launchListState.value?.let { launches ->
            LazyColumn(modifier = Modifier.weight(1f)) {
                items(launches) { launch ->
                    Text(text = launch?.missionName ?: "未知任务", modifier = Modifier.padding(16.dp))
                }
            }
        } ?: CircularProgressIndicator(modifier = Modifier.align(Alignment.CenterHorizontally))
    }
}

2. 用户交互触发查询(用rememberCoroutineScope)

如果需要通过按钮点击等用户操作触发查询,优化后的写法(无需额外async,直接在协程内执行):

@Composable
fun HomeScreen(apolloClient: ApolloClient) {
    val coroutineScope = rememberCoroutineScope()
    val launchListState = remember { mutableStateOf<List<Launch>?>(null) }
    val errorState = remember { mutableStateOf<String?>(null) }

    val fetchLaunches = {
        coroutineScope.launch(Dispatchers.IO) {
            try {
                val response = apolloClient.query(LaunchListQuery()).execute()
                // 更新UI需切回主线程
                withContext(Dispatchers.Main) {
                    if (response.hasErrors()) {
                        errorState.value = response.errors?.joinToString { it.message }
                    } else {
                        launchListState.value = response.data?.launches
                        Log.d("LaunchList", "Success ${response.data}")
                    }
                }
            } catch (e: Exception) {
                withContext(Dispatchers.Main) {
                    errorState.value = e.localizedMessage ?: "数据加载失败"
                }
                Log.e("LaunchList", "Fetch error", e)
            }
        }
    }

    Column(modifier = Modifier.fillMaxSize()) {
        Text(text = "Home")
        Button(onClick = fetchLaunches, modifier = Modifier.padding(16.dp)) {
            Text("加载任务列表")
        }
        errorState.value?.let {
            Text(text = "错误:$it", color = Color.Red, modifier = Modifier.padding(16.dp))
        }
        launchListState.value?.let { launches ->
            LazyColumn(modifier = Modifier.weight(1f)) {
                items(launches) { launch ->
                    Text(text = launch?.missionName ?: "未知任务", modifier = Modifier.padding(16.dp))
                }
            }
        }
    }
}

3. 结合Flow实现响应式加载

Apollo支持将查询转为Flow,配合Compose的collectAsState可更优雅处理状态:

@Composable
fun HomeScreen(apolloClient: ApolloClient) {
    // 将查询转为Flow,并映射为统一状态
    val launchListFlow = remember(apolloClient) {
        apolloClient.query(LaunchListQuery())
            .toFlow()
            .map { response ->
                if (response.hasErrors()) {
                    Result.Failure(Exception(response.errors?.joinToString { it.message }))
                } else {
                    Result.Success(response.data?.launches)
                }
            }
            .onStart { emit(Result.Loading) }
    }

    val launchListResult by launchListFlow.collectAsState(initial = Result.Loading)

    // 根据状态渲染UI
    Column(modifier = Modifier.fillMaxSize()) {
        Text(text = "Home")
        when (launchListResult) {
            is Result.Loading -> CircularProgressIndicator(modifier = Modifier.align(Alignment.CenterHorizontally))
            is Result.Success -> {
                (launchListResult as Result.Success).data?.let { launches ->
                    LazyColumn(modifier = Modifier.weight(1f)) {
                        items(launches) { launch ->
                            Text(text = launch?.missionName ?: "未知任务", modifier = Modifier.padding(16.dp))
                        }
                    }
                } ?: Text("暂无任务数据", modifier = Modifier.padding(16.dp))
            }
            is Result.Failure -> {
                Text(text = "错误:${(launchListResult as Result.Failure).exception.message}", color = Color.Red, modifier = Modifier.padding(16.dp))
            }
        }
    }
}

// 自定义密封类统一状态
sealed class Result<out T> {
    object Loading : Result<Nothing>()
    data class Success<out T>(val data: T) : Result<T>()
    data class Failure(val exception: Exception) : Result<Nothing>()
}

核心注意事项

  • execute()是挂起函数,必须在协程或其他挂起函数内调用,Compose中优先用LaunchedEffect(自动绑定生命周期)或rememberCoroutineScope(手动管理协程)。
  • 网络请求建议在Dispatchers.IO线程执行,更新UI时需切回Dispatchers.Main(Compose协程默认在主线程,手动指定IO时需切换)。
  • 用remember+mutableStateOf或Flow+collectAsState存储查询结果,可实现UI自动响应数据变化。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 20:01:02