Apollo Kotlin 中的程序化缓存 ID
在以下情况下声明式缓存 ID 不适用于您的用例时,您可以为 程序化 生成您标准化缓存中的对象类型缓存 ID。
您可以从以下两个来源之一生成给定 对象类型' 的缓存在 ID:
来源 | 类 | 描述 |
---|---|---|
从响应对象的字段(例如,Book.id ) | CacheKeyGenerator | 这会在 网络请求之后 发生,对于将查询结果与现有缓存数据合并至关重要。这是最常见的情况。 |
从GraphQL操作的参数(例如,author(id: "au456") )中 | 缓存键解析器 | 这会发生在一个网络请求之前,使你能够在缓存中已经存在所有请求的数据的情况下避免网络往返。这是一个可选的优化,可以避免一些缓存缺失。 |
Apollo Kotlin提供了一个类,可以从每个这些来源生成缓存ID。
CacheKeyGenerator
类CacheKeyGenerator
允许您从对象的字段值中生成自定义缓存ID。以下是一个基本示例,从每个对象类型的id
字段生成其缓存ID:
val cacheKeyGenerator = object : CacheKeyGenerator {override fun cacheKeyForObject(obj: Map<String, Any?>, context: CacheKeyGeneratorContext): CacheKey? {// Generate the cache ID based on the object's id fieldreturn CacheKey(obj["id"] as String)}}
要使用您的自定义CacheKeyGenerator
,将其包含在您的缓存初始化代码中,如下所示:
val apolloClient = ApolloClient.Builder().serverUrl("https://example.com/graphql").normalizedCache(normalizedCacheFactory = cacheFactory,cacheKeyGenerator = cacheKeyGenerator,).build()
您可以从context
对象中获取当前对象的typename并将其包含在生成的ID中,如下所示:
val cacheKeyGenerator = object : CacheKeyGenerator {override fun cacheKeyForObject(obj: Map<String, Any?>, context: CacheKeyGeneratorContext): CacheKey? {val typename = context.field.type.rawType().nameval id = obj["id"] as Stringreturn CacheKey(typename, id)}}
您还可以使用当前对象的typename来针对不同的对象类型使用不同的缓存ID生成逻辑。
请注意,为了使缓存ID生成有效,您的GraphQL操作的操作必须返回自定义代码所依赖的任何字段(如上例中的id
)。如果查询没有返回所需字段,则缓存ID将不一致,导致数据重复。此外,对于接口和联合类型,使用context.field.type.rawType().name
属性会返回与该类型在规范中声明时相同的typename,而不是响应中接收到的类型的运行时值。相反,查询typename会更安全。为了确保所有操作都包含__typename
,设置以下addTypename gradle配置:
apollo {service("service") {addTypename.set("always")}}
缓存键解析器
The CacheKeyResolver
类允许您从一个字段's 参数中生成自定义缓存 ID。这个基本示例从现有的 id
参数生成每种对象类型的缓存 ID:如果该参数存在。
val cacheKeyResolver = object: CacheKeyResolver() {override fun cacheKeyForField(field: CompiledField, variables: Executable.Variables): CacheKey? {// [field] contains compile-time information about what type of object is being resolved.// Even though we call rawType() here, we're guaranteed that the type is a composite type and not a listval typename = field.type.rawType().name// argumentValue returns the runtime value of the "id" argument// from either the variables or as a literal valueval id = field.argumentValue("id", variables).getOrNull()if (id is String) {// This field has an id argument, so we can use it to compute a cache IDreturn CacheKey(typename, id)}// Return null to use the default handlingreturn null}}
要使用您自己的 CacheKeyResolver
,请将其包含在您的缓存初始化代码中,如下所示:
val apolloClient = ApolloClient.Builder().serverUrl("https://example.com/graphql").normalizedCache(normalizedCacheFactory = cacheFactory,cacheKeyGenerator = cacheKeyGenerator,cacheResolver = cacheKeyResolver).build()
请注意使用自定义 CacheKeyResolver
的以下事项:
- 对于返回复合类型的操作中的每个字段,都会调用
cacheKeyForField
函数,因此如果您不想处理特定字段,则非常重要地返回null
。 - 对于返回复合类型列表的字段,不调用函数。
处理列表
让我们假设我们有一个这样的查询:
query GetBooks($ids: [String!]!) {books(ids: $ids) {idtitle}}
要使缓存查找 ids 列表中的所有书籍,我们需要覆盖 listOfCacheKeysForField
在 CacheKeyResolver
:
override fun listOfCacheKeysForField(field: CompiledField, variables: Executable.Variables): List<CacheKey?>? {// Note that the field *can* be a list type hereval typename = field.type.rawType().name// argumentValue returns the runtime value of the "id" argument// from either the variables or as a literal valueval ids = field.argumentValue("ids", variables).getOrNull()if (ids is List<*>) {// This field has an id argument, so we can use it to compute a cache IDreturn ids.map { CacheKey(typename, it as String) }}// Return null to use the default handlingreturn null}
为了简单起见,仅支持一个列表级别。要支持更多嵌套列表,可以实现 CacheResolver
。 CacheResolver
是 CacheKeyResolver
的一般化,可以返回缓存中的任何值,甚至 标量 值:
val cacheResolver = object: CacheResolver {override fun resolveField(field: CompiledField,variables: Executable.Variables,parent: Map<String, @JvmSuppressWildcards Any?>,parentId: String,): Any? {var type = field.typevar listDepth = 0while (true) {when (type) {is CompiledNotNullType -> type = type.ofTypeis CompiledListType -> {listDepth++type = type.ofType}else -> break}}// Now type points to the leaf type and lestDepth is the nesting of lists required// Return a kotlin value for this field// No type checking is done here, so it must match the expected GraphQL typeif (listDepth == 2) {return listOf(listOf("0", "1"))}// CacheResolver must always call DefaultCacheResolver last or all fields will be null elsereturn DefaultCacheResolver.resolveField(field, variables, parent, parentId)}}