与我司一同于10月8日至10日在纽约市参加活动,学习关于 GraphQL Federation 和 API 平台工程的最新技巧、趋势和新闻。参加2024年纽约市GraphQL峰会
文档
免费开始

迁移到Apollo Kotlin 4

从第3版本开始


3是对Apollo在Kotlin多平台上的重大重写。

Apollo Kotlin 4专注于工具、稳定性和使库更容易维护,以便在未来多年内顺利发展。

尽管大多数核心API保持不变,Apollo Kotlin 4包含一些二进制不兼容的变化。为了解决这个问题,并为了更好地面向未来,我们更改了包名com.apollographql.apollo。请参阅 Apollo Kotlin 演变策略 了解更多详情。

Apollo Kotlin 4 移除了一些已弃用的符号。我们强烈建议更新到最新的 3.x 版本,并在迁移到 4 版本之前移除弃用的用法。

重要变更包括

下面将提供更多详情。

使用 Android Studio/IntelliJ 插件进行自动迁移

Apollo Kotlin 4 版本附带一个 Android Studio/IntelliJ 插件,它自动完成大部分迁移工作。

它自动完成大部分 API 变更,但不能处理如错误处理这样的行为变更。

我们建议使用插件来自动执行重复性任务,但是还是要仔细阅读以下 以了解详细信息。

组 ID/插件 ID/包名

Apollo Kotlin 4 使用新的标识符用于其 Maven 组 ID、Gradle 插件 ID 和包名 : com.apollographql.apollo

这也允许在需要时并行运行版本 4 和 3。

在大多数情况下,您可以通过查找和替换操作来更新项目中的标识符,将 com.apollographql.apollo3 替换为 com.apollographql.apollo

组 ID

用于识别 Apollo Kotlin 4 的 Maven 组 ID 是 构件,它是 com.apollographql.apollo

// Replace:
implementation("com.apollographql.apollo3:apollo-runtime:3.8.4")
// With:
implementation("com.apollographql.apollo:apollo-runtime:4.0.0")

Gradle 插件 ID

Apollo Kotlin 4.0 的 Gradle 插件 ID 是 com.apollographql.apollo

// Replace:
plugins {
id("com.apollographql.apollo3") version "3.8.4"
}
// With:
plugins {
id("com.apollographql.apollo") version "4.0.0"
}

包名

Apollo Kotlin 4 类使用 com.apollographql.apollo 包:

// Replace:
import com.apollographql.apollo3.ApolloClient
import com.apollographql.apollo3.*
// With:
import com.apollographql.apollo.ApolloClient
import com.apollographql.apollo.*

移动的构件

多年来,许多支持功能被添加到 apollo-apiapollo-runtime 构件中。虽然很有用,但其中大部分功能并不具备核心构件同样程度的稳定性和成熟度,将它们捆绑在一起并没有太大意义。

向前发展,只有核心组件保留在 apollo-kotlin 仓库中,并且一起发布。其他组件移动到新的 Maven 坐标和 GitHub 仓库中。

这将使我们能够更快地对新功能进行迭代,同时保持核心组件更小、更易于维护。

新坐标的组件有:

旧坐标新坐标新仓库
com.apollographql.apollo3:apollo-adapterscom.apollographql.adapters:apollo-adapters-coreapollographql/apollo-kotlin-adapters
com.apollographql.adapters:apollo-adapters-kotlinx-datetimeapollographql/apollo-kotlin-adapters
com.apollographql.apollo3:apollo-compose-support-incubatingcom.apollographql.compose:compose-supportapollographql/apollo-kotlin-compose-support
com.apollographql.apollo3:apollo-compose-paging-support-incubatingcom.apollographql.compose:compose-paging-supportapollographql/apollo-kotlin-compose-support
com.apollographql.apollo3:apollo-cli-incubatingcom.apollographql.cli:apollo-cliapollographql/apollo-kotlin-cli
com.apollographql.apollo3:apollo-engine-ktorcom.apollographql.ktor:apollo-engine-ktorapollographql/apollo-kotlin-ktor-support
com.apollographql.apollo3:apollo-mockservercom.apollographql.mockserver:apollo-mockserverapollographql/apollo-kotlin-mockserver
com.apollographql.apollo3:apollo-normalized-cache-incubatingcom.apollographql.cache:normalized-cache-incubatingapollographql/apollo-kotlin-normalized-cache-incubating
com.apollographql.apollo3:apollo-normalized-cache-api-incubatingcom.apollographql.cache:normalized-cache-incubatingapollographql/apollo-kotlin-normalized-cache-incubating
com.apollographql.apollo3:apollo-normalized-cache-sqlite-incubatingcom.apollographql.cache:normalized-cache-sqlite-incubatingapollographql/apollo-kotlin-normalized-cache-incubating
com.apollographql.apollo3:apollo-runtime-javacom.apollographql.java:clientapollographql/apollo-kotlin-java-support
com.apollographql.apollo3:apollo-rx2-support-javacom.apollographql.java:rx2apollographql/apollo-kotlin-java-support
com.apollographql.apollo3:apollo-rx3-support-javacom.apollographql.java:rx3apollographql/apollo-kotlin-java-support

这些新组件也使用了新的包名。您通常可以通过删除 .apollo3 来猜测它:

// Replace
import com.apollographql.apollo3.mockserver.MockServer
// With
import com.apollographql.mockserver.MockServer

apollo-runtime

捕取错误不会抛出异常

⚠️ 注意:

错误处理更改是编译时未被检测到的行为更改。对 executetoFlowwatch 的使用必须更新到新的错误处理,或者更改为它们版本 3 的兼容等效版本。

查看executeV3toFlowV3以获取帮助迁移的临时方法。

查看nullability,快速了解中的nullability和错误处理。

在 Apollo Kotlin 3 中,ApolloCall.execute() 和 Flows (ApolloCall.toFlow(), ApolloCall.watch()) 会通过抛出异常来抛出网络错误、缓存未命中和解析错误。

这存在问题,因为它在处理 GraphQL 错误与其他错误时的做法有所不同。也容易忘记捕获异常。未捕获的网络错误是 Google Play SDK Index 上报告的最常见的错误。Google Play SDK Index此外,抛出操作会终止一个 Flow,消费者必须处理重新收集。

在 Apollo Kotlin 4 中,增加了一个新的 ApolloResponse.exception,这些错误现在是通过返回(对于 execute())或发出(对于 Flows)一个包含非空异常的 ApolloResponse 来暴露的,而不是抛出。

查询和

// Replace
try {
val response = client.query(MyQuery()).execute()
if (response.hasErrors()) {
// Handle GraphQL errors
} else {
// No errors
val data = response.data
// ...
}
} catch (e: ApolloException) {
// Handle fetch errors
}
// With
val response = client.query(MyQuery()).execute()
if (response.data != null) {
// Handle (potentially partial) data
} else {
// Something wrong happened
if (response.exception != null) {
// Handle fetch errors
} else {
// Handle GraphQL errors in response.errors
}
}

// Replace
client.subscription(MySubscription()).toFlow().collect { response ->
if (response.hasErrors()) {
// Handle GraphQL errors
}
}.catch { e ->
// Handle fetch errors
}
// With
client.subscription(MySubscription()).toFlow().collect { response ->
val data = response.data
if (data != null) {
// Handle (potentially partial) data
} else {
// Something wrong happened
if (response.exception != null) {
// Handle fetch errors
} else {
// Handle GraphQL errors in response.errors
}
}
}

请注意,这对于所有Flows都成立,包括观察者。如果您不希望接收错误响应(如缓存未命中),请过滤掉它们:

// Replace
apolloClient.query(query).watch()
// With
apolloClient.query(query).watch().filter { it.exception == null }

较低级别的 ApolloStore APIs 没有改变,并在缓存未命中或 I/O 错误时抛出异常。

ApolloCompositeException 不会抛出

当使用缓存时,Apollo Kotlin 3 如果没有找到响应会抛出ApolloCompositeException。例如,如果缓存和网络都失败了,使用CacheFirst获取策略会抛出ApolloCompositeException(cacheMissException, apolloNetworkException)

在这些情况下,Apollo Kotlin 4 抛出第一个异常,并将第二个异常作为被抑制异常添加:

// Replace
if (exception is ApolloCompositeException) {
val cacheMissException = exception.first
val networkException = exception.second
}
// With
val cacheMissException = exception
val networkException = exception.suppressedExceptions.firstOrNull()

要更好地控制异常,使用toFlow()`和收集不同的ApolloResponse

emitCacheMisses(Boolean)被移除

在 Apollo Kotlin 3 中,当使用规范化缓存时,可以将emitCacheMisses(Boolean)设置true以发出缓存未命中而不是抛出。

在 Apollo Kotlin 4 中,缓存未命中始终会发出包含 response.exception 的响应,其中包含一个 CacheMissExceptionemitCacheMisses(Boolean) 已被移除。

使用 CacheFirstNetworkFirstCacheAndNetwork 策略时,缓存未命中和网络错误现在会暴露在 ApolloResponse.exception 中。

迁移助手

为了简化从 Apollo Kotlin 3 的迁移,提供了一组即插即用的帮助函数,以恢复 3 版本的行刑:

  • ApolloCall.executeV3()
  • ApolloCall.toFlowV3()

这些帮助函数

  • 在获取错误时抛出异常
  • 使 CacheFirstNetworkFirstCacheAndNetwork 策略忽略获取错误。
  • 如有必要,抛出 ApolloComposite 异常。

由于 3 版本中有许多不同的选项和错误处理复杂性的设置,这些函数可能不完全匹配 3 版本的行为,尤其是在涉及观察者的复杂案例中。如果您处于这些情况之一,我们强烈建议使用 4 版本的函数,这些函数更容易推理。

默认不发送非标准 HTTP 头

X-APOLLO-OPERATION-NAMEX-APOLLO-OPERATION-ID 是非标准头,默认不再发送。如果您使用它们进行日志记录或正在使用 CSRF Prevention,您可以使用 ApolloInterceptor 添加它们:

val apolloClient = ApolloClient.Builder()
.serverUrl(mockServer.url())
.addInterceptor(object : ApolloInterceptor {
override fun <D : Operation.Data> intercept(request: ApolloRequest<D>, chain: ApolloInterceptorChain): Flow<ApolloResponse<D>> {
return chain.proceed(request.newBuilder()
.addHttpHeader("X-APOLLO-OPERATION-NAME", request.operation.name())
.addHttpHeader("X-APOLLO-OPERATION-ID", request.operation.id())
.build()
)
}
})
.build()

ApolloCall.Builder.httpHeaders 是可添加的

Apollo Kotlin 3 中,如果已在一个 ApolloCall 上设置了 HTTP 头,它们将替换 ApolloClient 上设置的 Http 头。在 Apollo Kotlin 4 中,默认情况下它们是通过添加来设置的。要替换它们,请调用 ApolloCall.Builder.ignoreApolloClientHttpHeaders(true)

// Replace
val call = client.query(MyQuery())
.httpHeaders(listOf("key", "value"))
.execute()
// With
val call = client.query(MyQuery())
.httpHeaders(listOf("key", "value"))
.ignoreApolloClientHttpHeaders(true)
.execute()

HttpEngine 实现 Closeable

HttpEngine 现在实现 Closeable,并将其 dispose 方法重命名为 close。如果您有一个自定义 HttpEngine,您需要实现 close 而不是 dispose

apollo-gradle-plugin

多模块依赖关系

在 Apollo Kotlin 3 中,根据上游 GraphQL 模块的依赖,使用的是apolloMetadata配置。

在 Apollo Kotlin 4 中,现在使用的是Service.dependsOn() API。Service.dependsOn()适用于多服务存储库,并且通过隐藏配置名称,可以在必要时更改将来的实现。这可能需要适应Gradle 项目隔离

// feature1/build.gradle.kts
// Replace
dependencies {
// ...
// Get the generated schema types (and fragments) from the upstream schema module
apolloMetadata(project(":schema"))
// You also need to declare the schema module as a regular dependency
implementation(project(":schema"))
}
// With
dependencies {
// ...
// You still need to declare the schema module as a regular dependency
implementation(project(":schema"))
}
apollo {
service("service") {
// ...
// Get the generated schema types and fragments from the upstream schema module
dependsOn(project(":schema"))
}
}

自动检测使用中的类型

在多模块项目中,默认情况下会生成上游模块的所有类型,因为没有办法提前知道下游模块将使用哪些类型。对于大型项目,这可能导致大量未使用的代码和增加构建时间。

为了避免这种情况,在 Apollo Kotlin 3 中可以通过使用alwaysGenerateTypesMatching手动指定要生成的类型。在 Apollo Kotlin 4 中,可以选择自动检测使用中的类型。

要启用此功能,请使用isADependencyOf()添加依赖项的“相反”链接。

// schema/build.gradle.kts
// Replace
apollo {
service("service") {
// ...
// Generate all the types in the schema module
alwaysGenerateTypesMatching.set(listOf(".*"))
// Enable generation of metadata for use by downstream modules
generateApolloMetadata.set(true)
}
}
// With
apollo {
service("service") {
// ...
// Enable generation of metadata for use by downstream modules
generateApolloMetadata.set(true)
// Get used types from the downstream module1
isADependencyOf(project(":feature1"))
// Get used types from the downstream module2
isADependencyOf(project(":feature2"))
// ...
}
}

如果您之前使用过apolloUsedCoordinates,也可以将其删除:

dependencies {
// Remove this
apolloUsedCoordinates(project(":feature1"))
}

所有模式类型都在模式模块中生成

Apollo Kotlin 3 允许在任意模块中生成枚举或输入类型的模式类型。虽然非常灵活,但这也创建了很多复杂性:

  • 可能在不同模块中生成重复的类型。
  • 不清楚应该使用什么包名。
    • 使用下游模块的包名意味着当类型移动到另一个模块时,类的限定名称会发生变化。
    • 使用模式模块的包名可能会令人惊讶,因为它不是在 Gradle 配置中指定的。
  • Gradle 有 API 以聚合项目中所有项目以实现项目隔离(见gradle#22514)。但对于项目子集来说,目前尚不清楚如何实现这一点(见gradle#29037)。

Apollo Kotlin 4 强制在单个模块中生成所有模式类型,即模式模块。这种限制简化了 Gradle 插件,并使得调试和代码的演进更加容易。

这意味着您无法仅使用类型子集发布您的模式模块,并允许其他模块在另一个存储库中自行生成。如果您正在发布您的模式模块,它需要包含所有消费者使用的模式类型,可以使用alwaysGenerateTypesMatching.set(listOf(".*"))或手动添加。

如果这个限制太多,请提出您的情况问题,我们将重新审视。

Apollo元数据发布不再自动创建

Apollo Kotlin 3会自动使用 artifactId${project.name}-apollo创建一个 "apollo" 发布。这导致一些项目中出现了依赖关系解析问题(见apollo-kotlin#4350)。

Apollo Kotlin 4通过暴露组件而不是发布来提供更多关于如何创建发布的控制。要发布Apollo元数据,请手动创建一个MavenPublication

publishing {
publications {
create("apollo", MavenPublication::class.java) {
from(components["apollo"])
artifactId = "${project.name}-apollo"
}
}
}

自定义标量声明

customScalarsMapping被移除并替换为mapScalar(),这使得将其映射到内置类型以及/或为给定类型提供编译器类型适配器变得更加容易:

// Replace
customScalarsMapping.set(mapOf(
"Date" to "kotlinx.datetime.LocalDate"
))
// With
mapScalar("Date", "kotlinx.datetime.LocalDate")
// Replace
customScalarsMapping.put("MyLong", "kotlin.Long")
// With
mapScalarToKotlinLong("MyLong")

迁移到ApolloCompilerPlugin

Apollo Kotlin 4通过引入ApolloCompilerPlugin作为定制代码生成的方式。 ApolloCompilerPlugin替换编译器钩子,并使用ServiceLoader API来加载。结果,与Service.operationIdGenerator/Service.operationOutputGenerator以及ApolloCompilerPlugin一起使用是不可能的。

Service.operationIdGenerator/Service.operationOutputGenerator已被弃用,并将在未来版本中删除。您可以按照专门的页面说明迁移到ApolloCompilerPlugin,并使用ApolloCompilerPlugin.operationIds()方法:

// Replace (in your build scripts)
val operationOutputGenerator = object: OperationOutputGenerator {
override fun generate(operationDescriptorList: Collection<OperationDescriptor>): OperationOutput {
return operationDescriptorList.associateBy {
it.source.sha1()
}
}
override val version: String = "v1"
}
// Or, if using OperationIdGenerator, replace
val operationIdGenerator = object: OperationIdGenerator {
override fun apply(operationDocument: String, operationName: String): String {
return operationDocument.sha1()
}
override val version: String = "v1"
}
// With (in your compiler plugin module)
class MyPlugin: ApolloCompilerPlugin {
override fun operationIds(descriptors: List<OperationDescriptor>): List<OperationId>? {
return descriptors.map {
OperationId(it.source.sha1(), it.name)
}
}
}

操作说明文件的位置

由于Apollo Kotlin现在支持不同的操作说明文件格式,因此operationOutput.json文件现在在"build/generated/manifest/apollo/$service/operationOutput.json"生成,而不是"build/generated/operationOutput/apollo/$service/operationOutput.json"

useSchemaPackageNameForFragments 已删除

这提供了对 2.x 的兼容性,现在已删除。如果您需要为 而指定的包名,您可以使用 编译器插件ApolloCompilerPlugin.layout() 来代替。

apollo-compiler

"compat" codegenModels 已删除

为了与 2.x 兼容,"compat" 代码生成模型已被提供,现在已删除。 "operationBased" 更加一致并生成更少的代码:

// Replace
apollo {
service("service") {
codegenModels.set("compat")
}
}
// With
apollo {
service("service") {
codegenModels.set("operationBased")
}
}

在生成的模型中,内联 fragment 访问器以 on 作为前缀:

// Replace
data.hero.asDroid
// With
data.hero.onDroid

现在不再需要片段 fragments 伪属性:

// Replace
data.hero.fragments.heroDetails
// With
data.hero.heroDetails

最后,一些来自其父级内联 fragment 合并的 必须现在通过内联片段来访问:

// Replace
/**
* {
* hero {
* # this condition is always true
* # allowing to merge the name field
* ... on Character {
* name
* }
* }
* }
*/
data.hero.name
// With
/**
* name is not merged anymore
*/
data.hero.onCharacter?.name

注意

Android Studio 插件提供 一个 compat 盘格替换 operationBased 工具,该工具自动执行许多这些更改。

数据构建器

Apollo Kotlin 3 中,使用片段和 需要在 buildFoo {} 块中嵌套字段以确定返回的具体类型。

Apollo Kotlin 4 中,这通过使用 Data() 构造函数的第一个参数来指定。这使得使用片段的数据构建器的语法与操作相同:

// Replace
val data = AnimalDetailsImpl.Data {
buildCat {
name = "Noushka"
species = "Maine Coon"
}
}
// With
val data = AnimalDetailsImpl.Data(Cat) {
name = "Noushka"
species = "Maine Coon"
}

有关更多信息,请参阅 数据构建器文档

测试构建器已删除

测试构建器是一个实验性功能,已被更简单、与自定义 标度 交互更好的数据构建器所取代。

Apollo Kotlin 4 中,测试构建器不再可用 - 请参阅数据构建器文档以获取更多信息。

枚举类名现在以大写字母开头

为了与其他类型一致,Kotlin 中的 GraphQL 枚举现在需要首字母大写。您可以使用 @targetName 来恢复以前的行为:

# Make sure `someEnum` isn't renamed to `SomeEnum`
enum someEnum @targetName(name: "someEnum"){
A
B
}

__Schema 位于 schema 子模块

当使用 generateSchema 选项时,为了避免与同名的 类型产生名称冲突,现在 __Schema 类型将生成在 schema 子模块中(而不是 type):

// Replace
import com.example.type.__Schema
// With
import com.example.schema.__Schema

KotlinLabs 指令现在已升级到 0.3 版本

嵌入式的 kotlin_labs 现在已升级到 0.3 版本。

以下是由 Apollo Kotlin 支持的客户端 directives。它们将被自动导入,但如果您因为重命名或其他原因显式依赖于导入,您需要升级版本:

# Replace
extend schema @link(url: "https://specs.apollo.dev/kotlin_labs/v0.1/", import: ["@typePolicy"])
# With
extend schema @link(url: "https://specs.apollo.dev/kotlin_labs/v0.3/", import: ["@typePolicy"])

这是一个向后兼容的变更。

指令使用情况得到验证

在 Apollo Kotlin 4 中,所有 使用都会与它们的定义进行验证。如果您有使用客户端指令的查询:

query HeroName {
hero {
# WARNING: '@required' is not defined by the schema
name @required
}
}

这些查询现在需要与模式中的匹配 directive 定义。如果您使用 introspection 下载了您的模式,这不应该是一个问题。

在某些情况下,对于客户端 directives 和/或如果没有使用 introspection,指令定义可能缺失。对于这些情况,您可以在一个 extra.graphqls 文件中显式添加它:

directive @required on FIELD

Sealed 类的 UNKNOWN__ 构造函数现在是私有的

当使用 sealedClassesForEnumsMatching 选项处理枚举时,现在将 UNKNOWN__ 构造函数生成为私有,以防止其意外使用。

建议您更新模式而不是实例化一个 UNKNOWN__ 值,但如果您需要,请改用 safeValueOf 方法:

// Replace
val myEnum = MyEnum.UNKNOWN__("foo")
// With
val myEnum = MyEnum.safeValueOf("foo")

apollo-normalized-cache

配置顺序

在自动持久化查询之前必须配置规范化缓存,配置在之后将无法成功(见https://github.com/apollographql/apollo-kotlin/pull/4709)。

// Replace
val apolloClient = ApolloClient.Builder()
.serverUrl(...)
.autoPersistedQueries(...)
.normalizedCache(...)
.build()
// With
val apolloClient = ApolloClient.Builder()
.serverUrl(...)
.normalizedCache(...)
.autoPersistedQueries(...)
.build()

apollo-ast

Apollo Kotlin 3 中,apollo-ast 使用 Antlr 来解析 GraphQL 文档。

Apollo Kotlin 4 中,apollo-ast 使用其自己的递归下降解析器。这不仅消除了依赖,而且速度更快,还支持 KMP。我们借此机会清理了 apollo-ast API:

  • AST 类(GQLNode及其子类)以及Introspection类不再是数据类(见https://github.com/apollographql/apollo-kotlin/pull/4704/)。
  • 已经调整了类层次结构,以便 GQLNamedGQLDescribedGQLHasDirectives更一致地继承。
  • GQLSelectionSetGQLArguments已从GQLFieldGQLInlineFragment中删除。直接使用.selections
  • GQLInlineFragment.typeCondition现在为可为空,以处理继承其类型条件的内部片段。
  • SourceLocation.position已重命名为SourceLocation.column,现在是 1 索引。
  • GQLNode.sourceLocation现在为空可考虑程序性地构造节点的情况。
  • File.toSchema()String.toSchema()已删除。相反,使用toGQLDocument().toSchema()

迁移示例

如果您在寻找灵感,我们已更新了版本 3 集成测试以使用版本 4。如果您有一个迁移了的开源项目,请随时分享,我们将将其包含在此处。

上一页
开始使用
下一页
模块
评分文章评分在GitHub上编辑编辑论坛Discord

©2024Apollo Graph Inc.,简称Apollo GraphQL。

隐私政策

公司