-
Notifications
You must be signed in to change notification settings - Fork 730
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
Pagination: Pager does not provide updates during a refresh #3387
Comments
@pixelmatrix Thanks for the detailed description and reproduction steps, as you said I'm not sure if this is a bug or a feature request. I will check with the team so we can see what the path forward here is. |
@pixelmatrix Ah, I see exactly what you mean. It's a difficult problem to solve for: calling We can certainly allow cache updates in the subscribe logic but given that the watchers were unsubscribed from during this process, there may not be a watcher to trigger that update in the first place. I have a pull request proposing to remove this restriction from the subscribe logic, but do note that there will be a behavioral change: You will receive an update each time you get a page, as opposed to only when we have the full set of data. Until this load is complete, the pager's internal data state is incomplete: It has deleted its previous state and is constructing the new state. I imagine that any cache change at this time is an optimistic mutation that should be reflected in the new data? In that case, there would be a few ways to solve for this:
Would either of these work for your use-case? cc: @AnthonyMDev perhaps an examination of the subscribe method and the assumptions we make are warranted. The current logic makes some assumptions around how users want to be updated. A solution to this problem – and ones like it – is to fully remove those assumptions, such that we let all updates through. The current assumption is that if a user calls
These are breaking changes but would push users to use the Combine Publisher APIs for interacting with the pager.sink { [weak self] result in
guard let self else { return }
if self.pager.isLoadingAll { /* Custom logic */ }
// Whatever the user wants to do
}.store(in: &cancellables) |
Yeah, this does seem like a difficult problem. As you mentioned, the old state is deleted in the pager, while the new state is still pending. However, the UI / consumer of the pager is still using on the old state, so it's in this limbo. In my specific use case, doing a full refresh typically does not change the data much, but we need to occasionally refresh still to ensure things are up to date. It's transparent to the user, since refreshing happens automatically. This makes it feel like a bug when data on screen does not immediately respond to mutations or subscriptions. To your note about allowing progressive updates while loading all – that would actually be preferred in my case. Loading all the pages can take several seconds, and the first page generally includes enough data to fill the screen. It could be nice to see updates sooner.
The problem with this is that we trigger the refresh automatically when the screen appears. While the refresh is ongoing, the user performs a mutation, which results in an update to the cache, but that update does not come through the pager until the full refresh is complete. The goal of the refresh is to sync with other clients, rather than to update the data as a result of the mutation.
Do you mean just updating the UI's copy of the data with the change based on the user's action? There are a couple of hurdles there. One problem is that the generated models are not mutable, so it either requires some hacks, or an abstraction to even change the data. The other challenge is that the component that owns the pager may not be aware that something has changed directly. Another view might have made the update, so we rely on the watchers to signal when something has changed. The workaround I have implemented for now is to observe the ApolloStore directly and fetch from the cache again when a change is detected. That solves the issue for me, but it would be convenient if the pager could do it automatically, since it's a bit tedious to detect the changes and refetch. |
@Iron-Ham I think I'm in favor of removing the I'm wondering if there is another solution for this though. While waiting for all of the pages to reload, two types of changes to the data could occur.
While reloading all pages, changes to the order of the list (2) are going to cause issues, which is why we are vacating the watchers during that time. But what if we continued to watch all of the items for pages already queried for changes during a refresh (1), and then return the entire new list only after the entire refresh is done (2)? We could then propagate updates from the cache to the existing list while waiting for the reload to occur. This would probably require us to go the route of an internal One limitation here is that this would require the entities in your list to have their own unique cache keys configured, but that limitation could be well documented (and even validated at run time if need be). |
I'm glad that removing the In general, inserts/removals can happen even when we're paging manually and can cause issues that aren't well handled by Apollo in general now; the problem is no better or worse here than it would be otherwise. A consumer of Apollo can handle this, but it's not easy unless they're using custom models. For consumers that primarily rely on the generated models, this introduces an issue – and one that we could solve by introducing custom directives to be added as decorators to the user's query. I am curious to dive a bit deeper into the |
Related to this – and part of why I think |
I believe this work was completed in apollographql/apollo-ios-dev#428. We'll be releasing a new minor version of the pagination library shortly that includes the change. I'll close this issue. If there is other work captured in this issue that has not been completed, we can re-open this. |
Do you have any feedback for the maintainers? Please tell us by taking a one-minute survey. Your responses will help us understand Apollo iOS usage and allow us to serve you better. |
Summary
It looks like
AsyncGraphQLQueryPager
does not provide updates to previously fetched objects that have changed while it's being refreshed.I'm not sure if this is more of a bug or a feature request, but it's causing problems for our use case.
Expected behavior:
Changes to the data originally provided from the pager is updated while an in-flight refresh request is active.
Actual behavior:
The data provided from the pager does not update until the refresh is complete.
Version
1.10.0
Steps to reproduce the behavior
We're using the pager to fetch all pages for a particular query. Later, when we need to refresh the data we call
loadAll(fetchFromInitialPage: true)
. This particular query is a bit slow, so it can take a couple of seconds before it completes. In the meantime, we allow the user to interact with the items in the pager, which results in mutations.It seems while the query is being refreshed, the pager does not clear the results via the
subscribe
callback, but it also does not emit any updates for local cache mutations or updates received from subscriptions until the pager completes its work.Repro steps:
AsyncGraphQLQueryPager
with a subscribe handler.loadAll(fetchFromInitialPage: true)
.loadAll(fetchFromInitialPage: true)
.Logs
No response
Anything else?
No response
The text was updated successfully, but these errors were encountered: