02 · 在鸿蒙工程中使用 Apollo-Kotlin

本文讲怎么用:把适配好的 Apollo 引入你的 KMP 鸿蒙工程并发起请求。
假设你已按 01 · 适配 完成适配,或直接使用本文给出的坐标。

一、引入依赖

1.1 配置仓库

settings.gradle.kts 里两个 repositories 块都要加(模板里 mavenLocal() 默认是注释掉的):

pluginManagement {
    repositories {
        mavenLocal()                                                      // ← 适配产物在这里
        maven("https://maven.eazytec-cloud.com/nexus/repository/maven-public/")
        gradlePluginPortal()
    }
}

dependencyResolutionManagement {
    repositories {
        mavenLocal()                                                      // ← 别忘了这个
        maven("https://maven.eazytec-cloud.com/nexus/repository/maven-public/")
    }
}

漏掉任何一处都会报 Could not resolve com.apollographql.apollo:...。

1.2 声明依赖

关键:必须放在 ohosMain,不能放 commonMain。

kotlin {
    sourceSets {
        commonMain.dependencies {
            implementation(compose.runtime)
            implementation(compose.foundation)
            implementation(compose.material3)
            // ❌ 不要在这里加 apollo!
        }

        val ohosMain = sourceSets.create("ohosMain").apply {
            dependsOn(commonMain.get())
        }
        ohosMain.dependencies {
            api(libs.compose.multiplatform.export)
            // ✅ 正确位置
            implementation("com.apollographql.apollo:apollo-runtime:4.3.0")
            implementation("com.apollographql.apollo:apollo-normalized-cache:4.3.0")
            implementation("com.apollographql.apollo:apollo-adapters:4.3.0")
        }
    }
}

为什么不能放 commonMain?

因为你的工程通常同时有 androidTarget,而 commonMain 必须对所有目标成立。
Apollo 的适配产物只发布了 ohosArm64 变体,Android 侧没有 → 解析失败:

The dependency should target platforms: [android, android, ohosArm64]
Unresolved platforms: [android, android]

判断标准:这个依赖有没有覆盖工程的所有 target?

  • 只有 ohos → 放 ohosMain
  • 全平台都有(如 kotlinx-coroutines)→ 才放 commonMain

1.3 桥接(如果 UI 在 commonMain)

如果 commonMain 里的 UI 要调用 Apollo,用 expect/actual 桥接:

// commonMain —— 只声明能力
expect fun runApolloDemo(): String

// ohosMain —— 真正调用 Apollo
actual fun runApolloDemo(): String {
    val engine = DefaultHttpEngine(timeoutMillis = 15_000)
    // ...
}

// androidMain —— 占位(保证 commonMain 能解析)
actual fun runApolloDemo(): String = "该演示仅在 HarmonyOS 启用"

二、基础用法

2.1 发一个 HTTP 请求(用 ohos 引擎)

import com.apollographql.apollo.network.http.DefaultHttpEngine
import com.apollographql.apollo.network.http.get
import kotlinx.coroutines.runBlocking

fun fetchSomething(): String = runBlocking {
    val engine = DefaultHttpEngine(timeoutMillis = 15_000)
    try {
        val response = engine.get("https://jsonplaceholder.typicode.com/todos/1").execute()
        val body = response.body?.readUtf8() ?: "(empty)"
        "HTTP ${response.statusCode}\n$body"
    } finally {
        engine.close()   // 必须关闭,释放连接池
    }
}

DefaultHttpEngine() 就是我们在适配时新写的 ktor/Curl 实现(CIO 在 Kotlin/Native 上不支持 TLS)。

在这里插入图片描述

2.2 构造 ApolloClient

import com.apollographql.apollo.ApolloClient

val client = ApolloClient.Builder()
    .serverUrl("https://your-graphql-endpoint/graphql")
    .build()

如果要配拦截器、缓存等:

val client = ApolloClient.Builder()
    .serverUrl("https://your-endpoint/graphql")
    .normalizedCache(MemoryCacheFactory(maxSizeBytes = 10 * 1024 * 1024))
    .build()

normalizedCache 需要引入 apollo-normalized-cache(已在 1.2 中声明)。

2.3 用 BigDecimal 适配器

GraphQL 没有原生的十进制类型,Apollo 提供了 BigDecimal 适配器:

import com.apollographql.apollo.adapter.BigDecimal

val price = BigDecimal("12345.678")
val tax = BigDecimal("1000.322")
val total = price.add(tax)          // 13346.000
val doubled = price.multiply(BigDecimal("2"))   // 24691.356

鸿蒙上这是纯 Kotlin 实现(unscaled + scale 十进制表示),精度可靠:

println("$price + $tax = $total")     // 12345.678 + 1000.322 = 13346.000
println("$price * 2 = $doubled")      // 12345.678 * 2 = 24691.356

2.4 时间戳

import com.apollographql.apollo.mpp.currentTimeMillis

val ts = currentTimeMillis()   // 毫秒时间戳,POSIX clock_gettime 实现

三、完整可跑示例

下面这段代码在 KMPCMPOHOS 的 ohosMain 里,点按钮即可运行:

package com.goway.kmpcmpohos

import com.apollographql.apollo.ApolloClient
import com.apollographql.apollo.adapter.BigDecimal
import com.apollographql.apollo.mpp.currentTimeMillis
import com.apollographql.apollo.network.http.DefaultHttpEngine
import com.apollographql.apollo.network.http.get
import kotlinx.coroutines.runBlocking
import okio.IOException

actual fun runApolloDemo(): String = buildString {
    // ① mpp-utils 的 ohos actual
    appendLine("① currentTimeMillis() = ${currentTimeMillis()}")

    // ② adapters 的 ohos BigDecimal
    val a = BigDecimal("12345.678")
    val b = BigDecimal("1000.322")
    appendLine("② BigDecimal: $a + $b = ${a.add(b)} ; $a * 2 = ${a.multiply(BigDecimal("2"))}")

    // ③ runtime 可构建
    appendLine(runCatching {
        ApolloClient.Builder().serverUrl("https://jsonplaceholder.typicode.com/todos/1").build()
        "③ ApolloClient 构建成功"
    }.getOrElse { "③ ApolloClient 构建失败:${it.message}" })

    // ④ 真实 HTTP 请求
    append(httpEngineDemo())
}

private fun httpEngineDemo(url: String = "https://jsonplaceholder.typicode.com/todos/1"): String = try {
    runBlocking {
        val engine = DefaultHttpEngine(timeoutMillis = 15_000)
        try {
            val r = engine.get(url).execute()
            "④ HTTP ${r.statusCode} ← $url\n${(r.body?.readUtf8() ?: "").take(280)}"
        } finally {
            engine.close()
        }
    }
} catch (e: IOException) {
    "④ 网络异常(引擎已正常构建):${e.message}"
} catch (e: Throwable) {
    "④ 请求失败:${e::class.simpleName}: ${e.message}"
}

UI 侧(commonMain):

@Composable
private fun ApolloDemoCard() {
    var result by remember { mutableStateOf<String?>(null) }
    var running by remember { mutableStateOf(false) }
    val scope = rememberCoroutineScope()

    Card(modifier = Modifier.fillMaxWidth()) {
        Column(modifier = Modifier.padding(12.dp)) {
            Text("Apollo-Kotlin(清单 #1)", style = MaterialTheme.typography.titleMedium)
            Button(
                enabled = !running,
                onClick = {
                    running = true
                    // 不要在 onClick 里直接 runBlocking:网络请求会阻塞 UI 线程,
                    // 触发 APP_INPUT_BLOCK / THREAD_BLOCK_3S,应用被系统强杀(闪退)。
                    scope.launch {
                        val text = withContext(Dispatchers.Default) {
                            runCatching { runApolloDemo() }
                                .getOrElse { "异常:${it.message}" }
                        }
                        result = text
                        running = false
                    }
                },
            ) { Text(if (running) "运行中…" else "运行 Apollo 演示") }
            result?.let { Text(it, style = MaterialTheme.typography.bodySmall) }
        }
    }
}

在这里插入图片描述

四、注意事项

项说明
依赖位置必须在 ohosMain,放 commonMain 会因 Android 缺变体而失败
mavenLocal()settings.gradle.kts 两处都要开
网络权限module.json5 需声明 ohos.permission.INTERNET
HTTPS鸿蒙默认校验证书;自签证书需额外配置
engine.close()用完必须关,否则连接池泄漏
协程execute() 是 suspend,需在协程中调用;禁止在 UI 线程 runBlocking,否则输入阻塞会被系统强杀

加网络权限

harmonyApp/entry/src/main/module.json5:

{
  "module": {
    "requestPermissions": [
      { "name": "ohos.permission.INTERNET" }
    ]
  }
}

不加会报 Network is unreachable 或直接抛异常。

下一篇:03 · 排障 —— 常见报错速查。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐