apollo-ios: Unable to animate UI changes when using a query watcher
I’m unable to animate changes to a UITableView when the underlying data changes (inserts/deletes) while using a query watcher. When I use a mutation elsewhere to insert/delete data from the table view, the table view and watcher query both get in a funky state because the local cache doesn’t agree with my data I’ve modified on the server and stored in my posts
instance variable as my datasource.
Here’s some code from one of my UITableViewControllers for the watch query:
class PostListViewController: UITableViewController {
var watcher: GraphQLQueryWatcher<AllPostsQuery>?
var posts: [PostDetails]?
# snip
func loadData() {
watcher = apollo.watch(query: AllPostsQuery()) { (result, error) in
if let error = error {
print(#function, "ERROR | An error occured: \(error)")
return
}
guard let posts = result?.data?.posts else {
print(#function, "ERROR | Could not retrieve posts")
return
}
self.posts = posts.map { $0.fragments.postDetails }
self.tableView.reloadData()
}
}
}
And here is where I add a new Post:
let author = AuthorInput(firstName: newPost["firstName"]!, lastName: newPost["lastName"]!)
let createPostMutation = CreatePostWithAuthorMutation(
author: author,
title: newPost["title"]!
)
apollo.perform(mutation: createPostMutation) { (result, error) in
if let error = error {
print(#function, "ERROR | An error occured while adding the new Post: \(error)")
return
}
guard let newPost = result?.data?.createPostWithAuthor.fragments.postDetails else {
print(#function, "ERROR | Could not get the new Post")
return
}
print("Created new post: \(newPost)")
if let postsCount = self.posts?.count {
let indexPath = IndexPath(row: postsCount, section: 0)
self.posts?.append(newPost)
self.tableView.insertRows(at: [indexPath], with: .automatic)
}
What happens is the new row is correctly animated into the table like I want, but my watcher query doesn’t work with the new row. I believe this doesn’t work because the data cached for the AllPostsQuery
is separate from the CreatePostWithAuthorMutation
data (QUERY_ROOT
vs MUTATION_ROOT
).
If I remove my code to animate adding the row and instead call watcher.refetch()
, all is right in the world again because the cache data for the AllPostsQuery
is updated from the server and the table view gets completely reloaded instead of just animating in the new row.
I’ve perused documentation but I can’t seem to find anything on updating cache for a particular query manually (which I believe would solve this issue).
Is there something I’m missing or another way to achieve what I’m trying to do? I’d like to be able to animate UI changes individually to my table views when data changes but still benefit from the query watcher updating the UI as well when the cache changes.
I’m happy to provide more of the code as necessary to reproduce/debug this issue.
About this issue
- Original URL
- State: closed
- Created 7 years ago
- Comments: 18 (6 by maintainers)
I have a similar use case, but don’t have access to the watcher; I want the changes made in the
withinReadWriteTransaction
block to propagate to the watchers.When using
withinReadWriteTransaction
I don’t seem to be able to get the values to be saved to the cache, while using the cache directly does correctly update the values, but does not call any watchers.For example, to set the
rating
of aMovieFragment
I have the something similar to:To get to this code I modified one of the tests that uses this function.
Doing this via the cache saves the value correctly, but does not notify watchers:
Am I doing something wrong here?
@martijnwalraven It looks like the two local modifications I have to make this all work is to mark the
store
property onApolloClient
as public as well as thefetch
method onGraphQLQueryWatcher
. With these two changes to the library, updating a query watcher’s cache after a mutation is workable.Would also like mutations to trigger
watch
updates as well 😃, fwiwThanks for describing your issue in detail, it really helps to have concrete use cases in mind when discussing features.
I think there are two somewhat separate things going on here.
One has to do with the ability to update existing queries based on mutation results. The current version of Apollo iOS does have some primitives in place for updating the cache manually, similar to the ones in the JavaScript client (see updating the cache after a mutation). But these aren’t documented yet because I’m working on revising the generated code and API. I hope to get a beta out this week, and then we can also improve the documentation to cover these use cases.
The other issue is animating changes to query results. Performing the animation from the mutation result handler is a bit brittle, because you’re manually changing UI state. Instead, I think the best strategy here would be to animate changes based on updates from the query watcher. In order to do so, you need the ability to calculate the diff between the old and the new state. I haven’t looked at this in detail yet, but we may be able to use something like Dwifft here. The benefit of setting animations up like this is that you never update the UI directly but always go through the store, an example of unidirectional data flow.