Skip to content

Add cacheInterceptor() and autoPersistedQueriesInterceptor() #6455

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Apr 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Change Log
# Next version

* Downloading or converting a SDL schema from introspection now includes scalar definitions. This is required for clients to get a [full view of the schema](https://github.com/graphql/graphql-wg/blob/main/rfcs/FullSchemas.md).
* The cache and auto persisted queries interceptors are now always added after all users interceptor. If you relied on some interceptors being called **after** `normalizedCache()` or `persistedQueries()`, you might have to update your code. One example is if you need to change cache flags based on the GraphQL response. In those cases, we recommend you use a custom `NetworkTransport` instead (See [this commit](https://github.com/apollographql/apollo-kotlin/pull/6455/commits/a53a44e5e506af7b0f6f495eed1a9d477e18bf73) for an example).

# Version 4.1.1

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,26 +113,54 @@ fun ApolloClient.Builder.normalizedCache(
fun ApolloClient.Builder.logCacheMisses(
log: (String) -> Unit = { println(it) },
): ApolloClient.Builder {
check(interceptors.none { it is ApolloCacheInterceptor }) {
"Apollo: logCacheMisses() must be called before setting up your normalized cache"
}
return addInterceptor(CacheMissLoggingInterceptor(log))
}

fun ApolloClient.Builder.store(store: ApolloStore, writeToCacheAsynchronously: Boolean = false): ApolloClient.Builder {
check(interceptors.none { it is AutoPersistedQueryInterceptor }) {
"Apollo: the normalized cache must be configured before the auto persisted queries"
private class DefaultInterceptorChain(
private val interceptors: List<ApolloInterceptor>,
private val index: Int,
) : ApolloInterceptorChain {

override fun <D : Operation.Data> proceed(request: ApolloRequest<D>): Flow<ApolloResponse<D>> {
check(index < interceptors.size)
return interceptors[index].intercept(
request,
DefaultInterceptorChain(
interceptors = interceptors,
index = index + 1,
)
)
}
}

private fun ApolloInterceptorChain.asInterceptor(): ApolloInterceptor {
return object : ApolloInterceptor {
override fun <D : Operation.Data> intercept(
request: ApolloRequest<D>,
chain: ApolloInterceptorChain,
): Flow<ApolloResponse<D>> {
return this@asInterceptor.proceed(request)
}
}
// Removing existing interceptors added for configuring an [ApolloStore].
// If a builder is reused from an existing client using `newBuilder()` and we try to configure a new `store()` on it, we first need to
// remove the old interceptors.
val storeInterceptors = interceptors.filterIsInstance<ApolloStoreInterceptor>()
storeInterceptors.forEach {
removeInterceptor(it)
Comment on lines -126 to -131
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not an issue anymore!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe the ApolloStoreInterceptor interface can also be deleted, then?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call! → #6612

}
internal class CacheInterceptor(val store: ApolloStore): ApolloInterceptor {
private val delegates = listOf(
WatcherInterceptor(store),
FetchPolicyRouterInterceptor,
ApolloCacheInterceptor(store)
)

override fun <D : Operation.Data> intercept(
request: ApolloRequest<D>,
chain: ApolloInterceptorChain,
): Flow<ApolloResponse<D>> {
return DefaultInterceptorChain(delegates + chain.asInterceptor(), 0).proceed(request)
}
return addInterceptor(WatcherInterceptor(store))
.addInterceptor(FetchPolicyRouterInterceptor)
.addInterceptor(ApolloCacheInterceptor(store))
}


fun ApolloClient.Builder.store(store: ApolloStore, writeToCacheAsynchronously: Boolean = false): ApolloClient.Builder {
return cacheInterceptor(CacheInterceptor(store))
.writeToCacheAsynchronously(writeToCacheAsynchronously)
.addExecutionContext(CacheDumpProviderContext(store.cacheDumpProvider()))
}
Expand Down Expand Up @@ -228,9 +256,7 @@ internal fun <D : Query.Data> ApolloCall<D>.watchInternal(data: D?): Flow<Apollo

val ApolloClient.apolloStore: ApolloStore
get() {
return interceptors.firstOrNull { it is ApolloCacheInterceptor }?.let {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this no longer works, and should be cacheInterceptor (but then it must be made public)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but then it must be made public

I have made it public

(it as ApolloCacheInterceptor).store
} ?: error("no cache configured")
return (cacheInterceptor as? CacheInterceptor ?: error("no cache configured")).store
}

/**
Expand Down
5 changes: 5 additions & 0 deletions libraries/apollo-runtime/api/android/apollo-runtime.api
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ public final class com/apollographql/apollo/ApolloClient : com/apollographql/apo
public fun close ()V
public final fun dispose ()V
public final fun executeAsFlow (Lcom/apollographql/apollo/api/ApolloRequest;)Lkotlinx/coroutines/flow/Flow;
public final fun getCacheInterceptor ()Lcom/apollographql/apollo/interceptor/ApolloInterceptor;
public fun getCanBeBatched ()Ljava/lang/Boolean;
public final fun getCustomScalarAdapters ()Lcom/apollographql/apollo/api/CustomScalarAdapters;
public fun getEnableAutoPersistedQueries ()Ljava/lang/Boolean;
Expand Down Expand Up @@ -76,7 +77,9 @@ public final class com/apollographql/apollo/ApolloClient$Builder : com/apollogra
public final fun autoPersistedQueries (Lcom/apollographql/apollo/api/http/HttpMethod;Lcom/apollographql/apollo/api/http/HttpMethod;)Lcom/apollographql/apollo/ApolloClient$Builder;
public final fun autoPersistedQueries (Lcom/apollographql/apollo/api/http/HttpMethod;Lcom/apollographql/apollo/api/http/HttpMethod;Z)Lcom/apollographql/apollo/ApolloClient$Builder;
public static synthetic fun autoPersistedQueries$default (Lcom/apollographql/apollo/ApolloClient$Builder;Lcom/apollographql/apollo/api/http/HttpMethod;Lcom/apollographql/apollo/api/http/HttpMethod;ZILjava/lang/Object;)Lcom/apollographql/apollo/ApolloClient$Builder;
public final fun autoPersistedQueriesInterceptor (Lcom/apollographql/apollo/interceptor/ApolloInterceptor;)Lcom/apollographql/apollo/ApolloClient$Builder;
public final fun build ()Lcom/apollographql/apollo/ApolloClient;
public final fun cacheInterceptor (Lcom/apollographql/apollo/interceptor/ApolloInterceptor;)Lcom/apollographql/apollo/ApolloClient$Builder;
public fun canBeBatched (Ljava/lang/Boolean;)Lcom/apollographql/apollo/ApolloClient$Builder;
public synthetic fun canBeBatched (Ljava/lang/Boolean;)Ljava/lang/Object;
public final fun copy ()Lcom/apollographql/apollo/ApolloClient$Builder;
Expand All @@ -86,6 +89,8 @@ public final class com/apollographql/apollo/ApolloClient$Builder : com/apollogra
public synthetic fun enableAutoPersistedQueries (Ljava/lang/Boolean;)Ljava/lang/Object;
public final fun executionContext (Lcom/apollographql/apollo/api/ExecutionContext;)Lcom/apollographql/apollo/ApolloClient$Builder;
public final fun failFastIfOffline (Ljava/lang/Boolean;)Lcom/apollographql/apollo/ApolloClient$Builder;
public final fun getAutoPersistedQueryInterceptor ()Lcom/apollographql/apollo/interceptor/ApolloInterceptor;
public final fun getCacheInterceptor ()Lcom/apollographql/apollo/interceptor/ApolloInterceptor;
public fun getCanBeBatched ()Ljava/lang/Boolean;
public final fun getCustomScalarAdapters ()Lcom/apollographql/apollo/api/CustomScalarAdapters;
public final fun getDispatcher ()Lkotlinx/coroutines/CoroutineDispatcher;
Expand Down
8 changes: 8 additions & 0 deletions libraries/apollo-runtime/api/apollo-runtime.klib.api
Original file line number Diff line number Diff line change
Expand Up @@ -586,6 +586,8 @@ final class com.apollographql.apollo.network.ws/WebSocketNetworkTransport : com.
}

final class com.apollographql.apollo/ApolloClient : com.apollographql.apollo.api/ExecutionOptions, okio/Closeable { // com.apollographql.apollo/ApolloClient|null[0]
final val cacheInterceptor // com.apollographql.apollo/ApolloClient.cacheInterceptor|{}cacheInterceptor[0]
final fun <get-cacheInterceptor>(): com.apollographql.apollo.interceptor/ApolloInterceptor? // com.apollographql.apollo/ApolloClient.cacheInterceptor.<get-cacheInterceptor>|<get-cacheInterceptor>(){}[0]
final val canBeBatched // com.apollographql.apollo/ApolloClient.canBeBatched|{}canBeBatched[0]
final fun <get-canBeBatched>(): kotlin/Boolean? // com.apollographql.apollo/ApolloClient.canBeBatched.<get-canBeBatched>|<get-canBeBatched>(){}[0]
final val customScalarAdapters // com.apollographql.apollo/ApolloClient.customScalarAdapters|{}customScalarAdapters[0]
Expand Down Expand Up @@ -627,6 +629,10 @@ final class com.apollographql.apollo/ApolloClient : com.apollographql.apollo.api
final val interceptors // com.apollographql.apollo/ApolloClient.Builder.interceptors|{}interceptors[0]
final fun <get-interceptors>(): kotlin.collections/List<com.apollographql.apollo.interceptor/ApolloInterceptor> // com.apollographql.apollo/ApolloClient.Builder.interceptors.<get-interceptors>|<get-interceptors>(){}[0]

final var autoPersistedQueryInterceptor // com.apollographql.apollo/ApolloClient.Builder.autoPersistedQueryInterceptor|{}autoPersistedQueryInterceptor[0]
final fun <get-autoPersistedQueryInterceptor>(): com.apollographql.apollo.interceptor/ApolloInterceptor? // com.apollographql.apollo/ApolloClient.Builder.autoPersistedQueryInterceptor.<get-autoPersistedQueryInterceptor>|<get-autoPersistedQueryInterceptor>(){}[0]
final var cacheInterceptor // com.apollographql.apollo/ApolloClient.Builder.cacheInterceptor|{}cacheInterceptor[0]
final fun <get-cacheInterceptor>(): com.apollographql.apollo.interceptor/ApolloInterceptor? // com.apollographql.apollo/ApolloClient.Builder.cacheInterceptor.<get-cacheInterceptor>|<get-cacheInterceptor>(){}[0]
final var canBeBatched // com.apollographql.apollo/ApolloClient.Builder.canBeBatched|{}canBeBatched[0]
final fun <get-canBeBatched>(): kotlin/Boolean? // com.apollographql.apollo/ApolloClient.Builder.canBeBatched.<get-canBeBatched>|<get-canBeBatched>(){}[0]
final var dispatcher // com.apollographql.apollo/ApolloClient.Builder.dispatcher|{}dispatcher[0]
Expand Down Expand Up @@ -680,7 +686,9 @@ final class com.apollographql.apollo/ApolloClient : com.apollographql.apollo.api
final fun addInterceptors(kotlin.collections/List<com.apollographql.apollo.interceptor/ApolloInterceptor>): com.apollographql.apollo/ApolloClient.Builder // com.apollographql.apollo/ApolloClient.Builder.addInterceptors|addInterceptors(kotlin.collections.List<com.apollographql.apollo.interceptor.ApolloInterceptor>){}[0]
final fun addListener(com.apollographql.apollo.internal/ApolloClientListener): com.apollographql.apollo/ApolloClient.Builder // com.apollographql.apollo/ApolloClient.Builder.addListener|addListener(com.apollographql.apollo.internal.ApolloClientListener){}[0]
final fun autoPersistedQueries(com.apollographql.apollo.api.http/HttpMethod = ..., com.apollographql.apollo.api.http/HttpMethod = ..., kotlin/Boolean = ...): com.apollographql.apollo/ApolloClient.Builder // com.apollographql.apollo/ApolloClient.Builder.autoPersistedQueries|autoPersistedQueries(com.apollographql.apollo.api.http.HttpMethod;com.apollographql.apollo.api.http.HttpMethod;kotlin.Boolean){}[0]
final fun autoPersistedQueriesInterceptor(com.apollographql.apollo.interceptor/ApolloInterceptor?): com.apollographql.apollo/ApolloClient.Builder // com.apollographql.apollo/ApolloClient.Builder.autoPersistedQueriesInterceptor|autoPersistedQueriesInterceptor(com.apollographql.apollo.interceptor.ApolloInterceptor?){}[0]
final fun build(): com.apollographql.apollo/ApolloClient // com.apollographql.apollo/ApolloClient.Builder.build|build(){}[0]
final fun cacheInterceptor(com.apollographql.apollo.interceptor/ApolloInterceptor?): com.apollographql.apollo/ApolloClient.Builder // com.apollographql.apollo/ApolloClient.Builder.cacheInterceptor|cacheInterceptor(com.apollographql.apollo.interceptor.ApolloInterceptor?){}[0]
final fun canBeBatched(kotlin/Boolean?): com.apollographql.apollo/ApolloClient.Builder // com.apollographql.apollo/ApolloClient.Builder.canBeBatched|canBeBatched(kotlin.Boolean?){}[0]
final fun copy(): com.apollographql.apollo/ApolloClient.Builder // com.apollographql.apollo/ApolloClient.Builder.copy|copy(){}[0]
final fun customScalarAdapters(com.apollographql.apollo.api/CustomScalarAdapters): com.apollographql.apollo/ApolloClient.Builder // com.apollographql.apollo/ApolloClient.Builder.customScalarAdapters|customScalarAdapters(com.apollographql.apollo.api.CustomScalarAdapters){}[0]
Expand Down
5 changes: 5 additions & 0 deletions libraries/apollo-runtime/api/jvm/apollo-runtime.api
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ public final class com/apollographql/apollo/ApolloClient : com/apollographql/apo
public fun close ()V
public final fun dispose ()V
public final fun executeAsFlow (Lcom/apollographql/apollo/api/ApolloRequest;)Lkotlinx/coroutines/flow/Flow;
public final fun getCacheInterceptor ()Lcom/apollographql/apollo/interceptor/ApolloInterceptor;
public fun getCanBeBatched ()Ljava/lang/Boolean;
public final fun getCustomScalarAdapters ()Lcom/apollographql/apollo/api/CustomScalarAdapters;
public fun getEnableAutoPersistedQueries ()Ljava/lang/Boolean;
Expand Down Expand Up @@ -76,7 +77,9 @@ public final class com/apollographql/apollo/ApolloClient$Builder : com/apollogra
public final fun autoPersistedQueries (Lcom/apollographql/apollo/api/http/HttpMethod;Lcom/apollographql/apollo/api/http/HttpMethod;)Lcom/apollographql/apollo/ApolloClient$Builder;
public final fun autoPersistedQueries (Lcom/apollographql/apollo/api/http/HttpMethod;Lcom/apollographql/apollo/api/http/HttpMethod;Z)Lcom/apollographql/apollo/ApolloClient$Builder;
public static synthetic fun autoPersistedQueries$default (Lcom/apollographql/apollo/ApolloClient$Builder;Lcom/apollographql/apollo/api/http/HttpMethod;Lcom/apollographql/apollo/api/http/HttpMethod;ZILjava/lang/Object;)Lcom/apollographql/apollo/ApolloClient$Builder;
public final fun autoPersistedQueriesInterceptor (Lcom/apollographql/apollo/interceptor/ApolloInterceptor;)Lcom/apollographql/apollo/ApolloClient$Builder;
public final fun build ()Lcom/apollographql/apollo/ApolloClient;
public final fun cacheInterceptor (Lcom/apollographql/apollo/interceptor/ApolloInterceptor;)Lcom/apollographql/apollo/ApolloClient$Builder;
public fun canBeBatched (Ljava/lang/Boolean;)Lcom/apollographql/apollo/ApolloClient$Builder;
public synthetic fun canBeBatched (Ljava/lang/Boolean;)Ljava/lang/Object;
public final fun copy ()Lcom/apollographql/apollo/ApolloClient$Builder;
Expand All @@ -86,6 +89,8 @@ public final class com/apollographql/apollo/ApolloClient$Builder : com/apollogra
public synthetic fun enableAutoPersistedQueries (Ljava/lang/Boolean;)Ljava/lang/Object;
public final fun executionContext (Lcom/apollographql/apollo/api/ExecutionContext;)Lcom/apollographql/apollo/ApolloClient$Builder;
public final fun failFastIfOffline (Ljava/lang/Boolean;)Lcom/apollographql/apollo/ApolloClient$Builder;
public final fun getAutoPersistedQueryInterceptor ()Lcom/apollographql/apollo/interceptor/ApolloInterceptor;
public final fun getCacheInterceptor ()Lcom/apollographql/apollo/interceptor/ApolloInterceptor;
public fun getCanBeBatched ()Ljava/lang/Boolean;
public final fun getCustomScalarAdapters ()Lcom/apollographql/apollo/api/CustomScalarAdapters;
public final fun getDispatcher ()Lkotlinx/coroutines/CoroutineDispatcher;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ private constructor(
val subscriptionNetworkTransport: NetworkTransport
val interceptors: List<ApolloInterceptor> = builder.interceptors
val customScalarAdapters: CustomScalarAdapters = builder.customScalarAdapters
val cacheInterceptor: ApolloInterceptor? = builder.cacheInterceptor
private val autoPersistedQueryInterceptor: ApolloInterceptor? = builder.autoPersistedQueryInterceptor
private val retryOnError: ((ApolloRequest<*>) -> Boolean)? = builder.retryOnError
private val retryOnErrorInterceptor: ApolloInterceptor? = builder.retryOnErrorInterceptor
private val failFastIfOffline = builder.failFastIfOffline
Expand Down Expand Up @@ -315,6 +317,12 @@ private constructor(

val allInterceptors = buildList {
addAll(interceptors)
if (cacheInterceptor != null) {
add(cacheInterceptor)
}
if (autoPersistedQueryInterceptor != null) {
add(autoPersistedQueryInterceptor)
}
add(retryOnErrorInterceptor ?: RetryOnErrorInterceptor())
add(networkInterceptor)
}
Expand Down Expand Up @@ -407,6 +415,12 @@ private constructor(
var failFastIfOffline: Boolean? = null
private set

var cacheInterceptor: ApolloInterceptor? = null
private set

var autoPersistedQueryInterceptor: ApolloInterceptor? = null
private set

/**
* Whether to fail fast if the device is offline.
* Requires setting an interceptor that is aware of the network state with [retryOnErrorInterceptor].
Expand Down Expand Up @@ -466,6 +480,24 @@ private constructor(
this.retryOnErrorInterceptor = retryOnErrorInterceptor
}

/**
* Sets the [ApolloInterceptor] used for caching.
*
* @see addInterceptor
*/
fun cacheInterceptor(cacheInterceptor: ApolloInterceptor?) = apply {
this.cacheInterceptor = cacheInterceptor
}

/**
* Sets the [ApolloInterceptor] used for auto persisted queries.
*
* @see addInterceptor
*/
fun autoPersistedQueriesInterceptor(autoPersistedQueryInterceptor: ApolloInterceptor?) = apply {
this.autoPersistedQueryInterceptor = autoPersistedQueryInterceptor
}

/**
* Configures the [HttpMethod] to use.
*
Expand Down Expand Up @@ -791,8 +823,18 @@ private constructor(
* such as normalized cache and auto persisted queries. [ApolloClient] also inserts a terminating [ApolloInterceptor] that
* executes the request.
*
* **The order is important**. The [ApolloInterceptor]s are executed in the order they are added. Because cache and APQs also
* use interceptors, the order of the cache/APQs configuration also influences the final interceptor list.
* **The order is important**. The [ApolloInterceptor]s are added in the order they are added and always added before
* the built-in intercepted:
*
* - user interceptors
* - cacheInterceptor
* - autoPersistedQueriesInterceptor
* - retryOnErrorInterceptor
* - networkInterceptor
*
* @see cacheInterceptor
* @see autoPersistedQueriesInterceptor
* @see retryOnErrorInterceptor
*/
fun addInterceptor(interceptor: ApolloInterceptor) = apply {
_interceptors.add(interceptor)
Expand Down Expand Up @@ -878,8 +920,7 @@ private constructor(
httpMethodForDocumentQueries: HttpMethod = HttpMethod.Post,
enableByDefault: Boolean = true,
) = apply {
_interceptors.removeAll { it is AutoPersistedQueryInterceptor }
addInterceptor(
autoPersistedQueriesInterceptor(
AutoPersistedQueryInterceptor(
httpMethodForHashedQueries,
httpMethodForDocumentQueries
Expand Down Expand Up @@ -915,9 +956,9 @@ private constructor(
* Creates an [ApolloClient] from this [Builder]
*/
fun build(): ApolloClient {
return ApolloClient(
this.copy()
)
// Copy the builder so that any subsequent modifications of the builder
// doesn't change the ApolloClient owned one
return ApolloClient(copy())
}

fun copy(): Builder {
Expand Down Expand Up @@ -946,6 +987,8 @@ private constructor(
.wsProtocol(wsProtocolFactory)
.retryOnError(retryOnError)
.retryOnErrorInterceptor(retryOnErrorInterceptor)
.cacheInterceptor(cacheInterceptor)
.autoPersistedQueriesInterceptor(autoPersistedQueryInterceptor)
.failFastIfOffline(failFastIfOffline)
.listeners(listeners)
}
Expand Down
7 changes: 3 additions & 4 deletions tests/http-headers/src/test/kotlin/HttpHeaderTest.kt
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

import com.apollographql.apollo.ApolloClient
import com.apollographql.apollo.api.DefaultUpload
import com.apollographql.apollo.api.Optional
Expand Down Expand Up @@ -28,7 +27,7 @@ class HttpHeadersTest {
@Test
fun getRequestsSendPreflightHeader() = mockServerTest(
clientBuilder = { autoPersistedQueries() }
){
) {

mockServer.enqueueString("")
apolloClient.query(GetRandomQuery()).enableAutoPersistedQueries(true).execute()
Expand Down Expand Up @@ -63,14 +62,14 @@ class MockServerTest(val mockServer: MockServer, val apolloClient: ApolloClient,

fun mockServerTest(
clientBuilder: ApolloClient.Builder.() -> Unit = {},
block: suspend MockServerTest.() -> Unit
block: suspend MockServerTest.() -> Unit,
) = runTest {
MockServer().use { mockServer ->
ApolloClient.Builder()
.serverUrl(mockServer.url())
.apply(clientBuilder)
.build()
.use {apolloClient ->
.use { apolloClient ->
MockServerTest(mockServer, apolloClient, this).block()
}
}
Expand Down
Loading
Loading