amplify-swift: DataStore persistence not working

Describe the bug

While trying to maintain persistence in AWS DataStore by pushing to the cloud, Data is not persisted to AWS AppSync.

AMPLIFYCONFIGURATION.JSON

{
    "UserAgent": "aws-amplify-cli/2.0",
    "Version": "1.0",
    "api": {
        "plugins": {
            "awsAPIPlugin": {
                "equestlyride": {
                    "endpointType": "GraphQL",
                    "endpoint": "https://xxxxxx.appsync-api.us-east-1.amazonaws.com/graphql",
                    "region": "us-east-1",
                    "authorizationType": "AMAZON_COGNITO_USER_POOLS",
                    "apiKey": ""
                }
            }
        }
    },
    "auth": {
        "plugins": {
            "awsCognitoAuthPlugin": {
                "UserAgent": "aws-amplify/cli",
                "Version": "0.1.0",
                "IdentityManager": {
                    "Default": {}
                },
                "CredentialsProvider": {
                    "CognitoIdentity": {
                        "Default": {
                            "PoolId": "us-east-1:e1890af2-49dd-4250-b9fc-xxxxxxxx",
                            "Region": "us-east-1"
                        }
                    }
                },
                "CognitoUserPool": {
                    "Default": {
                        "PoolId": "us-east-xxxxxx",
                        "AppClientId": "xxxxxx",
                        "Region": "us-east-1"
                    }
                },
                "Auth": {
                    "Default": {
                        "authenticationFlowType": "USER_SRP_AUTH",
                        "socialProviders": [],
                        "usernameAttributes": [
                            "EMAIL"
                        ],
                        "signupAttributes": [
                            "EMAIL"
                        ],
                        "passwordProtectionSettings": {
                            "passwordPolicyMinLength": 8,
                            "passwordPolicyCharacters": []
                        },
                        "mfaConfiguration": "OFF",
                        "mfaTypes": [
                            "SMS"
                        ],
                        "verificationMechanisms": [
                            "EMAIL"
                        ]
                    }
                },
                "AppSync": {
                    "Default": {
                        "ApiUrl": "https://xxxxxx.appsync-api.us-east-1.amazonaws.com/graphql",
                        "Region": "us-east-1",
                        "AuthMode": "AMAZON_COGNITO_USER_POOLS",
                        "ClientDatabasePrefix": "equestlyride_AMAZON_COGNITO_USER_POOLS"
                    },
                    "equestlyride_API_KEY": {
                        "ApiUrl": "https://xxxxxx.appsync-api.us-east-1.amazonaws.com/graphql",
                        "Region": "us-east-1",
                        "AuthMode": "API_KEY",
                        "ApiKey": "",
                        "ClientDatabasePrefix": "equestlyride_API_KEY"
                    }
                }
            }
        }
    }
}

APP DELEGATE SET UP

import Foundation
import SwiftUI
import Amplify
import AWSCognitoAuthPlugin
import AWSDataStorePlugin
import AWSAPIPlugin


class AppDelegate: NSObject, UIApplicationDelegate {
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

        do {
            try Amplify.add(plugin: AWSCognitoAuthPlugin())
            try Amplify.add(plugin: AWSDataStorePlugin(modelRegistration: AmplifyModels()))
            try Amplify.add(plugin: AWSAPIPlugin())
            try Amplify.configure()
            Amplify.DataStore.start{ (result) in
                switch(result) {
                    case .success:
                        print("DataStore started")
                    case .failure(let error):
                        print("Error starting DataStore:\(error)")
                }
            }
            print("Amplify configured with auth plugin")
            print("Amplify configured with dataStore plugin")
            print("Amplify configured with API plugin")
        } catch {
            print("Failed to initialize Amplify with \(error)")
        }

        return true
    }
}

SCHEMA.GRAPHQL

# This "input" configures a global authorization rule to enable public access to
# all models in this schema. Learn more about authorization rules here: https://docs.amplify.aws/cli/graphql/authorization-rules

# input AMPLIFY { globalAuthRule: AuthRule = { allow: public } } # FOR TESTING ONLY!

type User @model @auth(rules: [{ allow: owner }]) {
  id: ID!
  username: String!
  firstName: String
  lastName: String
  subscriptionType: Int
  rides: [Ride] @hasMany
  calendarItems: [CalendarEvent] @hasMany
  cartID : ID
  cart: UserCart @hasOne (fields:["cartID"])
}

type Ride @model @auth(rules: [{ allow: owner }]) {
  id: ID!
  distance: Float!
  duration: Int!
  timestamp: AWSTimestamp!
  locations: [Location] @hasMany(indexName: "fromRide", fields: ["id"])
}

type Location @model @auth(rules: [{ allow: owner }]) {
  rideID: ID! @index(name: "fromRide")
  latitude: Float!
  longitude: Float!
  timestamp: AWSTimestamp!
  ride: Ride @belongsTo(fields: ["rideID"])
}

type CalendarEvent @model @auth(rules: [{ allow: owner }]){
  id: ID!
  title: String!
  timestamp: AWSTimestamp!
}

type UserCart @model @auth(rules: [{ allow: owner }]){
  id: ID!
  products: [SelectedProducts]
}

type SelectedProducts{
  id: ID!
  variantID: String!
}

When writing to CalendarEvent, I implemented a publisher to notify when changes were made as per documentation as shown below

func subscribeToPosts() {
        postsSubscription = Amplify.DataStore.publisher(for: CalendarEvent.self)
            .sink {
                if case let .failure(error) = $0 {
                    print("Subscription received error - \(error.localizedDescription)")
                }
            }
            receiveValue: { changes in
                // handle incoming changes
                print("Subscription received mutation: \(changes)")
            }
    }

I then receive:

Subscription received mutation: MutationEvent(id: "BF9F8445-4E9D-4048-876E-32FCEA79505C", modelId: "31AA572E-AAB8-40D9-873D-FB155B9769CE", modelName: "CalendarEvent", json: "{\"id\":\"31AA572E-AAB8-40D9-873D-FB155B9769CE\",\"title\":\"Eeer\",\"timestamp\":1646795797}", mutationType: "create", createdAt: Amplify.Temporal.DateTime(foundationDate: 2022-03-09 03:16:41 +0000), version: nil, inProcess: false, graphQLFilterJSON: nil)

But later, when querying AWS AppSync, there are no items. I have also verified against DynamoDB. No items are persisted.

Steps To Reproduce

Code Snipets + configuration files provided above

Expected behavior

I expect persistence to backend

Amplify Framework Version

1.21.0

Amplify Categories

API, DataStore

Dependency manager

Swift PM

Swift version

5.0

CLI version

7.6.23

Xcode version

13.2.1 (13C100)

Relevant log output

Not applicable

Is this a regression?

No

Regression additional context

No response

Device

Iphone 12ProMax

iOS Version

15.3.1

Specific to simulators

No

Additional context

No response

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Reactions: 1
  • Comments: 19 (8 by maintainers)

Most upvoted comments

To further investigate the issue you are facing with Auth, I would need to ask some clarifying questions.

  • I would like to understand your application flow… specifically in more details about the calls to getCurrentUser, fetchAuthSession, signIn and signOut?
  • Can you elaborate what you mean by “Once the app initializes”?
  • Since the logout fails, is it blocking you from doing all other operations using amplify?

Hi @carl0sLOL, what I meant by a user that needs to be confirmed is if your signIn API call returns a AuthSignInStep other than done. In your case, you have done the correct flow to confirm the user that has signed up. I can also see the tokens in your logs, DataStore uses either accessToken or idToken when constructing the request for AppSync that has UserPool auth enabled, when syncing the save’s. I checked my User Pools console and my app client also doesn’t have Cognito User Pool checked off under Enable Identity Providers so I feel that might not be what made it work. You can reduce this step down just starting DataStore to reduce the logging and further debug, ie.

Amplify.Auth.signIn(username: "[USERNAME]", password: "[PASSWORD]") { result in
            switch result {
            case .success(let result):
                guard result.isSignedIn, case .done = result.nextStep else {
                    print("Not signed in, not .done \(result.nextStep)")
                    return
                }
                
                Amplify.DataStore.start { result in
                    print(result)
                }
            case .failure(let error):
                print("\(error)")
            }
        }

Please see the updated schema in https://github.com/aws-amplify/amplify-ios/issues/1673#issuecomment-1063496504 at the bottom, i’ve made an explicit belongs-to relationship from the CalendarEvent to the user. one directional has-many isn’t something fully tested/supported by DataStore on iOS at the moment. The field that is generated but not explicit in the schema userCalendarItemsId stores the userId which the calendarEvent belongs to