I am trying to build a prototype app to evaluate the usage of AWS-Amplify (DataStore) in our next App. I am facing an issue when trying to sync 2 Clients. I set up my AWS-DataStore as explained by the tutorial here: https://docs.amplify.aws/lib/datastore/getting-started/q/platform/ios by using Cocoapods. Regarding Cocoapods everything is working as expected. I tried some testing which can also be seen under the link above with "manipulating data". I also did the "syncing data to cloud" section. I can see my data in dynamoDB in the AWS console. When I am adding an entry I can see it in the console everything is working as expected but i have a problem in two scenarios:
Let's say i have 2 clients A and B (both running iOS 13 - it doesn't matter here if it is a real device or on simulator) and I want to these clients to stay in sync. For this purpose I added a PostStore (I am using the example schema from the AWS-link above atm) which looks like this:
import Foundation
import Combine
import SwiftUI
import Amplify
class PostStore: ObservableObject {
@Published private(set) var posts: [Post] = []
var postSubscription: AnyCancellable?
init() {
self.getAllPostsFromDataStore()
}
deinit {
self.unsubscribeFromDataStore()
}
func getAllPostsFromDataStore() {
Amplify.DataStore.query(Post.self) { (result) in
switch result {
case .success(let posts):
DispatchQueue.main.async {
print("Got \(posts.count) Posts from DataStore initially")
self.posts = posts
}
case .failure(let error):
print("Error getting Posts from DataStore: \(error.localizedDescription)")
}
}
}
func addRandomPostToDataStore() {
let post = Post.getRandomPost()
self.addPostToArray(post)
print("Fire: \(post.id)")
Amplify.DataStore.save(post) {
switch $0 {
case .success(let post):
print("Added post with id: \(post.id)")
case .failure(let error):
print("Error adding post with title: \(post.title) Error: \(error.localizedDescription)")
}
}
}
func deletePostFromDataStore(for indexSet: IndexSet) {
let postsToDelete = indexSet.map { self.posts[$0] }
self.posts.remove(atOffsets: indexSet)
for post in postsToDelete {
Amplify.DataStore.delete(post) { (result) in
switch result {
case .success():
print("Deleted Post from DataStore")
case .failure(let error):
print("Error deleting Post From DataStore: \(error)")
}
}
}
}
func subscribeToDataStore() {
postSubscription = Amplify.DataStore.publisher(for: Post.self)
.sink(receiveCompletion: { (completion) in
print("Completion!")
if case .failure(let error) = completion {
print("Subscription received Error: \(error.localizedDescription)")
}
}, receiveValue: { (changes) in
//print("Subscription received mutation: \(changes)")
print("\n\n\n")
print("\(try! changes.toJSON())")
print("\n\n\n")
// print("Changes!")
let newPost = try! changes.decodeModel(as: Post.self)
DispatchQueue.main.async {
switch changes.mutationType {
case "create":
// print("Create Subscription")
self.addPostToArray(newPost)
break
case "update":
print("Update Subscription")
self.updatePostInArray(newPost)
break
case "delete":
print("Delete Subscription")
self.deletePostFromArray(newPost)
break
default:
print("AnotherType?")
print(changes.mutationType)
break
}
}
print("\n")
})
}
func unsubscribeFromDataStore() {
postSubscription?.cancel()
}
private func addPostToArray(_ post: Post) {
if !self.posts.contains(post) {
self.posts.append(post)
}
}
private func deletePostFromArray(_ post: Post) {
if let index = self.posts.firstIndex(of: post) {
self.posts.remove(at: index)
}
}
private func updatePostInArray(_ post: Post) {
print("update?")
}
}
The subscribeToDataStore
method is triggered via the SwiftUI-View (in onAppear
) and is the key thing in here. If I am adding some Posts with Client A (slowly) then Client B receives every change and is adding it to the store (I visualized this with a SwiftUI View but that doesn't matter in this example). No Problems here everything is working as expected.
But
In both cases when I trigger getAllPostsFromDataStore
every single entry is fetched and we are good to go again.
So my question is: Why is the subscription not fetching every single entry?
There is an interesting fact when doing scenario 1: A common log message when receiving an entry per subscription looks like this:
WebsocketDidReceiveMessage - {MyObject}
Then I am logging the object in the sink 'receiveValue' completion again:
{MyObject}
But when adding a few entries fast (in a short time) the log message from Amplify sometimes looks like that: (And this is where the first "Object" is missing in the subscription fetch):
WebsocketDidReceiveMessage - {MyObject}
WebsocketDidReceiveMessage - {MyObject2}
Then I am logging the object in the sink 'receiveValue' completion again and there is only the last object getting logged:
{MyObject2}
Seems like that {MyObject}
got cancelled or something when adding more than one entries fast. Like mentioned above, when B is in Flight Mode or has no connection only the last added entry is fetched. (But I also receive only the last log message for WebsocketDidReceiveMessage
).
Am I missing a thing here or is this a bug in the Amplify-DataStore SDK?
Excuse my bad english please. I am not a native speaker and sometimes it is hard to tell exactly what is the problem. If you have any questions regarding this problem or need more information just comment and I will edit my question providing everything you need.
Best Regards
For anyone who also came across this problem:
I filed an Issue here and the problem seems to be fixed in version 1.3.1 with PR #756
Have fun!