Stylianos Gakis
07/18/2024, 1:26 PMCacheMissException
), do not emit anything and just continue with the network request
β’ Then do network request, and for this if it succeeds emit the success, otherwise if it fails emit the failure so that the caller actually knows that both have failed.
So this would be kinda like the normal CacheFirst option, but with the change that it does not stop at only the cache if the cache does in fact respond with something.
If I make this function as an extension to ApolloCall<D>
I got no way to do this safely in a way that would work for other FetchPolicies right? Because if I just do not emit anything on a cache miss, if someone had passed CacheOnly for example, then the flow would simply emit nothing at all and the caller would not see a result ever.
My use case is that in some screen, I want to get the cached value if it's available to show it immediately in the UI.
Then I want to ALSO fetch from the network because we've had some stale cache issues there.
But I do not want to show the "error" state in-between while I just looked at the cache and it returned no data, and I am now waiting for the network request. I would prefer to just keep it at it's "Loading" state, thinking it has still just not received anything back yet.Stylianos Gakis
07/18/2024, 1:27 PMStylianos Gakis
07/18/2024, 1:33 PMthis.executionContext.get(FetchPolicyContext)
To try and manually check, but that's internal, probably for good reason π
Perhaps I could make a specialized extension function on ApolloCall<D> which both sets the policy to CacheAndNetwork, and then does something like this:
But that would also look odd from the caller, as it'd look different from all other places where we do fetch policy separate from the restStylianos Gakis
07/18/2024, 1:35 PMisLast
which I could perhaps use instead? πππ
If I know that it's in fact the last emission, I can just emit the cache miss as-is, to inform about the failure!
If it's false, I can safely(?) just emit nothing, since I know there's gonna be more emissions coming later, where those can in fact emit the failure if that also fails.Stylianos Gakis
07/18/2024, 1:38 PMThere can be false negatives where [isLast] is false if the producer does not know in advance if
other items are emitted. For an example, the CacheAndNetwork fetch policy doesn't emit the network
item if it fails.
There must not be false positives. If [isLast] is true, no other items must follow.
I am not 100% sure what it means by "the CacheAndNetwork fetch policy doesn't emit the network
item if it fails.". If the cache fails it emits the cache failure, then it proceeds to continue returning all the network responses. Is it that those network responses may never come for some reason? What reason would that be?bod
07/18/2024, 1:56 PMStylianos Gakis
07/18/2024, 2:15 PMCacheAndNetwork
I will get the cache miss emission, with isLast = false
and then I will not get any more emissions ever? I am still having a hard time figuring out when that would be the case.
Why would it not emit again if there is a failure in the network exception, is that how it always behaves?
The code here https://github.com/apollographql/apollo-kotlin/blob/e3f72aade9a4f7cd1aa6e5d7e7c688[β¦]pollographql/apollo/cache/normalized/FetchPolicyInterceptors.kt just forwards the request as-is in chain.proceed(request)
, after it's done trying to hit the cache. Why would that not emit anything afterwards?bod
07/18/2024, 3:56 PM.exception
instead of throwingStylianos Gakis
07/18/2024, 4:02 PMbod
07/18/2024, 4:06 PMStylianos Gakis
08/19/2024, 2:26 PMEither<ApolloOperationError, Data>
, where ApolloOperationError
is this
sealed interface ApolloOperationError {
val throwable: Throwable?
fun isCacheMiss(): Boolean = this is CacheMiss
data class CacheMiss(override val throwable: CacheMissException) : ApolloOperationError {
override fun toString(): String {
return "CacheMiss(throwableMessage=${throwable.message}, throwable=$throwable)"
}
}
data class OperationException(override val throwable: ApolloException) : ApolloOperationError {
override fun toString(): String {
return "OperationException(throwableMessage=${throwable.message}, throwable=$throwable)"
}
}
data class OperationError(private val message: String) : ApolloOperationError {
override val throwable: Throwable? = null
}
}
Which is created from this code, which I've tried to write following the information from the truth table. Which btw was insanely helpful for me, thanks so much! The one thing we don't handle there is partial responses (data + errors) but we never did so I am leaving it as-is for now.
And then on each call site, if I know that I do not want to show the temporary error while waiting for the network to respond, I just check for isCacheMiss
and ignore that.
Works well for me so far. It's a bit manual work on the call-site, but at the same time I am not sure if I want to introduce something more involved than this. I will come back here if we ever change this more πbod
08/19/2024, 2:35 PMOperationException
-> FetchError
and OperationError
-> GraphQLError
. The names you picked are also good since they match what we have in the library.Stylianos Gakis
08/19/2024, 2:47 PMStylianos Gakis
08/19/2024, 2:49 PMfun <D : Operation.Data> Flow<Either<ApolloOperationError, D>>.filterCacheMisses(): Flow<Either<ApolloOperationError, D>> {
return this.filterNot { it.leftOrNull()?.isCacheMiss() == true }
}
Which would also work fine, if I want the filtering to happen higher up, but haven't decided if I like this or not yet πbod
08/19/2024, 2:52 PMfeeling quite more comfortable now with how we handle all errors and exceptions, this 4.x update is super nice about this!so good to hear that! π
keeping the word "exception" in there, to kinda make ourselves understand that there was some exception thrown there.yes probably a good idea