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 242 forks source link

Conflict handler not called for conflicts created while offline #5176

Open filipesbragio opened 1 month ago

filipesbragio commented 1 month ago

Description

It seems as though amplify in optimistic concurrency mode is not triggering the conflictHandler method when conflicts are created while offline. I have tested on android but not on iOS, although I'd be surprised if this was related to a specific platform.

Categories

Steps to Reproduce

In our testing we have a model with a "name" field. That name field can also be edited through a REST endpoint which calls a lambda (or alternatively be modified through AppSync). So our test scenario goes as follows:

The name field starts as "joe". We go offline in the app and change the field to "mary" We update the name through our REST endpoint to be "frank" We go online in the app.

We were expecting the conflictHandler method, in the AmplifyDataStore options, to be called but it isn't. Instead the name is set to "mary" and the version is increased as if the app was simply pushing the changes and ignoring the version the change was made in. Does amplify not store and respect the version in which the changes were made on? Is there a different way of resolving this kind of conflict?

Screenshots

No response

Platforms

Flutter Version

12.12.4

Amplify Flutter Version

2.1.0

Deployment Method

Amplify CLI

Schema

I've removed some properties of the schema and only left the relevant property described, "name".

type User
@model
@auth(
    rules: [
        { allow: owner, ownerField: "userId" }
        { allow: private, provider: iam }
    ]
) {
    userId: ID! @primaryKey @auth(
        rules: [
            { allow: groups, groups: ["FORBIDDEN"], operations: [update, delete] }
            { allow: owner, ownerField: "userId", operations: [create, read] }
            { allow: private, provider: iam }
        ]
    )
    name: String!
}
Jordan-Nelson commented 1 month ago

Hello @filipesbragio - Thanks for taking the time to open the issue. Currently this would be the expected behavior. The documentation does not detail how mutations are processed when a device comes back online. We can add this information to the documentation.

When a device goes offline, mutations made while the device is offline are added to an outbox queue. When the device comes back online, the device will sync updates from remote before clearing the outbox queue. If the updates synced from remote include changes to the model metadata (including version) of any of the pending mutations in the outbox queue, the updated metadata is applied to those pending mutations. Then the pending mutations are made from the outbox queue.

Since the remote sync and metadata update occurs prior to processing the outbox queue, the mutations are made with the latest version which App Sync will accept. If you would like to see this behavior changed we could track this as an enhancement.

filipesbragio commented 1 month ago

Thanks for the response, before this feature gets implemented do you see a way to work around this? Such as a way to add some preprocessing before datastore applied the local data on top of the remote data.

I'll have to ponder this a bit more but if this isn't available the only options I see are somehow withholding offline updates and manually pushing them when online or creating a separate table for the REST API changes and then manually comparing data and merging them when we notice the mobile went online. None of which seem very good.

Jordan-Nelson commented 1 month ago

Hello @filipesbragio - Unfortunately I do not see a way to work around this at the moment.

Can I ask what your use case for DataStore is? Do you need an offline first solution (do you users regularly use the app for extended periods of time with no network)?

filipesbragio commented 1 month ago

I see, that's unfortunate. Our users will likely be moving through areas without coverage or very poor coverage.

We ended up planning on splitting our model into two tables. One where we always want the user to overwrite data on, which should be the default case. And one where the user doesn't edit but changes can happen through our REST API and we'd like to deal with changes on the mobile side since some of them might not be valid depending on which point in our flow the user is at. We'll be creating a copy of that tables data on the mobile device and then when amplify observes a change we'll compare the models and decide if we accept or reject that change and deal with it appropriately.

Do you want to keep this open since this is pending documentation changes and has a feature request?

Jordan-Nelson commented 1 month ago

@filipesbragio Yes, we will keep this open at least until the documentation is updated. If you are still interested in seeing this supported we can continue to track it as as feature request.

filipesbragio commented 1 month ago

It would be useful in my scenario. Datastore could keep track of what the latest backend version was for tables as well as the version of any frontend changes. When Datastore receives data from the backend, it would compare the version it had for the backend when going offline and the newer backend change. If those versions are different and there are any frontend changes as well, a conflict resolution event would be raised to allow us to manipulate the latest frontend change before updating the backend. I suppose one of the complications there would be if a backend change is made while conflict resolution is going on.