Skip to content
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

Report of inconsistent cache results after update from mutation #788

Closed
micimize opened this issue Dec 30, 2020 · 8 comments
Closed

Report of inconsistent cache results after update from mutation #788

micimize opened this issue Dec 30, 2020 · 8 comments

Comments

@micimize
Copy link
Collaborator

@reilem mentions in #692 (comment):

This also just turned out to be the solution to a very annoying bug I was having, steps to repro:

  • Fetch list of items, result = [A, B, C]
  • Add item D using GraphQL mutate, result shown in UI: [A, B, C, D]
  • Refetch list of items, result = [A, B, C] again, item D disappears from the UI

Maybe I am just using it incorrectly, but this seems like strange default behaviour. Adding network-only luckily solved it in my case. Is this in docs anywhere? Maybe under caching the section we could add a "How to disable cache" item.

@reilem:

  1. Which version are you on?
  2. What is the code you're using in update / what causes the UI to show [A, B, C, D] (mutation or query result?)
  3. Where do you add networkOnly? Is it to force refetch the results from the network using the first query?

Broke the documentation aspect into #787

@reilem
Copy link

reilem commented Jan 4, 2021

  1. This is the entry in my pubspec: graphql_flutter: ^3.1.0
  2. The mutation returns the created item, and I am then manually inserting it into the list of shown items.
  3. Since I wanted to avoid this bug in the future, I just added networkOnly in the defaultPolicies during creation of the client so that all queries always use network results.
final defaultPolicies = Policies(fetch: FetchPolicy.networkOnly);
final _client = GraphQLClient(
    cache: InMemoryCache(),
    link: Link.from(...)
    defaultPolicies: DefaultPolicies(
      watchQuery: defaultPolicies,
      query: defaultPolicies,
      mutate: defaultPolicies,
    ),
  );

@micimize
Copy link
Collaborator Author

micimize commented Jan 7, 2021

@reilem what do you mean by "manually inserting?" If you want changes persisted to the cache, you need to use update to write the updated query value back to the cache. If you're just inserting an item into the list available in build, you will likely encounter other issues as it is an anti-pattern in flutter

Here's an example, but you'd need to read the query, modify it, then write it again:
https://github.com/zino-app/graphql-flutter/blob/f85c45e962d645d012363adaa9e9c01669f1d5bc/packages/graphql_flutter/example/lib/graphql_widget/main.dart#L179-L187

@reilem
Copy link

reilem commented Jan 8, 2021

It's a bit complicated to explain. We did not build our app from the ground up using this library and migrated to GraphQL recently. We already used providers (https://pub.dev/packages/provider) to notify widgets of changes and sembast (https://pub.dev/packages/sembast) to store local changes. We simply changed it to sync these changes using graphql-flutter to our GraphQL backend instead of using a REST API. So, we already had a data access layer before migrating to graphql and so we were unable to use any of the Mutation or Query widgets because we follow the principles of separating UI logic from business logic (and this would also have been an insane amount of work to migrate ALL of our widgets 😛). So what we are doing more or less is just calling service functions from our providers, and a service function looks something like this:

// In some service function
final result = await _graphqlClient.query(QueryOptions(documentNode: UserQueries.getUser));
if (result.hasException) { 
  // ... etc. any error logic here
}
final user = result.data['user'];
return user;

This is not 100% how our app works, but basically the data is returned to the providers and they will notify widgets of the changes.

I could not find lots of documentation on how to work with graphQL client directly so lots of this I just discovered by trial and error + github issues. I am assuming that maybe I need to add a call to some update function on the graphQL client to update the cache? Then we could also put this in the docs for people like me using the client directly.

@micimize
Copy link
Collaborator Author

@reilem ok – in that case it sounds like disabling the cache via networkOnly makes decent sense, given that you have your own caching solution already.

Basically what was happening here was that the graphql/client.dart cache was unaware of the change you made to your UI facing cache, and thus returned the old value because the default default is FetchPolicy.cacheFirst.

In the v4 beta, graphql/client.dart is much better documented, and has a more robust direct cache access api. You could consider upgrading to that, and writing a custom sembast store – here's the HiveStore implementation for reference.

Alternatively, it's possible that you'd be better served building a custom client around your design using the gql_link system directly, if you encounter any more hard to debug issues.

update is a callback passed to the Mutation widget that gets added to the onData callbacks of an ObservableQuery. One awkward thing about graphql_flutter is that the Mutation widget uses watchQuery, which causes some issues (#774).

Anyhow, it seems like this isn't really a bug and more of a design challenge, so I'm gunna go ahead and close this

@ollyde
Copy link

ollyde commented Mar 13, 2022

Is there a simple way to disable the cache on the client? It's giving us issues, and kinda strange API client would have cache by default...

gqlClient = GraphQLClient(
    cache: GraphQLCache(store: InMemoryStore()), // << What do we put here?
    link: link,
  );

@budde377
Copy link
Collaborator

@OllyDixon, take a look at the different policies or implement your own store.

If you want a client without a cache, you can use standard libraries for HTTP requests. This client would provide little value without the caching.

@ollyde
Copy link

ollyde commented Mar 13, 2022

I figured it out, for anyone else who needs help. It still provides value @budde377 :-)

  gqlClient = GraphQLClient(
    cache: GraphQLCache(store: InMemoryStore()),
    link: link,
    defaultPolicies: DefaultPolicies(
      mutate: Policies(fetch: FetchPolicy.networkOnly),
      query: Policies(fetch: FetchPolicy.networkOnly),
      subscribe: Policies(fetch: FetchPolicy.networkOnly),
      watchMutation: Policies(fetch: FetchPolicy.networkOnly),
      watchQuery: Policies(fetch: FetchPolicy.networkOnly),
    ),
  );

Would be nice if there was just a NoCacheStore :-)

@micimize
Copy link
Collaborator Author

@OllyDixon at one point I looked at making the cache optional, but it created a significant amount of complexity when such usecases are probably better served by using gql_link directly.

But, if you wanted to implement a noop store, the interface is pretty small, and you'd basically just make everything a noop I think.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants