amplify-swift
amplify-swift copied to clipboard
Amplify.API - custom query pagination issue
Describe the bug
When using custom queries the ".getNextPage" method returned doesn't use the custom query to fetch next pages, it instead uses the default query but since this default query is not compatible with the custom query NextToken the call fails schema:
type SentMessage @model @key(name: "byChannelId", fields: ["channelid", "createdAt"], queryField: "messagesByChanneId") {
id: ID!
senderid: String!
recieverid: String!
channelid: String!
createdAt: AWSDateTime!
content: String!
}
custom query:
static func getMessagesByChannelId(_ channelId: String, pageSize: Int) -> GraphQLRequest<List<SentMessage>> {
let operationName = "messagesByChanneId"
let document = """
query messagesByChanneId($channelid: String, $filter: ModelSentMessageFilterInput, $sortDirection: ModelSortDirection, $limit: Int) {
messagesByChanneId (channelid: $channelid, filter: $filter, sortDirection: $sortDirection, limit: $limit) {
items {
id
channelid
content
createdAt
recieverid
senderid
__typename
}
nextToken
}
}
"""
return GraphQLRequest<List<SentMessage>>(
document: document,
variables: [
"channelid": channelId,
"sortDirection": "DESC",
"limit": pageSize
],
responseType: List<SentMessage>.self,
decodePath: operationName)
}
initial API call:
{
"query": "query messagesByChanneId($channelid: String, $filter: ModelSentMessageFilterInput, $sortDirection: ModelSortDirection, $limit: Int) {\n messagesByChanneId (channelid: $channelid, filter: $filter, sortDirection: $sortDirection, limit: $limit) {\n items {\n id\n channelid\n content\n createdAt\n recieverid\n senderid\n __typename\n }\n nextToken\n }\n}",
"variables": {
"channelid": "eab3c25c-ecaf-4886-9c50-1e69fa1e7179",
"limit": 10,
"sortDirection": "DESC"
}
}
getNextPage API call:
{
"query": "query ListSentMessages($limit: Int, $nextToken: String) {\n listSentMessages(limit: $limit, nextToken: $nextToken) {\n items {\n id\n channelid\n content\n createdAt\n recieverid\n senderid\n __typename\n }\n nextToken\n }\n}",
"variables": {
"limit": 10,
"nextToken": "eyJ2ZXJzaW9uIjoyLCJ0b2tlbiI6IkFRSUNBSGcxN1ltYUNNTENHbXlQNEt5OGRnNHFZVjZoTDVZemdnalZUZnRub2M5U2t3Rm00MitIcjI1WXE2THhZQTg4ZHJFa0FBQURvVENDQTUwR0NTcUdTSWIzRFFFSEJxQ0NBNDR3Z2dPS0FnRUFNSUlEZ3dZSktvWklodmNOQVFjQk1CNEdDV0NHU0FGbEF3UUJMakFSQkF4R0dFK2RMQXdCL00wK01hb0NBUkNBZ2dOVWJNRWtCSGpwNU5KTERodzVzQ1BWbm13eWx4ODczUnQ1bHFnVmtncWVjTjJrRU1xUHU0WFpsZ0pMZGJQNWtPa3FlUlZkYXl3MjdUaDAvcC9uUnpyYURISDQ5NEc1a0FqTGtFd0hQcXhWUVNHbGV6VXNVUStCb1pCeGJUQUJIaFRvNUFnVnNZNTNVUGUxdFpucmJVZEFhOVptMmFpV0NTMXBOQmxMYU5jSXRFNWpHUEhMWitJeStRQldLM0ZyNzcvbDdqZ3M4MGFzVkhQMDRlUFQ3ME1GVzRyQlI4RjJYNG40T3ozdHBiNk5SYy9uYTVzRHQ3bSttdXNvaDdqWk9kZXhtUVFUdGZRMDRwRjlVNzh0alRudVBnRHVPbWx2MlQ3M3ZEY0VISER3R0ZlRm9SR2p2QzcraXdWM0w2SmZzamtUaTZMNUVuTUVITWF5RVZmV0xiYXRoOWxUZHdxMUJ4RG1JK3JrVWJOU3c2ck5rUXJ3SjJmUlFYTkU2YjE1T1Z3REltaTlITU9LUFl1enFZcWxVcWtLRlVOSFdnTVU1R0xFM1VqQk5qVDQrUWNLNldKMlA5eVpwdno3T0gxdVFnc3o0SkwrU0liSEVDdXcyMW5pNFU5ay9XVVpRV0RHcmoxTUU4TTNpV1gxOFlJL3pqL1RjOTNSaHJWTnRhN3RXTHJ2VjhtMTJaT1BBYmZrbkN0WTVBMHBuNm53TFUySFhYQTlPRHl6bUlsaitZeE9MK1R6SWxydXhUN29BTDdaQzBLMlNUQXJaWjhqeUs4UDdGYzJNQVgwd25wRXNIbDRvdTZQdVdVNzJoQmF5aytvRFVRdjJhdzE5ZjcxTDd3am5mczBPN1NFaWZBUmdDR1VPZHdkemhoOUd1SUdRelBxTkZFaFVIWU14Y1pBRFNHSC9mQW1YK3JiVEtDcE4xWGdCMnRocVNrQVBsYzA5NnNxODl0TzRsbFZBUFdYRE1uOHlhVzBubkVJWklvcVJLYTVrZmhMTWFpeWZHdUQrQWNyTFB5d1NJTXd5cDlrTy9rdGZCTkRVL2UrSG5WUTllT1ZaTitNWlBmU2RPR2dyWEVoK0FMMFVXTllVdnRBSUtpSlU2VE9yZTlYc2xub0w4cE9iS1BTYUJYbDd6WmFaZnVGaTRncjJoL3JLTyt5SkNybmRSVFJ5N205VGRRdXdzY0ExVnU1R0p2QWluM0dmek1oOUZvemFwUFdoWG9jdzQzb1NWUUFaK055K3l6ekNzMU1RUVNRVlJqUWpZZkdwSVQvT2Npdi8xOHIvazNOeGp1djVqQjlhQ1ZRSFA0eFZXNkhlNTVKZjhvZ3doeVo5VFZlKzE2cy9xWUcwZitKUGliQkUybEdCdkJweUhOMVBQTXBlZEhXaXM2eFdCOTdrMmNxVnQ5WFR1VjY5NE9SVW4rY0FiWThnM01sd0RFTzV6RVgwOUtuQmw3TDUrVkZ6OEJUdUFjM3dETzZ1d3Qwa3F3VVJ5Vk4vQjFWWTc0Mm1nOHZLdDY1In0="
}
}
and since the Next token used is not compatible with the query used to get next page, the call fails:
{
"data": {
"listSentMessages": null
},
"errors": [
{
"data": null,
"errorInfo": null,
"errorType": "DynamoDB:DynamoDbException",
"locations": [
{
"column": 3,
"line": 2,
"sourceName": null
}
],
"message": "The provided starting key is invalid: The provided key element does not match the schema (Service: DynamoDb, Status Code: 400, Request ID: 0672R8LD612MASAK3UU4O0DN5RVV4KQNSO5AEMVJF66Q9ASUAAJG, Extended Request ID: null)",
"path": [
"listSentMessages"
]
}
]
}
Steps To Reproduce
Steps to reproduce the behavior:
1. Use custom query to fetch records
2. when more pages are available a NextToken is returned and the method getNextPage is available, call getNextPage
Expected behavior
Amplify.API should be smart enough to reuse the custom query in the GetNextPage method allowing us to paginate while using custom queries
Amplify Framework Version
1.12
Amplify Categories
API
Dependency manager
Swift PM
Swift version
5.0
CLI version
4.5.0
Xcode version
12.3
Relevant log output
No response
Is this a regression? (i.e. was this working before a version upgrade)
No response
Device
iPhone 7
iOS Version
iOS 14
Specific to simulators
No response
Additional context
No response
Hi @rcastrovivar, thanks for the detailed description. This currently doesn't work because the provider used to get the next page has an hardcoded GraphQLRequest that uses listQuery.
To get this to work, you can change the response type to a type that can be decoded to:
struct ListResponse<M: Model> {
let nextToken: String
let items: [M]
}
then use it in as your response type: responseType: ListResponse<SentMessage>.self. This way, you can pull the nextToken back out, and call your custom query again with the next token: getMessagesByChannelId(_ channelId: String, pageSize: Int, nextToken: String).
I understand this isn't ideal but it should unblock your use case. For List to support custom request, I believe it's difficult because re-using the original request means maniupulating the document (a free form string at that point) and variables to inject the nextToken appropriately, which is prone to error.
i have tried your work around and it does work, thanks for your response.
@rcastrovivar Thanks for getting back and confirming it is working for you- I'll mark this as a feature request to better support this use case.
Constructing custom queries go beyond just getting the correct document and variables for the GraphQL request, but also getting the correct response type to decode the response from AppSync. Amplify CLI codegenerates the models which contain the associated has-many connected models as a List<Model>, which can easily be used as the responseType in GraphQLRequest. We should investigate how we can better support the use case of creating using custom graphql requests and re-using the models that are generated by amplify codegen models.
We know that the List object has metadata that can be used to perform fetching the first page or getting the next page. we can provide escape hatches from this object to better leverage the metadata that's already present from the custom decoding logic done in the plugin. For example, can we simply expose the nextToken that is an AppSync specific data?
let appSyncList = list as? AppSyncList, let nextToken = appSyncList.nextToken {
getMessagesByChannelId(nextToken)
}
This way, developer does not need to modify the responseType to their own custom type and reuse List.
Can we provide a way to provide overrides?
// let request = list.getNextRequest() // Without overrides
let request = list.getNextRequest().override(document, variables) // with overrides
Amplify.API.query(request)