aws-amplify / amplify-flutter

A declarative library with an easy-to-use interface for building Flutter applications on AWS.
https://docs.amplify.aws
Apache License 2.0
1.31k stars 243 forks source link

[API/GraphQL] partial mutation / allow to specify which fields should be updated #2927

Open dJani97 opened 1 year ago

dJani97 commented 1 year ago

When using ModelMutations.update(model), Amplify assumes that all properties of the model have to be updated and it lists all properties in the GraphQL document.

For our current use case, this is not desirable, as we only want to update properties that have actually changed. This is because updates to the fields trigger certain server-side events, even if there is no actual change in their values.

Would it be possible to add an optional parameter to ModelMutations so that developers can specify which fields they want to update?

I tried this in my own fork of amplify_api: 0.6.13, and it works. You can see the required changes in this commit:

https://github.com/dJani97/amplify-flutter/commit/e379e2337f8b5b0d39ed68f81a1b7013f9934af7

I added a Map<String, dynamic>? overrideModel method parameter to ModelMutations.update(model), and if set, this replaces the input generated by Amplify. Then in our own codebase, we compute the updated fields using json_diff and pass those fields to this new property.

This works fine in our use case, but it would be great if something like this could be added to Amplify, so we could switch back to the official version.

ragingsquirrel3 commented 1 year ago

@dJani97 good question! In summary, no, it's not possible with the model helpers because (as you noted) it uses the codegen toJSON which includes all the fields. To work around this, you would have to either make a custom request without the model helpers, or you could take output of the model helper and create a new GraphQLRequest, copy all the attributes from the output request and replace the variables object with something like { 'input': overrideModel }.

dJani97 commented 1 year ago

@ragingsquirrel3 Thank you, this actually works:

final requestWithChangesOnly = GraphQLRequest<Profile>(
  document: originalRequest.document,
  apiName: originalRequest.apiName,
  authorizationMode: originalRequest.authorizationMode,
  headers: originalRequest.headers,
  decodePath: originalRequest.decodePath,
  modelType: originalRequest.modelType,
  variables: { ...originalRequest.variables, 'input': overrideModel },
);

Since GraphQLRequest is not mutable, I have to copy the entire object. It would be nice to have a copyWith() method, the code would be a lot shorter, and less likely to break in case more fields get added:

final requestWithChangesOnly = originalRequest.copyWith(
  variables: { ...originalRequest.variables, 'input': overrideModel },
);