apollo-kotlin
apollo-kotlin copied to clipboard
Stuck in constant query loop
Summary I have two different queries that return the same data fragment (cached by id) that changes every time it is fetched from the server. When one query completes it causes the other query to fire again and gets into a never-ending loop.
Description
I have a schema that has something like this: Site ..[Photo]
Gallery ..[Photo]
Example: I fetch a site with a photo with id "1", and then I fetch a gallery which returns the same photo with id "1" but with some different data, the site query fires again and returns photo with id "1" and then it causes the gallery query to fire, repeat indefinitely.
Am I doing something wrong? Why do the queries fire again when the underlying data changes, shouldn't it just update in cache?
Version 1.4.5 / 2.0.0 I did not see this issue in 1.2.0
Ah, I have to set refetchResponseFetcher on the watcher to CACHE_ONLY. Shouldn't that be the default?
strange, as default fetcher for watchers is ApolloResponseFetchers.CACHE_FIRST
so it should hit cache first and skip network if there is data in cache.
Is this issue still valid? @giantramen can you share your queries and how you are executing them (coroutines, rxjava, callback, etc...)? Also, do you use a CustomKeyResolver?
this happens to be too, but my use case is more complicated and so i cannot provide queries. I use coroutines this is my keyresolver
class DefaultCacheKeyResolver : CacheKeyResolver() {
override fun fromFieldArguments(field: ResponseField, variables: Operation.Variables): CacheKey {
return formatCacheKey(field.resolveArgument("id", variables) as? String)
}
override fun fromFieldRecordSet(field: ResponseField, recordSet: Map<String, Any>): CacheKey {
return formatCacheKey(recordSet["id"] as? String)
}
private fun formatCacheKey(id: String?): CacheKey {
return if (id == null || id.isEmpty()) {
CacheKey.NO_KEY
} else {
CacheKey.from(id)
}
}
}
@nicusorflorin if you get a chance, it would be interesting to investigate why there's a cache miss. The process is not straightfoward at the moment and we should definitely make it easier (see https://github.com/apollographql/apollo-android/issues/2397). But you should be able to get some information about the missing key (if any) by adding breakpoints to CacheFieldValueResolver: https://github.com/apollographql/apollo-android/blob/905620cd98dfb5cbb07b5a540558c1cc761c45e1/apollo-normalized-cache/src/commonMain/kotlin/com/apollographql/apollo/cache/normalized/internal/CacheFieldValueResolver.kt#L42
@martinbonnin i'm getting Missing value: outer
for fieldKey: outer({"first":150})
I have a query that gets the ids for several lists, then i query each list for it's elements. sometimes it seems to loop itself. if i check the logs only the main query keeps calling the api again and again. note that this doesn't happen always
Thanks. It might be the pagination parameters that trigger the cache miss. I'll try to reproduce.
There aren't any except the "first" tried with a page size of "2" and it happens even more often
There aren't any except the "first" tried with a page size of "2" and it happens even more often
Not sure I get it. There aren't any cache miss? Or are pagination parameters always the same?
for the main query the one that gets looped they are always the same. only a page size is specified via the "first" parameter. regarding cache misses, i do keep getting that "outer" error when it loops
I'm encountering a similar infinite refresh loop on the watcher when a query returns two objects of the same type with the same id. ex:
query FetchBuildings() {
user {
id
primaryBuilding {
id
name
}
buildings {
id
}
}
}
apolloClient.query(FetchBuildingsQuery())
.toBuilder().responseFetcher(ApolloResponseFetchers.CACHE_ONLY).build()
.watcher().toFlow().collect { response ->
Timber.d("Response is valid ${response.data?.user?.buildings?.size}")
}
I'm using the ID cache key resolver since all objects are fetched with a UUID. When I comment out primaryBuilding
the query and watchers work as normal. Even when I set it to CACHE_ONLY
it fetches network as per the profiler. The returned response every time the watcher is called (5-6 times a second) is correct.
Been stuck on this issue for a while, would love any help!
@Nealsoni00 It seems weird that CACHE_ONLY
watcher would go to the network, I wouldn't expect that to happen. How do you trigger the cache update? Do you have another query that goes to the network? Or do you write the cache directly?
I tried putting together a reproducer there: https://github.com/martinbonnin/apollo-android-samples/blob/aabd437becac27d251f1691ea257bd47b70d3b9d/query-loop/src/test/kotlin/MainTest.kt#L57
Any chance you can see what's different from your use case? That'll help a lot.
Hi Martin, The one difference I see is that I have a different query run on the initialization of the application (before the main page appears). That query has the same data as the FetchBuildingsQuery and a lot more (initializing user app state, etc).
How many watchers do you have ultimately? I feel like this issue should only happen with two watchers? And the initialization query you're doing doesn't seem like a good candidate to be a watcher. Or is it?
I commented out all other watchers in the entire application. That is the only one that is left. The initialization query is not itself being watched. The watcher is so that if any other views call the GetBuildingsQuery that interface is updated.
I see, it's the same query being watched from different places. Well that's unexpected given that's exactly what my reproducer does. Maybe it's a timing issue somewhere, having a reproducer somewhere would be super useful.
@Nealsoni00 I know this thread is old but did you resolve your issue? I'm encountering the same problem where a CACHE_ONLY
watcher seemingly triggers a network request and I can't figure out why.
@Nealsoni00 I know this thread is old but did you resolve your issue? I'm encountering the same problem where a
CACHE_ONLY
watcher seemingly triggers a network request and I can't figure out why.
Sorry have since moved off native android. I vaguely remember it being solved by ensuring the retuned data only returned an object with ID XXXXX once. (No fetching same object more than once).
Hello everyone 👋 This issue is quite the oldie and I'm not sure how to move forward without a reproducer. I'm going to close it for now but if anyone wants to deep dive in this again, please feel free to leave a comment and I'll reopen.
Do you have any feedback for the maintainers? Please tell us by taking a one-minute survey. Your responses will help us understand Apollo Kotlin usage and allow us to serve you better.