Search code examples
iosswiftamazon-web-servicesdatastoreaws-amplify

An iOS app uses AWS Amplify DataStore and it does not sync with backend (DynamoDB)


I have an app that uses AWS Cognito to authenticate a user. The authentication works well. The app also saves items to DataStore with AWS Amplify. I installed Amplify with Swift Package Manager as a package called amplify-ios. When I added the package I specified "Up to Next Major Version", 1.0.0 < 2.0.0.

In fact, the app DOES NOT sync the date saved to DataStore with AppSync. It saves them locally only. I triple checked my AWS console: the DynamoDB table for the data exists but it's empty. No data saved, whatsoever!

What do I do wrong? Does this Amplify library work with AppSync? If not, what shall I use instead?

My code:

ToDoStore.swift:

import Foundation
import Combine
import SwiftUI
import Amplify

class ToDoStore: ObservableObject {

    @Published private(set) var todos: [Todo] = []

    init() {
        self.getAllTodosFromDataStore()
    }


    func getAllTodosFromDataStore() {

        Amplify.DataStore.query(Todo.self) { (result) in
            switch result {
            case .success(let todos):
                DispatchQueue.main.async {
                    print("Got \(todos.count) Todos from DataStore initially")
                    self.todos = todos
                }
            case .failure(let error):
                print("Error getting Todos from DataStore: \(error.localizedDescription)")
            }
        }
    }


    func deleteTodoFromDataStore(for indexSet: IndexSet) {

        let todosToDelete = indexSet.map { self.todos[$0] }

        self.todos.remove(atOffsets: indexSet)

        for todo in todosToDelete {

            Amplify.DataStore.delete(todo) { (result) in
                switch result {
                case .success():
                    print("Deleted Todo from DataStore")
                case .failure(let error):
                    print("Error deleting Todo From DataStore: \(error)")
                }
            }
        }
    }


    private func addTodoToArray(_ todo: Todo) {

        if !self.todos.contains(todo) {
            self.todos.append(todo)
        }
    }

    private func deleteTodoFromArray(_ todo: Todo) {

        if let index = self.todos.firstIndex(of: todo) {
            self.todos.remove(at: index)
        }
    }

    private func updateTodoInArray(_ todo: Todo) {
        print("update?")
    }
}

AWSDataStoreApp.swift:

import SwiftUI
import Amplify
import AWSCognitoAuthPlugin
import AWSDataStorePlugin
import Foundation


@main
struct AWSDataStoreTestApp: App {
    @ObservedObject var sessionManager = SessionManager()

    init() {
        configureAmplify()
        sessionManager.getCurrentAuthUser()

    }

    var body: some Scene {
        WindowGroup {
            switch sessionManager.authState {
            case .login:
                LoginView()
                    .environmentObject(sessionManager)
            case .resetPassword:
                ResetPasswordView()
                    .environmentObject(sessionManager)
            case .signUp:
                SignUpView()
                    .environmentObject(sessionManager)
            case .confirmCode(let username):
                ConfirmationView(username: username)
                    .environmentObject(sessionManager)
            case .session(let user):
                SessionView(user: user)
                    .environmentObject(sessionManager)

            }
        }
    }

    private func configureAmplify() {
        do {
            try Amplify.add(plugin: AWSCognitoAuthPlugin())
            try Amplify.add(plugin: AWSDataStorePlugin(modelRegistration: AmplifyModels()))

            try Amplify.configure()
            print("Amplify configured sucessfully")
        } catch {
            print("Could not intstall Amplify", error)
        }
    }
}

SessionView.swift:

import SwiftUI
import Amplify
import CoreLocation

struct SessionView: View {
    @EnvironmentObject var sessionManager: SessionManager
    @ObservedObject var todoStore = ToDoStore()
    
    let user: AuthUser
    
    var body: some View {
        TodoListView()
            .onAppear(perform: {
                todoStore.getAllTodosFromDataStore()
            })
            .environmentObject(sessionManager)

    }
}


struct TodoListView: View {
    @EnvironmentObject var sessionManager: SessionManager

    @State private var todos: [Todo] = []
    
    var body: some View {
        NavigationView {
            VStack {
                List(todos) { todo in
                    Text(todo.name)
                }
                .onAppear {
                    // Query the DynamoDB table for Todo items
                    Amplify.DataStore.query(Todo.self) { result in
                        switch result {
                        case .success(let todos):
                            // Update the todos state with the retrieved Todo items
                            self.todos = todos
                        case .failure(let error):
                            print("Error retrieving Todo items: \(error)")
                        }
                    }
                }
                NavigationLink(destination: AddTodoView()
                    .environmentObject(sessionManager),
                               label: {
                        Text("Add todo")
                            .padding()
                            .foregroundColor(.white)
                            .background(Color.green)
                            .cornerRadius(10)
                            .font(
                                .system(size: 20))
                    })
            }
        }
    }
}

struct AddTodoView: View {
    @EnvironmentObject var sessionManager: SessionManager
    @State var name = ""
    var body: some View {
        VStack {
            TextField("Name", text: $name)
            Button(action: {
                // create todo
                let todo = Todo(name: name, ownerId: sessionManager.getUser().userId)
                Amplify.DataStore.save(todo) { result in
                    switch result {
                    case .success(let success):
                        // Update the todos state with the retrieved Todo items
                        print(success)
                        print("Saved")
                    case .failure(let error):
                        print("Error retrieving Todo items: \(error)")
                    }
                }
            }, label: {
                Text("Submit")
            })
        }
    }
}

The app shows the items added to the DataStore, everything works, but DOES NOT SYNC. The resources seem to be added to the project correctly. The authentication via Cognito works, but not the DataStore sync.

% amplify status
    Current Environment: dev
┌──────────┬──────────────────────────────────┬───────────┬───────────────────┐
│ Category │ Resource name                    │ Operation │ Provider plugin   │
├──────────┼──────────────────────────────────┼───────────┼───────────────────┤
│ Auth     │ awsdatastoretest058f3478058f3478 │ No Change │ awscloudformation │
├──────────┼──────────────────────────────────┼───────────┼───────────────────┤
│ Api      │ awsdatastoretest                 │ No Change │ awscloudformation │

Dependency manager: Swift PM Swift version: Apple Swift version 5.7 (swiftlang-5.7.0.127.4 clang-1400.0.29.50) Target: arm64-apple-darwin21.6.0 Amplify CLI version: 10.5.2 Xcode version: 14.0.1 (14A400)

I made a fresh project. I installed amplify from scratch. I checked similar posts end ensured that my app is connected to the Internet (it is). I tried everything. Nothing works.


Solution

  • I described the issue also at https://github.com/aws-amplify/amplify-swift/issues/2632.

    It appeared that I missed to add a separate plugin that is essential to start syncing local DataStore on the phone and the backend at AWS. Thus, the answer is to add to my main App swift file the following:

    try Amplify.add(plugin: AWSAPIPlugin())
    

    Thus, the whole function that I call from my app init() looks like this:

        private func configureAmplify() {
            do {
                try Amplify.add(plugin: AWSCognitoAuthPlugin())
                try Amplify.add(plugin: AWSAPIPlugin())
                try Amplify.add(plugin: AWSDataStorePlugin(modelRegistration: AmplifyModels()))
                try Amplify.add(plugin: AWSS3StoragePlugin())
    
                try Amplify.configure()
                print("Amplify configured sucessfully")
            } catch {
                print("Could not intstall Amplify", error)
            }
        }