awslabs / aws-mobile-appsync-sdk-ios

iOS SDK for AWS AppSync.
https://awslabs.github.io/aws-mobile-appsync-sdk-ios/
Other
262 stars 128 forks source link

no callback to mutation #221

Closed alionthego closed 9 months ago

alionthego commented 5 years ago

I have issues where on some mutations the request just hangs with absolutely no callback. I am using my own local storage and not using AppSync local storage for offline use or optimistic update which I set to nil in my mutations.

I occurs at irregular times but one time I noticed it happens is when I am connected to a router with no internet connection. So the reachability thinks there is an internet connection and my code allows the mutation to occur, however there is no internet connection.

The request just hangs. Even well after the timeout with absolutely no callback. Why is this? Incredibly annoying and difficult to troubleshoot. How can I ensure this doesn't happen and eliminate this offline/online problem for my case where I absolutely do not want to use appSync offline storage.

I am using AWSMobileClient for authentication and Swift SDK with latest libraries and iOS/Swift

alionthego commented 5 years ago

After more testing I can see that all cases when a mutation is attempted without any internet results in the request hanging with no callback regardless of timeout interval settings. This is far from ideal. In my chat app, how can I possibly update the ui to let the user know the message was not sent without a callback. I have no desire to use AppSync offline case. I just want a callback.
I need to use the appSync wrapper in order to send S3 objects in some of my mutations so using the Apollo Client is not an option for me. What can I do? Surely you wouldn't limit AppSync's usage by requiring it to handle all offline storage as well?

btn52396 commented 5 years ago

@alionthego Does the message get sent when the internet connection comes back on?

alionthego commented 5 years ago

Yes. And that is another problem because that’s not how a chat app should work. If it doesn’t send after a short time it should remain unsent until the user actions further

btn52396 commented 5 years ago

A hack you can do in the meantime is to have a timer start when you send a message. If you don't receive a callback in x amount of seconds, mark the message as unsent in your local storage and reflect that in the UI.

The perform() method returns a Cancellable so save it to a variable and run cancel() on it.

let cancellable = client.perform(mutation: cancellable.cancel()

This will prevent the mutation from sending to the server.

alionthego commented 5 years ago

@btn52396

very nice solution. thank you very much. I tried the timer but didn't know I could cancel the mutation like that. really saved me

twocentstudios commented 5 years ago

I was also struggling with this issue and found a better workaround.

Internally AWSAppSyncClient passes through calls to fetch and watch directly to its ApolloClient. However, it does not do the same for perform. There's an entire separate sub-system and code path for perform within AWSAppSyncClient. It's this sub-system that has the often unideal behavior of never calling the completion handler.

The solution to this is to use AWSAppSyncClient's underlying ApolloClient instance to call perform, which is usually unused.

// Before
appSyncClient.perform(mutation: mutation, queue: .main, optimisticUpdate: nil, conflictResolutionBlock: nil) { (result, error) in
    // ...
}
// After
appSyncClient.apolloClient!.perform(mutation: mutation, queue: .main) { (result, error) in
    // ...
}

In my testing so far, ApolloClient has the same offline and time-out behavior for perform as it does for fetch (since the both call through to the send method).

Perhaps the more long-term solution would be for AWSAppSyncClient to always call through to ApolloClient directly unless both the optimisticUpdate and conflictResolutionBlock are non-nil. I'm not sure though. It certainly took me some time to realize that the API for bypassing optimistic mutations exists and is exposed to callers.

My personal opinion is that optimistic mutations are rarely the correct paradigm for mutations. Even when optimistic mutations are appropriate for a use case, the overhead in the designing a proper UX flow that allows a user to recover from common errors is often well beyond the scope of the maturity of apps I assumed were the target market of AppSync to begin with. I'm unsure of why this feature is the default behavior or frankly why it exists in the first place.

btn52396 commented 5 years ago

@twocentstudios I'd advise caution when bypassing the appSyncClient. In doing so you are giving up useful features, such as being able to perform mutations while offline.

alionthego commented 5 years ago

@btn52396 I think the point for many is we don't want the offline mutation capabilities of appsync. I personally want to manage the local content myself and use the powerful graphql as my backend. The only way to do this with AWS that I know of is using appsync.
I agree that I don't want to give up the wrapper but only because of convenience methods like being able to pass an S3 object. I think what @twocentstudios and myself are inferring is that we want the powerful capabilities of appsync but without the sync part. At the very least there should be an option to disable the local storage and syncing. To give you just one example of why, suppose you have a chat app,. Suppose you send a message when you have a spotty connection and that message doesn't get through. You should have a way to let the user know that the message was not sent and give them the option to send it later. With appsync and the original purpose of this post was there is no callback telling you that the message was not sent to the server. It's happy knowing that the content will sync when a connection is reestablished. So the user has no way to know whether or not the message was actually sent or when it will be. I want complete control over my front end but to be able to utilize the amazing appsync back end. I would have thought that would be incredibly easy for the guys at AWS to manage. Just a simple option to neglect the sync part. Finally, I'll say my solution so far is to actually use @btn52396 solution above and set a timer to cancel the mutation if it's not successful after a period of time. I wish I didn't have to do that but that works for me.

btn52396 commented 5 years ago

@alionthego I understand that in your case, a chat app wouldn't want offline mutations. Just wanted to put that warning out there for anyone else reading this since there are use cases where large amounts of data may be needed to transfer and being offline shouldn't affect the user's ability to put the items on the operationQueue. An example is sending large amounts of logs, files, or other types of data to the backend. Different solutions for different problems.

I agree that it would be great if there was flag in the client to disable all the syncing parts.

ckifer commented 1 year ago

Not sure if it's the same issue but this still happens today except intermittently in AWS Lambda. We use this client as a lightweight BE client - when there are network issues (unconfirmed but likely) there is a hang and no response until 30s pass and AppSync times out.

After ~60 seconds the request is made and the lambda resumes.

atierian commented 9 months ago

Thank you for opening this issue. AWS AppSync SDK for iOS entered maintenance mode in September 2023 and will receive no further updates as of September 2024.

Please use Amplify Swift going forward. For information on upgrading to Amplify Swift, refer to the Upgrade from AppSync SDK documentation.