Skip to content

Commit

Permalink
Clear current ApolloStore related interceptors when calling `.store()…
Browse files Browse the repository at this point in the history
…` on builder (#5857)

* Clear old ApolloStore related interceptors when using .store

* Update .api for removeInterceptor

* Report changes to the incubating artifact

---------

Co-authored-by: BoD <BoD@JRAF.org>
  • Loading branch information
rohandhruva and BoD authored Apr 29, 2024
1 parent 7d5c7c7 commit a8737d1
Show file tree
Hide file tree
Showing 14 changed files with 63 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import com.apollographql.apollo3.cache.normalized.api.Record
import com.apollographql.apollo3.cache.normalized.api.RecordMerger
import com.apollographql.apollo3.cache.normalized.api.TypePolicyCacheKeyGenerator
import com.apollographql.apollo3.cache.normalized.internal.DefaultApolloStore
import com.apollographql.apollo3.interceptor.ApolloInterceptor
import com.benasher44.uuid.Uuid
import kotlinx.coroutines.flow.SharedFlow
import kotlin.reflect.KClass
Expand Down Expand Up @@ -235,3 +236,8 @@ fun ApolloStore(
fieldNameGenerator = fieldNameGenerator,
embeddedFieldsProvider = embeddedFieldsProvider,
)

/**
* Interface that marks all interceptors added when configuring a `store()` on ApolloClient.Builder.
*/
internal interface ApolloStoreInterceptor : ApolloInterceptor
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,13 @@ fun ApolloClient.Builder.store(
check(interceptors.none { it is AutoPersistedQueryInterceptor }) {
"Apollo: the normalized cache must be configured before the auto persisted queries"
}
// 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)
}
return addInterceptor(WatcherInterceptor(store))
.addInterceptor(FetchPolicyRouterInterceptor)
.addInterceptor(ApolloCacheInterceptor(store))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ val CacheAndNetworkInterceptor = object : ApolloInterceptor {
}
}

internal object FetchPolicyRouterInterceptor : ApolloInterceptor {
internal object FetchPolicyRouterInterceptor : ApolloInterceptor, ApolloStoreInterceptor {
override fun <D : Operation.Data> intercept(request: ApolloRequest<D>, chain: ApolloInterceptorChain): Flow<ApolloResponse<D>> {
if (request.operation !is Query) {
// Subscriptions and Mutations do not support fetchPolicies
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import com.apollographql.apollo3.api.Operation
import com.apollographql.apollo3.api.Query
import com.apollographql.apollo3.api.Subscription
import com.apollographql.apollo3.cache.normalized.ApolloStore
import com.apollographql.apollo3.cache.normalized.ApolloStoreInterceptor
import com.apollographql.apollo3.cache.normalized.CacheInfo
import com.apollographql.apollo3.cache.normalized.api.ApolloCacheHeaders
import com.apollographql.apollo3.cache.normalized.api.CacheHeaders
Expand Down Expand Up @@ -36,7 +37,7 @@ import kotlinx.coroutines.launch

internal class ApolloCacheInterceptor(
val store: ApolloStore,
) : ApolloInterceptor {
) : ApolloInterceptor, ApolloStoreInterceptor {
private suspend fun <D : Operation.Data> maybeAsync(request: ApolloRequest<D>, block: suspend () -> Unit) {
if (request.writeToCacheAsynchronously) {
val scope = request.executionContext[ConcurrencyInfo]!!.coroutineScope
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import com.apollographql.apollo3.api.CustomScalarAdapters
import com.apollographql.apollo3.api.Operation
import com.apollographql.apollo3.api.Query
import com.apollographql.apollo3.cache.normalized.ApolloStore
import com.apollographql.apollo3.cache.normalized.ApolloStoreInterceptor
import com.apollographql.apollo3.cache.normalized.api.dependentKeys
import com.apollographql.apollo3.cache.normalized.watchContext
import com.apollographql.apollo3.interceptor.ApolloInterceptor
Expand All @@ -17,7 +18,7 @@ import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach

internal class WatcherInterceptor(val store: ApolloStore) : ApolloInterceptor {
internal class WatcherInterceptor(val store: ApolloStore) : ApolloInterceptor, ApolloStoreInterceptor {
override fun <D : Operation.Data> intercept(request: ApolloRequest<D>, chain: ApolloInterceptorChain): Flow<ApolloResponse<D>> {
val watchContext = request.watchContext ?: return chain.proceed(request)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import com.apollographql.apollo3.cache.normalized.api.NormalizedCacheFactory
import com.apollographql.apollo3.cache.normalized.api.Record
import com.apollographql.apollo3.cache.normalized.api.TypePolicyCacheKeyGenerator
import com.apollographql.apollo3.cache.normalized.internal.DefaultApolloStore
import com.apollographql.apollo3.interceptor.ApolloInterceptor
import com.benasher44.uuid.Uuid
import kotlinx.coroutines.flow.SharedFlow
import kotlin.reflect.KClass
Expand Down Expand Up @@ -265,3 +266,8 @@ fun ApolloStore(
cacheKeyGenerator: CacheKeyGenerator = TypePolicyCacheKeyGenerator,
cacheResolver: CacheResolver = FieldPolicyCacheResolver,
): ApolloStore = DefaultApolloStore(normalizedCacheFactory, cacheKeyGenerator, cacheResolver)

/**
* Interface that marks all interceptors added when configuring a `store()` on ApolloClient.Builder.
*/
internal interface ApolloStoreInterceptor : ApolloInterceptor
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,13 @@ fun ApolloClient.Builder.store(store: ApolloStore, writeToCacheAsynchronously: B
check(interceptors.none { it is AutoPersistedQueryInterceptor }) {
"Apollo: the normalized cache must be configured before the auto persisted queries"
}
// 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)
}
return addInterceptor(WatcherInterceptor(store))
.addInterceptor(FetchPolicyRouterInterceptor)
.addInterceptor(ApolloCacheInterceptor(store))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ val CacheAndNetworkInterceptor = object : ApolloInterceptor {
}
}

internal object FetchPolicyRouterInterceptor : ApolloInterceptor {
internal object FetchPolicyRouterInterceptor : ApolloInterceptor, ApolloStoreInterceptor {
override fun <D : Operation.Data> intercept(request: ApolloRequest<D>, chain: ApolloInterceptorChain): Flow<ApolloResponse<D>> {
if (request.operation !is Query) {
// Subscriptions and Mutations do not support fetchPolicies
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import com.apollographql.apollo3.api.Operation
import com.apollographql.apollo3.api.Query
import com.apollographql.apollo3.api.Subscription
import com.apollographql.apollo3.cache.normalized.ApolloStore
import com.apollographql.apollo3.cache.normalized.ApolloStoreInterceptor
import com.apollographql.apollo3.cache.normalized.CacheInfo
import com.apollographql.apollo3.cache.normalized.api.ApolloCacheHeaders
import com.apollographql.apollo3.cache.normalized.api.CacheHeaders
Expand Down Expand Up @@ -36,7 +37,7 @@ import kotlinx.coroutines.launch

internal class ApolloCacheInterceptor(
val store: ApolloStore,
) : ApolloInterceptor {
) : ApolloInterceptor, ApolloStoreInterceptor {
private suspend fun <D : Operation.Data> maybeAsync(request: ApolloRequest<D>, block: suspend () -> Unit) {
if (request.writeToCacheAsynchronously) {
val scope = request.executionContext[ConcurrencyInfo]!!.coroutineScope
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import com.apollographql.apollo3.api.CustomScalarAdapters
import com.apollographql.apollo3.api.Operation
import com.apollographql.apollo3.api.Query
import com.apollographql.apollo3.cache.normalized.ApolloStore
import com.apollographql.apollo3.cache.normalized.ApolloStoreInterceptor
import com.apollographql.apollo3.cache.normalized.api.dependentKeys
import com.apollographql.apollo3.cache.normalized.watchContext
import com.apollographql.apollo3.exception.DefaultApolloException
Expand All @@ -23,7 +24,7 @@ import kotlinx.coroutines.flow.onSubscription

internal val WatcherSentinel = DefaultApolloException("The watcher has started")

internal class WatcherInterceptor(val store: ApolloStore) : ApolloInterceptor {
internal class WatcherInterceptor(val store: ApolloStore) : ApolloInterceptor, ApolloStoreInterceptor {
override fun <D : Operation.Data> intercept(request: ApolloRequest<D>, chain: ApolloInterceptorChain): Flow<ApolloResponse<D>> {
val watchContext = request.watchContext ?: return chain.proceed(request)

Expand Down
1 change: 1 addition & 0 deletions libraries/apollo-runtime/api/android/apollo-runtime.api
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ public final class com/apollographql/apollo3/ApolloClient$Builder : com/apollogr
public final fun httpServerUrl (Ljava/lang/String;)Lcom/apollographql/apollo3/ApolloClient$Builder;
public final fun interceptors (Ljava/util/List;)Lcom/apollographql/apollo3/ApolloClient$Builder;
public final fun networkTransport (Lcom/apollographql/apollo3/network/NetworkTransport;)Lcom/apollographql/apollo3/ApolloClient$Builder;
public final fun removeInterceptor (Lcom/apollographql/apollo3/interceptor/ApolloInterceptor;)Lcom/apollographql/apollo3/ApolloClient$Builder;
public fun sendApqExtensions (Ljava/lang/Boolean;)Lcom/apollographql/apollo3/ApolloClient$Builder;
public synthetic fun sendApqExtensions (Ljava/lang/Boolean;)Ljava/lang/Object;
public fun sendDocument (Ljava/lang/Boolean;)Lcom/apollographql/apollo3/ApolloClient$Builder;
Expand Down
1 change: 1 addition & 0 deletions libraries/apollo-runtime/api/jvm/apollo-runtime.api
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ public final class com/apollographql/apollo3/ApolloClient$Builder : com/apollogr
public final fun httpServerUrl (Ljava/lang/String;)Lcom/apollographql/apollo3/ApolloClient$Builder;
public final fun interceptors (Ljava/util/List;)Lcom/apollographql/apollo3/ApolloClient$Builder;
public final fun networkTransport (Lcom/apollographql/apollo3/network/NetworkTransport;)Lcom/apollographql/apollo3/ApolloClient$Builder;
public final fun removeInterceptor (Lcom/apollographql/apollo3/interceptor/ApolloInterceptor;)Lcom/apollographql/apollo3/ApolloClient$Builder;
public fun sendApqExtensions (Ljava/lang/Boolean;)Lcom/apollographql/apollo3/ApolloClient$Builder;
public synthetic fun sendApqExtensions (Ljava/lang/Boolean;)Ljava/lang/Object;
public fun sendDocument (Ljava/lang/Boolean;)Lcom/apollographql/apollo3/ApolloClient$Builder;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -776,6 +776,15 @@ private constructor(
_interceptors.add(interceptor)
}

/**
* Removes an [ApolloInterceptor] from this [ApolloClient].
*
* **The order is important**. This method removes the first occurrence of the [ApolloInterceptor] in the list.
*/
fun removeInterceptor(interceptor: ApolloInterceptor) = apply {
_interceptors.remove(interceptor)
}

/**
* Adds several [ApolloInterceptor]s to this [ApolloClient].
*
Expand Down
18 changes: 16 additions & 2 deletions tests/integration-tests/src/commonTest/kotlin/test/StoreTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,17 @@ class StoreTest {
assertFriendIsNotCached("1003")
}

@Test
fun testNewBuilderNewStore() = runTest(before = { setUp() }) {
storeAllFriends()
assertFriendIsCached("1000", "Luke Skywalker")

val newStore = ApolloStore(MemoryCacheFactory())
val newClient = apolloClient.newBuilder().store(newStore).build()

assertFriendIsNotCached("1000", newClient)
}

private suspend fun storeAllFriends() {
val query = HeroAndFriendsNamesWithIDsQuery(Episode.NEWHOPE)
apolloClient.enqueueTestResponse(query, HeroAndFriendsNamesWithIDsQuery.Data(
Expand Down Expand Up @@ -158,9 +169,12 @@ class StoreTest {
assertEquals2(characterResponse.data?.character?.name, name)
}

private suspend fun assertFriendIsNotCached(id: String) {
private suspend fun assertFriendIsNotCached(
id: String,
apolloClientToUse: ApolloClient = apolloClient,
) {
assertIs<CacheMissException>(
apolloClient.query(CharacterNameByIdQuery(id))
apolloClientToUse.query(CharacterNameByIdQuery(id))
.fetchPolicy(FetchPolicy.CacheOnly)
.execute()
.exception
Expand Down

0 comments on commit a8737d1

Please sign in to comment.