-
Notifications
You must be signed in to change notification settings - Fork 731
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
Added ability to directly update the Apollo cache using write methods #413
Conversation
@martijnwalraven Is this OK to be merged? |
@Nickersoft Thanks for looking into this. Unfortunately, I don't have much time to give this the attention it deserves right now, but one thing that caught my eye is the use of The reason is that cache keys should not depend on aliases and should take argument values into account. The
|
Hey @martijnwalraven I’ve started digging into this project and noticed there is a backlog of unreviewed contributions. A contributor on Slack suggested creating my own fork and merging some of the fixes. Are you the only person who has push access? If a few of us want to pick this up and review each others’ work, would you be willing to give us push access? Do you have other ideas about this project might move forward? |
@paulmelnikow I have some good news: We'll be hiring a full-time mobile library developer to get Apollo iOS in better shape. We'll be publishing a job posting next week, but if you or anyone you know is interested please get in touch! (Also see this Slack message.) |
Hey @martijnwalraven, thanks for the response! It's terrific to hear you guys will be investing more heavily in this repo, as I know myself and numerous other iOS devs here who have projects relying on this repo and the lack of support/maintenance can be troubling at times. I'd like to pull in @kevbuchanan to see if he might be able to contribute to the conversation, as he was the one who originally wrote the code that uses More or less just saying all this to get it written down and to further document the problem / where it's at. I'd be happy to look into producing a more appropriate solution to the problem. IIRC the actual issue is the fact the library caches entries using response keys but attempts to write/read them via cache keys. I talk about the problem in more detail here. If anyone can give me further pointers on what I can safely change without risking adverse behavior, I'll see what I can do :) |
@Nickersoft Yeah like you said, I'm not completely sure why my fix was necessary, but it does seem to work, and has been working in our app for some time now. Best I can tell is exactly what you described:
|
@Nickersoft @kevbuchanan I'd hate for this PR to die! As I mentioned in |
@martijnwalraven I totally agree. That's what it should be, and I also don't feel comfortable having this PR merged if the code uses its current hack. Unfortunately what I noticed in my app is that the cache is actually filled with entries using the response key, not the correct cache key. So instead of the query Now, I've hunted through the code looking to see where the discrepancy is between the read and write ops (what could be responsible for this behavior), and can't seem to find anything. The next step would be to hook the library up to my app as a prod dependency (no Carthage) and add various breakpoints/logs to figure it out. Perhaps it would help if you could give a high-level overview of how caching is managed? The code is decently complex and it might save on debugging time to have a bit of context going in. |
@Nickersoft Hmmm, that is surprising. The code is indeed quite complicated, so I could very well be missing something :) Ultimately, all writes to the cache (whether from the network or by directly manipulating the store) will go through:
Which is likely:
These methods deal with That relies on apollo-ios/Sources/Apollo/GraphQLExecutor.swift Lines 201 to 205 in f095ec2
Which in turn relies on
Hopefully this is useful in tracking down what might be going on. Let me know if you have more questions. |
@martijnwalraven Thanks for the clarification and overview. I'll try to dig into this further in the next few days. |
@martijnwalraven After revisiting this issue and the changes I made, I think I can explain the problem a little bit better: Within the readWriteTransaction we call apollo-ios/Sources/Apollo/ApolloStore.swift Line 214 in f095ec2
The body of the update is executed on the data, and then the write is performed: apollo-ios/Sources/Apollo/ApolloStore.swift Line 216 in f095ec2
This ultimately gets us to where the problem lies, when the apollo-ios/Sources/Apollo/ApolloStore.swift Lines 229 to 231 in f095ec2
apollo-ios/Sources/Apollo/ApolloStore.swift Lines 233 to 237 in f095ec2
The changes I've made in the PR don't change the fact that the cache key is used for cache reads and writes. It just makes this execution of a selection set against its own data use the response key, since the
which may explain why this wasn't obvious. When the executor translates the selection set into a record set, the resolved record set will contain the cache key at that point, before being merged into the cache, thus preserving the expected behavior of writing to the cache with the cache key. apollo-ios/Sources/Apollo/ApolloStore.swift Line 237 in f095ec2
I may be overlooking something here, so please let me know if this doesn't line up with your understanding. I can also try to provide a better explanation or test case if this isn't clear. But I am reasonably certain that the problem lies in the translation of the |
@kevbuchanan @martijnwalraven I think your explanation may also explain what I observed tonight. In the apollo-ios/Sources/Apollo/ApolloStore.swift Lines 213 to 216 in f095ec2
This conflicts with the logic further down the chain. If we follow that apollo-ios/Sources/Apollo/ApolloStore.swift Lines 233 to 242 in f095ec2
Following the executor statement (235), we eventually land in the executor block: apollo-ios/Sources/Apollo/GraphQLExecutor.swift Lines 191 to 205 in f095ec2
This is where the cache key is calculated and we attempt to read the value from the object that has been passed down, which still only uses the apollo-ios/Sources/Apollo/GraphQLExecutor.swift Lines 191 to 215 in f095ec2
Hopefully this helps to validate or further explain or illustrate the aforementioned point. There definitely seems to be a disconnect in how keys are being read and written. |
Ah, that makes sense! While we definitely want to use In the apollo-ios/Sources/Apollo/GraphQLResponse.swift Lines 21 to 23 in f095ec2
(I think we could just inline this code into |
Sources/Apollo/ApolloClient.swift
Outdated
@@ -28,8 +28,9 @@ public typealias OperationResultHandler<Operation: GraphQLOperation> = (_ result | |||
|
|||
/// The `ApolloClient` class provides the core API for Apollo. This API provides methods to fetch and watch queries, and to perform mutations. | |||
public class ApolloClient { | |||
let networkTransport: NetworkTransport | |||
let store: ApolloStore | |||
public let networkTransport: NetworkTransport |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd prefer not to allow users to change these objects after client creation. Could we make this private(set) public
instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure thing – I had just set them all to public for quick testing.
@martijnwalraven So I did some experimenting and I think you may be right. I replaced: apollo-ios/Sources/Apollo/ApolloStore.swift Lines 233 to 235 in f095ec2
with: private func write(object: JSONObject, forSelections selections: [GraphQLSelection], withKey key: CacheKey, variables: GraphQLMap?) throws {
let normalizer = GraphQLResultNormalizer()
let executor = GraphQLExecutor { object, info in
return .result(.success(object[info.responseKeyForField]))
}
executor.dispatchDataLoads = self.loader.dispatch
executor.cacheKeyForObject = self.cacheKeyForObject
try executor.execute(selections: selections, on: object, withKey: key, variables: variables, accumulator: normalizer) and it seems to work, assuming that's the recycling of network request executor logic you were talking about. In regards to inlining |
That means there isn't too much shared logic, so I think inlining |
Oh wait, we do still need |
I don't think subclasses are the answer here however, because |
@Nickersoft I have an updated fix for this here that can replace my two commits currently in this PR. |
Thanks, @kevbuchanan! Updated the code accordingly. @martijnwalraven please let us know what you think! Also, something I forgot I added to my fork (i.e. this PR), which isn't super relevant to but still super important: I upgraded Starscream from 3.0.5 to 3.0.6, because 3.0.6 adds support for XCode 10 and Swift 4.2. It's a very minor change – but if you need me to break it out in another PR, I'd be happy to. That is, assuming it can be merged very quickly. My app uses Swift 4.2 and I need 3.0.6 in order for it to run. |
@Nickersoft @kevbuchanan Thanks to you both for getting this done! |
Thanks so much @martijnwalraven :) Happy to see this feature finally become part of the library! I wrote a sister PR containing some brief docs about these functions just so people know they exist: #452. |
A vital part of calling mutations with the Apollo libraries is the ability to update the Apollo store after a server-side update. While the Apollo iOS library possesses this functionality, it does not expose it. This PR exposes these methods to allow devs to update the store directly after mutation.
In React, the code to update the store looks something like:
For iOS, this functionality now looks like:
Credit to @kevbuchanan for introducing a fix for query writing in #394.
As a novice to this library, however, I am wondering what the difference between
responseKeyForField
andcacheKeyForField
is, and whether this is the appropriate / best approach to fix the issue.Input from repo maintainers would be very much appreciated :)