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.3k stars 239 forks source link

[Gen2] dynamodb error when having optional sortkeys #5050

Open hangoocn opened 2 weeks ago

hangoocn commented 2 weeks ago

Description

If making a field as sortKey in secondaryIndex and making the field optional, we are not able to submit a create request without specifying the value of this field.

Categories

Steps to Reproduce

  1. For any model, add secondary index like this:
    Message: a
    .model({
      roomId: a.id().required(),
      room: a.belongsTo('Room', 'roomId'),
      createdAt: a.string(),
    }).secondaryIndexes(index => [index('roomId').sortKeys(['createdAt']).queryField('listByDate')])
  2. Then submit a request to create a message (without specify createAt value since it should be created by backend):
    final newMessage = Message(room: room);
    final request = ModelMutations.create(newMessage);
    final response = await Amplify.API.mutate(request: request).response;
  3. Then it will show error One or more parameter values were invalid: Type mismatch for Index Key createdAt Expected, I noticed this is because createAt has a value null in request.variables['input'] instead of just ignoring it

What I want to do is just to sort messages by createAt date, this is one of the bug I found during the attempts. Not sure if it is the correct way.

Screenshots

No response

Platforms

Flutter Version

3.19.3

Amplify Flutter Version

2.0.0

Deployment Method

Amplify CLI

Schema

No response

tyllark commented 1 week ago

Hello @hangoocn thank you for submitting this issue. We will look into this and get back to you!

tyllark commented 5 days ago

Hello @hangoocn sorry for the delay in our response, but we are still investigating the best way to handle overriding Amplify generated fields in the schema. In the meantime we recommend setting the createdAt field client side or customizing your mutation to omit createdAt:

final room = Room();
final newMessage = Message(room: room, name: 'name');
var request = ModelMutations.create(newMessage);
request = request.copyWith(
  variables: {
    'input': {'name': 'test message', 'roomId': room.id} // omit createdAt
  },
);

if you still need help sorting messages by createdAt for a specific roomId then the following code can help you manually generate a GraphQLRequest:

enum SortDirection {
asc('ASC'),
desc('DESC');

const SortDirection(this.graphQLValue);

final String graphQLValue;
}
GraphQLRequest<PaginatedResult<Message>> messagesByRoomIdAndCreatedAt({
  required Room room,
  SortDirection sortDirection = SortDirection.desc,
  int? limit,
}) {
  return GraphQLRequest<PaginatedResult<Message>>(
    document: '''query listByDate(
          \$roomId: ID!,
              \$createdAt: ModelStringKeyConditionInput,
              \$sortDirection: ModelSortDirection,
          \$filter: ModelMessageFilterInput,
          \$limit: Int,
          \$nextToken: String
          ) {
              listByDate(
                roomId: \$roomId,
                createdAt: \$createdAt,
                sortDirection: \$sortDirection,
                filter: \$filter,
                limit: \$limit,
                nextToken: \$nextToken
              ) {
                  items { id createdAt updatedAt room { roomId name createdAt updatedAt owner } roomId owner }
                  nextToken
                }
            }''',
    modelType: const PaginatedModelType(Message.classType),
    variables: <String, dynamic>{
      'roomId': room.roomId,
      'createdAt': null,
      'sortDirection': sortDirection.graphQLValue,
      'filter': null,
      'limit': limit,
      'nextToken': null,
    },
    decodePath: 'listByDate',
  );
}

You will have to update the items {} to match the fields in your model. The messagesByRoomIdAndCreatedAt helper function can then be used like this:

var request = messagesByRoomIdAndCreatedAt(
  room: someRoom,
  sortDirection: SortDirection.desc,
);
final response = await Amplify.API.query(request: request).response;
final messages = response.data?.items;
hangoocn commented 1 day ago

Thanks @tyllark, I am using similar approach to overide the request before sending the request, so can customize the document or variables to anything we want.

Looking forward to a more standard way of doing that.