From 676dd67ec67bc46113c0a5215c1d73abe8701f83 Mon Sep 17 00:00:00 2001 From: Elijah Quartey Date: Thu, 8 Aug 2024 09:58:31 -0500 Subject: [PATCH 01/14] fix(datastore): Restart Sync Engine when network on/off --- .../pigeons/NativePluginBindings.kt | 6 +++ .../ios/Runner.xcodeproj/project.pbxproj | 9 +++-- .../xcshareddata/xcschemes/Runner.xcscheme | 7 ++++ .../example/ios/Runner/Info.plist | 8 ++-- .../ios/Classes/FlutterApiPlugin.swift | 39 +++++++++++++++++-- .../Classes/SwiftAmplifyDataStorePlugin.swift | 5 ++- .../Classes/api/GraphQLResponse+Decode.swift | 9 ++++- .../pigeons/NativePluginBindings.swift | 6 +++ .../lib/amplify_datastore.dart | 24 ++++++++++++ .../lib/src/native_plugin.g.dart | 17 ++++++++ .../pigeons/native_plugin.dart | 3 ++ .../web_socket/blocs/subscriptions_bloc.dart | 6 +-- 12 files changed, 121 insertions(+), 18 deletions(-) diff --git a/packages/amplify_datastore/android/src/main/kotlin/com/amazonaws/amplify/amplify_datastore/pigeons/NativePluginBindings.kt b/packages/amplify_datastore/android/src/main/kotlin/com/amazonaws/amplify/amplify_datastore/pigeons/NativePluginBindings.kt index 0f82a922a5..6b936216b1 100644 --- a/packages/amplify_datastore/android/src/main/kotlin/com/amazonaws/amplify/amplify_datastore/pigeons/NativePluginBindings.kt +++ b/packages/amplify_datastore/android/src/main/kotlin/com/amazonaws/amplify/amplify_datastore/pigeons/NativePluginBindings.kt @@ -387,6 +387,12 @@ class NativeApiPlugin(private val binaryMessenger: BinaryMessenger) { callback() } } + fun deviceOffline(callback: () -> Unit) { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.amplify_datastore.NativeApiPlugin.deviceOffline", codec) + channel.send(null) { + callback() + } + } fun onStop(callback: () -> Unit) { val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.amplify_datastore.NativeApiPlugin.onStop", codec) channel.send(null) { diff --git a/packages/amplify_datastore/example/ios/Runner.xcodeproj/project.pbxproj b/packages/amplify_datastore/example/ios/Runner.xcodeproj/project.pbxproj index 1141635967..2323e9e307 100644 --- a/packages/amplify_datastore/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/amplify_datastore/example/ios/Runner.xcodeproj/project.pbxproj @@ -693,6 +693,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = DCGZ9P88MJ; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 13.0; @@ -700,7 +701,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.amazonaws.amplify.amplifyDatastoreExample; + PRODUCT_BUNDLE_IDENTIFIER = com.amazonaws.amplify.quelija.amplifyDatastoreExample; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -927,6 +928,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = DCGZ9P88MJ; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 13.0; @@ -934,7 +936,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.amazonaws.amplify.amplifyDatastoreExample; + PRODUCT_BUNDLE_IDENTIFIER = com.amazonaws.amplify.quelija.amplifyDatastoreExample; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -950,6 +952,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = DCGZ9P88MJ; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 13.0; @@ -957,7 +960,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.amazonaws.amplify.amplifyDatastoreExample; + PRODUCT_BUNDLE_IDENTIFIER = com.amazonaws.amplify.quelija.amplifyDatastoreExample; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; diff --git a/packages/amplify_datastore/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/amplify_datastore/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index ca2087b9e8..39bfae25b4 100644 --- a/packages/amplify_datastore/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/amplify_datastore/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -70,6 +70,13 @@ ReferencedContainer = "container:Runner.xcodeproj"> + + + + + CADisableMinimumFrameDurationOnPhone + CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName @@ -24,6 +26,8 @@ $(FLUTTER_BUILD_NUMBER) LSRequiresIPhoneOS + UIApplicationSupportsIndirectInputEvents + UILaunchStoryboardName LaunchScreen UIMainStoryboardFile @@ -43,9 +47,5 @@ UIViewControllerBasedStatusBarAppearance - CADisableMinimumFrameDurationOnPhone - - UIApplicationSupportsIndirectInputEvents - diff --git a/packages/amplify_datastore/ios/Classes/FlutterApiPlugin.swift b/packages/amplify_datastore/ios/Classes/FlutterApiPlugin.swift index 4b82bc8051..eab85a6575 100644 --- a/packages/amplify_datastore/ios/Classes/FlutterApiPlugin.swift +++ b/packages/amplify_datastore/ios/Classes/FlutterApiPlugin.swift @@ -10,6 +10,7 @@ public class FlutterApiPlugin: APICategoryPlugin, AWSAPIAuthInformation private let nativeSubscriptionEvents: PassthroughSubject private var cancellables = AtomicDictionary() private var endpoints: [String: String] + private var networkMonitor: AmplifyNetworkMonitor init( apiAuthProviderFactory: APIAuthProviderFactory, @@ -21,6 +22,7 @@ public class FlutterApiPlugin: APICategoryPlugin, AWSAPIAuthInformation self.nativeApiPlugin = nativeApiPlugin self.nativeSubscriptionEvents = subscriptionEventBus self.endpoints = endpoints + self.networkMonitor = AmplifyNetworkMonitor() } public func defaultAuthType() throws -> AWSAuthorizationType { @@ -122,6 +124,11 @@ public class FlutterApiPlugin: APICategoryPlugin, AWSAPIAuthInformation errors.contains(where: self.isUnauthorizedError(graphQLError:)) { return Fail(error: APIError.operationError("Unauthorized", "", nil)).eraseToAnyPublisher() } + if case .data(.failure(let graphQLResponseError)) = event, + case .error(let errors) = graphQLResponseError, + errors.contains(where: self.isFlutterNetworkError(graphQLError:)){ + return Fail(error: APIError.networkError("FlutterNetworkException", nil, URLError(.networkConnectionLost))).eraseToAnyPublisher() + } return Just(event).setFailureType(to: Error.self).eraseToAnyPublisher() } .eraseToAnyPublisher() @@ -182,6 +189,13 @@ public class FlutterApiPlugin: APICategoryPlugin, AWSAPIAuthInformation } return errorTypeValue == "Unauthorized" } + + private func isFlutterNetworkError(graphQLError: GraphQLError) -> Bool { + guard case let .string(errorTypeValue) = graphQLError.extensions?["errorType"] else { + return false + } + return errorTypeValue == "FlutterNetworkException" + } func asyncQuery(nativeRequest: NativeGraphQLRequest) async -> NativeGraphQLResponse { await withCheckedContinuation { continuation in @@ -237,13 +251,30 @@ public class FlutterApiPlugin: APICategoryPlugin, AWSAPIAuthInformation preconditionFailure("method not supported") } + private var cancellable: AnyCancellable? + public func reachabilityPublisher(for apiName: String?) throws -> AnyPublisher? { - preconditionFailure("method not supported") + let reachabilityUpdates = PassthroughSubject() + + // Listen to network events and send a notification to Flutter side when disconnected. + // This enables Flutter to clean up the websocket/subscriptions. + cancellable = networkMonitor.publisher.sink(receiveValue: {event in + switch event { + case (.offline, .online): + reachabilityUpdates.send(ReachabilityUpdate(isOnline: true)) + case (.online, .offline): + reachabilityUpdates.send(ReachabilityUpdate(isOnline: false)) + DispatchQueue.main.async { + self.nativeApiPlugin.deviceOffline {} + } + default: + break + } + }) + return reachabilityUpdates.eraseToAnyPublisher() } public func reachabilityPublisher() throws -> AnyPublisher? { - return nil + return try reachabilityPublisher(for: nil) } - - } diff --git a/packages/amplify_datastore/ios/Classes/SwiftAmplifyDataStorePlugin.swift b/packages/amplify_datastore/ios/Classes/SwiftAmplifyDataStorePlugin.swift index 7ee5db422f..aa72d3983a 100644 --- a/packages/amplify_datastore/ios/Classes/SwiftAmplifyDataStorePlugin.swift +++ b/packages/amplify_datastore/ios/Classes/SwiftAmplifyDataStorePlugin.swift @@ -5,7 +5,7 @@ import Flutter import UIKit import Combine -public class SwiftAmplifyDataStorePlugin: NSObject, FlutterPlugin, NativeAmplifyBridge, NativeAuthBridge, NativeApiBridge { +public class SwiftAmplifyDataStorePlugin: NSObject, FlutterPlugin, NativeAmplifyBridge, NativeAuthBridge, NativeApiBridge { private let bridge: DataStoreBridge private let modelSchemaRegistry: FlutterSchemaRegistry private let customTypeSchemaRegistry: FlutterSchemaRegistry @@ -615,6 +615,9 @@ public class SwiftAmplifyDataStorePlugin: NSObject, FlutterPlugin, NativeAmplify error: error, flutterResult: flutterResult) case .success(): + DispatchQueue.main.async { + self.nativeApiPlugin.onStop {} + } print("Successfully stopped datastore cloud syncing") flutterResult(nil) } diff --git a/packages/amplify_datastore/ios/Classes/api/GraphQLResponse+Decode.swift b/packages/amplify_datastore/ios/Classes/api/GraphQLResponse+Decode.swift index 289fa1d2e6..5b79869d97 100644 --- a/packages/amplify_datastore/ios/Classes/api/GraphQLResponse+Decode.swift +++ b/packages/amplify_datastore/ios/Classes/api/GraphQLResponse+Decode.swift @@ -141,7 +141,14 @@ extension GraphQLResponse { uniquingKeysWith: { _, a in a } ) } - + + if error.message?.stringValue?.contains("FlutterNetworkException") == true { + extensions = extensions.merging( + ["errorType": "FlutterNetworkException"], + uniquingKeysWith: { _, a in a } + ) + } + return (try? jsonEncoder.encode(error)) .flatMap { try? jsonDecoder.decode(GraphQLError.self, from: $0) } .map { diff --git a/packages/amplify_datastore/ios/Classes/pigeons/NativePluginBindings.swift b/packages/amplify_datastore/ios/Classes/pigeons/NativePluginBindings.swift index 9f4386ced4..df1b268a75 100644 --- a/packages/amplify_datastore/ios/Classes/pigeons/NativePluginBindings.swift +++ b/packages/amplify_datastore/ios/Classes/pigeons/NativePluginBindings.swift @@ -401,6 +401,12 @@ class NativeApiPlugin { completion() } } + func deviceOffline(completion: @escaping () -> Void) { + let channel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.amplify_datastore.NativeApiPlugin.deviceOffline", binaryMessenger: binaryMessenger, codec: codec) + channel.sendMessage(nil) { _ in + completion() + } + } func onStop(completion: @escaping () -> Void) { let channel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.amplify_datastore.NativeApiPlugin.onStop", binaryMessenger: binaryMessenger, codec: codec) channel.sendMessage(nil) { _ in diff --git a/packages/amplify_datastore/lib/amplify_datastore.dart b/packages/amplify_datastore/lib/amplify_datastore.dart index 3e4fc8ec4b..f95c2c0b0d 100644 --- a/packages/amplify_datastore/lib/amplify_datastore.dart +++ b/packages/amplify_datastore/lib/amplify_datastore.dart @@ -376,6 +376,30 @@ class NativeAmplifyApi } } + @override + Future deviceOffline() async { + await notifySubscriptionsDisconnected(); + } + + Future notifySubscriptionsDisconnected() async { + _subscriptionsCache.forEach((subId, _) async { + // Send Swift subscriptions an expected error message when network was lost. + // Swift side is expecting this string to transform into the correct error type. + // This will cause the Sync Engine to stop and in order to recover it later we must + // unsubscribe and close the websocket. + GraphQLResponseError error = GraphQLResponseError( + message: 'FlutterNetworkException - Network disconnected', + ); + sendSubscriptionStreamErrorEvent(subId, error.toJson()); + // Note: the websocket will still be closing after this line. + // There may be a small delay in sync engine recovery if turned back on + // within ~60 seconds. This is due to the reconnection logic doing a retry/backoff. + // TODO(equartey): To decrease the delay, expose some way to shutdown the web socket + // w/o going through the reconnection logic. + await unsubscribe(subId); + }); + } + /// Amplify.DataStore.Stop() callback /// /// Clean up subscriptions on stop. diff --git a/packages/amplify_datastore/lib/src/native_plugin.g.dart b/packages/amplify_datastore/lib/src/native_plugin.g.dart index 49bb3c66b7..c87f9b9994 100644 --- a/packages/amplify_datastore/lib/src/native_plugin.g.dart +++ b/packages/amplify_datastore/lib/src/native_plugin.g.dart @@ -358,6 +358,8 @@ abstract class NativeApiPlugin { Future unsubscribe(String subscriptionId); + Future deviceOffline(); + Future onStop(); static void setup(NativeApiPlugin? api, {BinaryMessenger? binaryMessenger}) { @@ -464,6 +466,21 @@ abstract class NativeApiPlugin { }); } } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.amplify_datastore.NativeApiPlugin.deviceOffline', + codec, + binaryMessenger: binaryMessenger); + if (api == null) { + channel.setMessageHandler(null); + } else { + channel.setMessageHandler((Object? message) async { + // ignore message + await api.deviceOffline(); + return; + }); + } + } { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.amplify_datastore.NativeApiPlugin.onStop', codec, diff --git a/packages/amplify_datastore/pigeons/native_plugin.dart b/packages/amplify_datastore/pigeons/native_plugin.dart index 8c49e0d3cb..cf09fd9042 100644 --- a/packages/amplify_datastore/pigeons/native_plugin.dart +++ b/packages/amplify_datastore/pigeons/native_plugin.dart @@ -42,6 +42,9 @@ abstract class NativeApiPlugin { @async void unsubscribe(String subscriptionId); + @async + void deviceOffline(); + @async void onStop(); } diff --git a/packages/api/amplify_api_dart/lib/src/graphql/web_socket/blocs/subscriptions_bloc.dart b/packages/api/amplify_api_dart/lib/src/graphql/web_socket/blocs/subscriptions_bloc.dart index 027dae236e..3f98fc8a30 100644 --- a/packages/api/amplify_api_dart/lib/src/graphql/web_socket/blocs/subscriptions_bloc.dart +++ b/packages/api/amplify_api_dart/lib/src/graphql/web_socket/blocs/subscriptions_bloc.dart @@ -142,11 +142,7 @@ class SubscriptionBloc } Stream> _complete(SubscriptionComplete event) async* { - assert( - _currentState is SubscriptionListeningState, - 'State should always be listening when completed.', - ); - yield (_currentState as SubscriptionListeningState).complete(); + yield _currentState.complete(); await close(); } From bb22b24ca2f743357caf83a30f67db6f1af2dce4 Mon Sep 17 00:00:00 2001 From: Elijah Quartey Date: Thu, 8 Aug 2024 11:15:59 -0500 Subject: [PATCH 02/14] add start/stop/clear buttons --- .../amplify_datastore/example/lib/main.dart | 9 ++++--- .../example/lib/queries_display_widgets.dart | 26 +++++++++++++++++++ 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/packages/amplify_datastore/example/lib/main.dart b/packages/amplify_datastore/example/lib/main.dart index 08c6487b77..cde020ca08 100644 --- a/packages/amplify_datastore/example/lib/main.dart +++ b/packages/amplify_datastore/example/lib/main.dart @@ -162,11 +162,12 @@ class _MyAppState extends State { void listenToHub() { setState(() { hubSubscription = Amplify.Hub.listen(HubChannel.DataStore, (msg) { - if (msg.type case DataStoreHubEventType.networkStatus) { - print('Network status message: $msg'); + final payload = msg.payload; + if (payload is NetworkStatusEvent) { + print('Network status active: ${payload.active}'); return; } - print(msg); + print(msg.type); }); _listeningToHub = true; }); @@ -317,6 +318,8 @@ class _MyAppState extends State { displayQueryButtons( _isAmplifyConfigured, _queriesToView, updateQueriesToView), +displaySyncButtons(), + Padding(padding: EdgeInsets.all(5.0)), Text("Listen to DataStore Hub"), Switch( diff --git a/packages/amplify_datastore/example/lib/queries_display_widgets.dart b/packages/amplify_datastore/example/lib/queries_display_widgets.dart index 1e8013a7e7..d695cb4aaa 100644 --- a/packages/amplify_datastore/example/lib/queries_display_widgets.dart +++ b/packages/amplify_datastore/example/lib/queries_display_widgets.dart @@ -175,3 +175,29 @@ Widget getWidgetToDisplayComment( }), ); } + +Widget displaySyncButtons() { + return Row(mainAxisAlignment: MainAxisAlignment.center, children: [ + VerticalDivider( + color: Colors.white, + width: 5, + ), + ElevatedButton.icon( + onPressed: () { + Amplify.DataStore.start(); + }, + label: Icon(Icons.play_arrow)), + divider, + ElevatedButton.icon( + onPressed: () { + Amplify.DataStore.stop(); + }, + label: Icon(Icons.stop)), + divider, + ElevatedButton.icon( + onPressed: () { + Amplify.DataStore.clear(); + }, + label: Icon(Icons.delete_sweep)), + ]); +} From c374c5181c3a5178d0d0084bcd767628c8f1fbed Mon Sep 17 00:00:00 2001 From: Elijah Quartey Date: Thu, 8 Aug 2024 13:22:47 -0500 Subject: [PATCH 03/14] reachability feedback --- .../ios/Classes/FlutterApiPlugin.swift | 33 +++++++++++-------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/packages/amplify_datastore/ios/Classes/FlutterApiPlugin.swift b/packages/amplify_datastore/ios/Classes/FlutterApiPlugin.swift index eab85a6575..6c7022054e 100644 --- a/packages/amplify_datastore/ios/Classes/FlutterApiPlugin.swift +++ b/packages/amplify_datastore/ios/Classes/FlutterApiPlugin.swift @@ -11,6 +11,7 @@ public class FlutterApiPlugin: APICategoryPlugin, AWSAPIAuthInformation private var cancellables = AtomicDictionary() private var endpoints: [String: String] private var networkMonitor: AmplifyNetworkMonitor + private var reachabilitySubscription: AnyCancellable? init( apiAuthProviderFactory: APIAuthProviderFactory, @@ -23,6 +24,20 @@ public class FlutterApiPlugin: APICategoryPlugin, AWSAPIAuthInformation self.nativeSubscriptionEvents = subscriptionEventBus self.endpoints = endpoints self.networkMonitor = AmplifyNetworkMonitor() + + // Listen to network events and send a notification to Flutter side when disconnected. + // This enables Flutter to clean up the websocket/subscriptions. + do { + reachabilitySubscription = try reachabilityPublisher()?.sink(receiveValue: { reachabilityUpdate in + if !reachabilityUpdate.isOnline { + DispatchQueue.main.async { + self.nativeApiPlugin.deviceOffline {} + } + } + }) + } catch { + print("Failed to create reachability publisher: \(error)") + } } public func defaultAuthType() throws -> AWSAuthorizationType { @@ -254,24 +269,16 @@ public class FlutterApiPlugin: APICategoryPlugin, AWSAPIAuthInformation private var cancellable: AnyCancellable? public func reachabilityPublisher(for apiName: String?) throws -> AnyPublisher? { - let reachabilityUpdates = PassthroughSubject() - - // Listen to network events and send a notification to Flutter side when disconnected. - // This enables Flutter to clean up the websocket/subscriptions. - cancellable = networkMonitor.publisher.sink(receiveValue: {event in + return networkMonitor.publisher.compactMap( { event in switch event { case (.offline, .online): - reachabilityUpdates.send(ReachabilityUpdate(isOnline: true)) + return ReachabilityUpdate(isOnline: true) case (.online, .offline): - reachabilityUpdates.send(ReachabilityUpdate(isOnline: false)) - DispatchQueue.main.async { - self.nativeApiPlugin.deviceOffline {} - } + return ReachabilityUpdate(isOnline: false) default: - break + return nil } - }) - return reachabilityUpdates.eraseToAnyPublisher() + }).eraseToAnyPublisher() } public func reachabilityPublisher() throws -> AnyPublisher? { From 08f89cf7b24f8bee7279e1156c1f6438108b4fda Mon Sep 17 00:00:00 2001 From: Elijah Quartey Date: Thu, 8 Aug 2024 13:24:05 -0500 Subject: [PATCH 04/14] removed unused type --- packages/amplify_datastore/ios/Classes/FlutterApiPlugin.swift | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/amplify_datastore/ios/Classes/FlutterApiPlugin.swift b/packages/amplify_datastore/ios/Classes/FlutterApiPlugin.swift index 6c7022054e..9644147410 100644 --- a/packages/amplify_datastore/ios/Classes/FlutterApiPlugin.swift +++ b/packages/amplify_datastore/ios/Classes/FlutterApiPlugin.swift @@ -265,9 +265,7 @@ public class FlutterApiPlugin: APICategoryPlugin, AWSAPIAuthInformation public func patch(request: RESTRequest) async throws -> RESTTask.Success { preconditionFailure("method not supported") } - - private var cancellable: AnyCancellable? - + public func reachabilityPublisher(for apiName: String?) throws -> AnyPublisher? { return networkMonitor.publisher.compactMap( { event in switch event { From c0388b1a22318cf9292eebe87ea1dfdf056e3548 Mon Sep 17 00:00:00 2001 From: Elijah Quartey Date: Thu, 8 Aug 2024 14:13:45 -0500 Subject: [PATCH 05/14] reuse cancellables dictionary --- .../amplify_datastore/ios/Classes/FlutterApiPlugin.swift | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/amplify_datastore/ios/Classes/FlutterApiPlugin.swift b/packages/amplify_datastore/ios/Classes/FlutterApiPlugin.swift index 9644147410..fb1020cc43 100644 --- a/packages/amplify_datastore/ios/Classes/FlutterApiPlugin.swift +++ b/packages/amplify_datastore/ios/Classes/FlutterApiPlugin.swift @@ -8,10 +8,9 @@ public class FlutterApiPlugin: APICategoryPlugin, AWSAPIAuthInformation private let apiAuthFactory: APIAuthProviderFactory private let nativeApiPlugin: NativeApiPlugin private let nativeSubscriptionEvents: PassthroughSubject - private var cancellables = AtomicDictionary() + private var cancellables = AtomicDictionary() private var endpoints: [String: String] private var networkMonitor: AmplifyNetworkMonitor - private var reachabilitySubscription: AnyCancellable? init( apiAuthProviderFactory: APIAuthProviderFactory, @@ -28,13 +27,15 @@ public class FlutterApiPlugin: APICategoryPlugin, AWSAPIAuthInformation // Listen to network events and send a notification to Flutter side when disconnected. // This enables Flutter to clean up the websocket/subscriptions. do { - reachabilitySubscription = try reachabilityPublisher()?.sink(receiveValue: { reachabilityUpdate in + let cancellable = try reachabilityPublisher()?.sink(receiveValue: { reachabilityUpdate in if !reachabilityUpdate.isOnline { DispatchQueue.main.async { self.nativeApiPlugin.deviceOffline {} } } }) + cancellables.set(value: (), forKey: cancellable) // the subscription is bind with class instance lifecycle, it should be released when stream is finished or unsubscribed + } catch { print("Failed to create reachability publisher: \(error)") } From 42e49efb1332d006ef366328cd2ca98fc327f0f7 Mon Sep 17 00:00:00 2001 From: Elijah Quartey Date: Tue, 13 Aug 2024 13:45:36 -0600 Subject: [PATCH 06/14] added static method to turn off reconnect behavior --- .../lib/src/types/api/api_types.dart | 1 + .../types/api/graphql/web_socket_options.dart | 21 ++++++++++++++ .../amplify_datastore/example/lib/main.dart | 3 +- .../ios/Classes/FlutterApiPlugin.swift | 22 ++++++++------- .../Classes/SwiftAmplifyDataStorePlugin.swift | 2 -- .../Classes/api/GraphQLResponse+Decode.swift | 2 +- .../lib/amplify_datastore.dart | 7 +++-- .../web_socket/blocs/web_socket_bloc.dart | 28 ++++++++++++++++--- 8 files changed, 66 insertions(+), 20 deletions(-) create mode 100644 packages/amplify_core/lib/src/types/api/graphql/web_socket_options.dart diff --git a/packages/amplify_core/lib/src/types/api/api_types.dart b/packages/amplify_core/lib/src/types/api/api_types.dart index 4f6b8a0408..f5fb215a37 100644 --- a/packages/amplify_core/lib/src/types/api/api_types.dart +++ b/packages/amplify_core/lib/src/types/api/api_types.dart @@ -22,6 +22,7 @@ export 'graphql/graphql_response.dart'; export 'graphql/graphql_response_error.dart'; export 'graphql/graphql_subscription_operation.dart'; export 'graphql/graphql_subscription_options.dart'; +export 'graphql/web_socket_options.dart'; export 'hub/api_hub_event.dart'; export 'hub/api_subscription_hub_event.dart'; // Types diff --git a/packages/amplify_core/lib/src/types/api/graphql/web_socket_options.dart b/packages/amplify_core/lib/src/types/api/graphql/web_socket_options.dart new file mode 100644 index 0000000000..eee15bd339 --- /dev/null +++ b/packages/amplify_core/lib/src/types/api/graphql/web_socket_options.dart @@ -0,0 +1,21 @@ +import 'package:meta/meta.dart'; // Importing the 'meta' package to use the @internal annotation + +/// An internal class to control websocket features after API plugin has been initialized. +@internal +class WebSocketOptions { + /// Private constructor to prevent instantiation + WebSocketOptions._(); + + /// Private static boolean field + static bool _autoReconnect = true; + + /// Static getter method for the boolean field + @internal + static bool get autoReconnect => _autoReconnect; + + /// Static setter method for the boolean field + @internal + static set autoReconnect(bool value) { + _autoReconnect = value; + } +} diff --git a/packages/amplify_datastore/example/lib/main.dart b/packages/amplify_datastore/example/lib/main.dart index cde020ca08..3789a5d1d5 100644 --- a/packages/amplify_datastore/example/lib/main.dart +++ b/packages/amplify_datastore/example/lib/main.dart @@ -318,7 +318,8 @@ class _MyAppState extends State { displayQueryButtons( _isAmplifyConfigured, _queriesToView, updateQueriesToView), -displaySyncButtons(), + // Start/Stop/Clear buttons + displaySyncButtons(), Padding(padding: EdgeInsets.all(5.0)), Text("Listen to DataStore Hub"), diff --git a/packages/amplify_datastore/ios/Classes/FlutterApiPlugin.swift b/packages/amplify_datastore/ios/Classes/FlutterApiPlugin.swift index fb1020cc43..3443e244ce 100644 --- a/packages/amplify_datastore/ios/Classes/FlutterApiPlugin.swift +++ b/packages/amplify_datastore/ios/Classes/FlutterApiPlugin.swift @@ -268,16 +268,18 @@ public class FlutterApiPlugin: APICategoryPlugin, AWSAPIAuthInformation } public func reachabilityPublisher(for apiName: String?) throws -> AnyPublisher? { - return networkMonitor.publisher.compactMap( { event in - switch event { - case (.offline, .online): - return ReachabilityUpdate(isOnline: true) - case (.online, .offline): - return ReachabilityUpdate(isOnline: false) - default: - return nil - } - }).eraseToAnyPublisher() + return networkMonitor.publisher + .compactMap { event in + switch event { + case (.offline, .online): + return ReachabilityUpdate(isOnline: true) + case (.online, .offline): + return ReachabilityUpdate(isOnline: false) + default: + return nil + } + } + .eraseToAnyPublisher() } public func reachabilityPublisher() throws -> AnyPublisher? { diff --git a/packages/amplify_datastore/ios/Classes/SwiftAmplifyDataStorePlugin.swift b/packages/amplify_datastore/ios/Classes/SwiftAmplifyDataStorePlugin.swift index aa72d3983a..49b1b5e40e 100644 --- a/packages/amplify_datastore/ios/Classes/SwiftAmplifyDataStorePlugin.swift +++ b/packages/amplify_datastore/ios/Classes/SwiftAmplifyDataStorePlugin.swift @@ -145,8 +145,6 @@ public class SwiftAmplifyDataStorePlugin: NSObject, FlutterPlugin, NativeAmplify nil ) } - // TODO: Migrate to Async Swift v2 - // AmplifyAWSServiceConfiguration.addUserAgentPlatform(.flutter, version: "\(version) /datastore") try Amplify.configure(with : .data(data)) return completion(.success(())) } catch let error as ConfigurationError { diff --git a/packages/amplify_datastore/ios/Classes/api/GraphQLResponse+Decode.swift b/packages/amplify_datastore/ios/Classes/api/GraphQLResponse+Decode.swift index 5b79869d97..f5a9c6a8d7 100644 --- a/packages/amplify_datastore/ios/Classes/api/GraphQLResponse+Decode.swift +++ b/packages/amplify_datastore/ios/Classes/api/GraphQLResponse+Decode.swift @@ -142,7 +142,7 @@ extension GraphQLResponse { ) } - if error.message?.stringValue?.contains("FlutterNetworkException") == true { + if error.message?.stringValue?.contains("NetworkException") == true { extensions = extensions.merging( ["errorType": "FlutterNetworkException"], uniquingKeysWith: { _, a in a } diff --git a/packages/amplify_datastore/lib/amplify_datastore.dart b/packages/amplify_datastore/lib/amplify_datastore.dart index f95c2c0b0d..56b72d2f9b 100644 --- a/packages/amplify_datastore/lib/amplify_datastore.dart +++ b/packages/amplify_datastore/lib/amplify_datastore.dart @@ -350,7 +350,9 @@ class NativeAmplifyApi Future subscribe( NativeGraphQLRequest request) async { final flutterRequest = nativeRequestToGraphQLRequest(request); - + // Turn off then default reconnection behavior to allow native side to trigger reconnect + // ignore: invalid_use_of_internal_member + WebSocketOptions.autoReconnect = false; final operation = Amplify.API.subscribe(flutterRequest, onEstablished: () => sendNativeStartAckEvent(flutterRequest.id)); @@ -382,7 +384,7 @@ class NativeAmplifyApi } Future notifySubscriptionsDisconnected() async { - _subscriptionsCache.forEach((subId, _) async { + _subscriptionsCache.forEach((subId, stream) async { // Send Swift subscriptions an expected error message when network was lost. // Swift side is expecting this string to transform into the correct error type. // This will cause the Sync Engine to stop and in order to recover it later we must @@ -397,6 +399,7 @@ class NativeAmplifyApi // TODO(equartey): To decrease the delay, expose some way to shutdown the web socket // w/o going through the reconnection logic. await unsubscribe(subId); + await stream.cancel(); }); } diff --git a/packages/api/amplify_api_dart/lib/src/graphql/web_socket/blocs/web_socket_bloc.dart b/packages/api/amplify_api_dart/lib/src/graphql/web_socket/blocs/web_socket_bloc.dart index 9f679d5076..0903db904b 100644 --- a/packages/api/amplify_api_dart/lib/src/graphql/web_socket/blocs/web_socket_bloc.dart +++ b/packages/api/amplify_api_dart/lib/src/graphql/web_socket/blocs/web_socket_bloc.dart @@ -53,8 +53,10 @@ class WebSocketBloc with AWSDebuggable, AmplifyLoggerMixin { add(const InitEvent()); } + final blocId = uuid(); + @override - String get runtimeTypeName => 'WebSocketBloc'; + String get runtimeTypeName => 'WebSocketBloc - $blocId'; /// Default timeout response for polling static const Duration _pollResponseTimeout = Duration(seconds: 5); @@ -221,8 +223,11 @@ class WebSocketBloc with AWSDebuggable, AmplifyLoggerMixin { () => _timeout(timeoutDuration), ); - final pollTimer = - Timer.periodic(_currentState.options.pollInterval, (_) => _poll()); + final pollTimer = Timer.periodic( + _currentState.options.pollInterval, + // ignore: invalid_use_of_internal_member + (_) => WebSocketOptions.autoReconnect ? _poll() : () {}, + ); final connectedState = (_currentState as ConnectingState).connected( timeoutTimer, @@ -474,13 +479,28 @@ class WebSocketBloc with AWSDebuggable, AmplifyLoggerMixin { /// Connectivity stream monitors network availability on a hardware level. StreamSubscription _getConnectivityStream() { + var prev = ConnectivityStatus.disconnected; return _connectivity.onConnectivityChanged.listen( (status) { - if (status == ConnectivityStatus.connected) { + // ignore: invalid_use_of_internal_member + if (!WebSocketOptions.autoReconnect) { + // shutdown the socket when autoReconnect is turned off + if (status == ConnectivityStatus.disconnected && + prev == ConnectivityStatus.connected) { + _shutdownWithException( + const NetworkException( + 'Unable to recover network connection, web socket will close.', + recoverySuggestion: 'Check internet connection.', + ), + StackTrace.current, + ); + } + } else if (status == ConnectivityStatus.connected) { add(const NetworkFoundEvent()); } else if (status == ConnectivityStatus.disconnected) { add(const NetworkLossEvent()); } + prev = status; }, onError: (Object e, StackTrace st) => logger.error('Error in connectivity stream $e, $st'), From b0bd4248e2527bddf53db2dbeb506666fc8bba39 Mon Sep 17 00:00:00 2001 From: Elijah Quartey Date: Tue, 13 Aug 2024 13:51:08 -0600 Subject: [PATCH 07/14] clean up comment --- .../amplify_datastore/lib/amplify_datastore.dart | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/packages/amplify_datastore/lib/amplify_datastore.dart b/packages/amplify_datastore/lib/amplify_datastore.dart index 56b72d2f9b..b71231d95c 100644 --- a/packages/amplify_datastore/lib/amplify_datastore.dart +++ b/packages/amplify_datastore/lib/amplify_datastore.dart @@ -380,24 +380,21 @@ class NativeAmplifyApi @override Future deviceOffline() async { - await notifySubscriptionsDisconnected(); + await _notifySubscriptionsDisconnected(); } - Future notifySubscriptionsDisconnected() async { + Future _notifySubscriptionsDisconnected() async { _subscriptionsCache.forEach((subId, stream) async { - // Send Swift subscriptions an expected error message when network was lost. + // Send Swift subscriptions an expected error message when network is lost. // Swift side is expecting this string to transform into the correct error type. - // This will cause the Sync Engine to stop and in order to recover it later we must - // unsubscribe and close the websocket. + // This will cause the Sync Engine to enter retry mode and in order to recover it + // later we must unsubscribe and close the websocket. GraphQLResponseError error = GraphQLResponseError( message: 'FlutterNetworkException - Network disconnected', ); sendSubscriptionStreamErrorEvent(subId, error.toJson()); // Note: the websocket will still be closing after this line. - // There may be a small delay in sync engine recovery if turned back on - // within ~60 seconds. This is due to the reconnection logic doing a retry/backoff. - // TODO(equartey): To decrease the delay, expose some way to shutdown the web socket - // w/o going through the reconnection logic. + // There may be a small delay in shutdown. await unsubscribe(subId); await stream.cancel(); }); From 5c0245a4191671eb10d7681454b99aae3ebe2fb9 Mon Sep 17 00:00:00 2001 From: Elijah Quartey Date: Tue, 13 Aug 2024 14:00:31 -0600 Subject: [PATCH 08/14] fix lint issue --- packages/api/amplify_api_dart/lib/amplify_api_dart.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/api/amplify_api_dart/lib/amplify_api_dart.dart b/packages/api/amplify_api_dart/lib/amplify_api_dart.dart index 44022623ce..e25c15fb25 100644 --- a/packages/api/amplify_api_dart/lib/amplify_api_dart.dart +++ b/packages/api/amplify_api_dart/lib/amplify_api_dart.dart @@ -4,7 +4,8 @@ /// Amplify API for Dart library amplify_api_dart; -export 'package:amplify_core/src/types/api/api_types.dart'; +export 'package:amplify_core/src/types/api/api_types.dart' + hide WebSocketOptions; export 'src/api_plugin_impl.dart'; From f70dbc377114bfc483cd7534b1fbaf8514e9bac4 Mon Sep 17 00:00:00 2001 From: Elijah Quartey Date: Tue, 13 Aug 2024 14:01:52 -0600 Subject: [PATCH 09/14] fix lint issue --- packages/amplify_core/lib/amplify_core.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/amplify_core/lib/amplify_core.dart b/packages/amplify_core/lib/amplify_core.dart index 745f3aedf0..9c31fb2e21 100644 --- a/packages/amplify_core/lib/amplify_core.dart +++ b/packages/amplify_core/lib/amplify_core.dart @@ -62,9 +62,11 @@ export 'src/state_machine/transition.dart'; /// Analytics export 'src/types/analytics/analytics_types.dart'; +// ignore: invalid_export_of_internal_element +export 'src/types/api/api_types.dart' show WebSocketOptions; /// API -export 'src/types/api/api_types.dart'; +export 'src/types/api/api_types.dart' hide WebSocketOptions; /// App path provider export 'src/types/app_path_provider/app_path_provider.dart'; From 5619beeb41e09aee56b1ce0e4e7651afdb9e645b Mon Sep 17 00:00:00 2001 From: Elijah Quartey Date: Tue, 13 Aug 2024 14:07:56 -0600 Subject: [PATCH 10/14] fix linter issue --- packages/amplify/amplify_flutter/lib/amplify_flutter.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/amplify/amplify_flutter/lib/amplify_flutter.dart b/packages/amplify/amplify_flutter/lib/amplify_flutter.dart index c8cc4241dc..f197054c2b 100644 --- a/packages/amplify/amplify_flutter/lib/amplify_flutter.dart +++ b/packages/amplify/amplify_flutter/lib/amplify_flutter.dart @@ -6,7 +6,7 @@ library amplify_flutter; import 'package:amplify_core/amplify_core.dart'; import 'package:amplify_flutter/src/amplify_impl.dart'; -export 'package:amplify_core/amplify_core.dart' hide Amplify; +export 'package:amplify_core/amplify_core.dart' hide Amplify, WebSocketOptions; export 'package:amplify_secure_storage/amplify_secure_storage.dart'; /// Top level singleton Amplify object. From 2c9887be0fe9086e4fb98fe7e55891f321f0680f Mon Sep 17 00:00:00 2001 From: Elijah Quartey Date: Tue, 13 Aug 2024 14:34:37 -0600 Subject: [PATCH 11/14] fix lint issue --- .../example/lib/queries_display_widgets.dart | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/packages/amplify_datastore/example/lib/queries_display_widgets.dart b/packages/amplify_datastore/example/lib/queries_display_widgets.dart index d695cb4aaa..792a39857a 100644 --- a/packages/amplify_datastore/example/lib/queries_display_widgets.dart +++ b/packages/amplify_datastore/example/lib/queries_display_widgets.dart @@ -183,21 +183,27 @@ Widget displaySyncButtons() { width: 5, ), ElevatedButton.icon( - onPressed: () { - Amplify.DataStore.start(); - }, - label: Icon(Icons.play_arrow)), + onPressed: () { + Amplify.DataStore.start(); + }, + icon: Icon(Icons.play_arrow), + label: const Text("Start"), + ), divider, ElevatedButton.icon( - onPressed: () { - Amplify.DataStore.stop(); - }, - label: Icon(Icons.stop)), + onPressed: () { + Amplify.DataStore.stop(); + }, + icon: Icon(Icons.stop), + label: const Text("Stop"), + ), divider, ElevatedButton.icon( - onPressed: () { - Amplify.DataStore.clear(); - }, - label: Icon(Icons.delete_sweep)), + onPressed: () { + Amplify.DataStore.clear(); + }, + icon: Icon(Icons.delete_sweep), + label: const Text("Clear"), + ), ]); } From bfbf2677655c3341549a799ad54adc833e0d53b9 Mon Sep 17 00:00:00 2001 From: Elijah Quartey Date: Tue, 13 Aug 2024 14:42:53 -0600 Subject: [PATCH 12/14] remove old change --- .../ios/Classes/SwiftAmplifyDataStorePlugin.swift | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/amplify_datastore/ios/Classes/SwiftAmplifyDataStorePlugin.swift b/packages/amplify_datastore/ios/Classes/SwiftAmplifyDataStorePlugin.swift index 49b1b5e40e..c3775556a2 100644 --- a/packages/amplify_datastore/ios/Classes/SwiftAmplifyDataStorePlugin.swift +++ b/packages/amplify_datastore/ios/Classes/SwiftAmplifyDataStorePlugin.swift @@ -5,7 +5,7 @@ import Flutter import UIKit import Combine -public class SwiftAmplifyDataStorePlugin: NSObject, FlutterPlugin, NativeAmplifyBridge, NativeAuthBridge, NativeApiBridge { +public class SwiftAmplifyDataStorePlugin: NSObject, FlutterPlugin, NativeAmplifyBridge, NativeAuthBridge, NativeApiBridge { private let bridge: DataStoreBridge private let modelSchemaRegistry: FlutterSchemaRegistry private let customTypeSchemaRegistry: FlutterSchemaRegistry @@ -613,9 +613,6 @@ public class SwiftAmplifyDataStorePlugin: NSObject, FlutterPlugin, NativeAmplify error: error, flutterResult: flutterResult) case .success(): - DispatchQueue.main.async { - self.nativeApiPlugin.onStop {} - } print("Successfully stopped datastore cloud syncing") flutterResult(nil) } From 5094ec44f64ec0db46552c0df79cbb4fb6efc374 Mon Sep 17 00:00:00 2001 From: Elijah Quartey Date: Wed, 14 Aug 2024 12:32:38 -0500 Subject: [PATCH 13/14] reorder export --- packages/amplify_core/lib/amplify_core.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/amplify_core/lib/amplify_core.dart b/packages/amplify_core/lib/amplify_core.dart index 9c31fb2e21..1fed515499 100644 --- a/packages/amplify_core/lib/amplify_core.dart +++ b/packages/amplify_core/lib/amplify_core.dart @@ -62,11 +62,11 @@ export 'src/state_machine/transition.dart'; /// Analytics export 'src/types/analytics/analytics_types.dart'; -// ignore: invalid_export_of_internal_element -export 'src/types/api/api_types.dart' show WebSocketOptions; /// API export 'src/types/api/api_types.dart' hide WebSocketOptions; +// ignore: invalid_export_of_internal_element +export 'src/types/api/api_types.dart' show WebSocketOptions; /// App path provider export 'src/types/app_path_provider/app_path_provider.dart'; From 6cb763617c41ac5e6b00ca03b6e71a514ef10ae0 Mon Sep 17 00:00:00 2001 From: Elijah Quartey Date: Wed, 14 Aug 2024 12:44:21 -0500 Subject: [PATCH 14/14] revert file --- .../example/ios/Runner.xcodeproj/project.pbxproj | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/packages/amplify_datastore/example/ios/Runner.xcodeproj/project.pbxproj b/packages/amplify_datastore/example/ios/Runner.xcodeproj/project.pbxproj index 2323e9e307..1141635967 100644 --- a/packages/amplify_datastore/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/amplify_datastore/example/ios/Runner.xcodeproj/project.pbxproj @@ -693,7 +693,6 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = DCGZ9P88MJ; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 13.0; @@ -701,7 +700,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.amazonaws.amplify.quelija.amplifyDatastoreExample; + PRODUCT_BUNDLE_IDENTIFIER = com.amazonaws.amplify.amplifyDatastoreExample; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -928,7 +927,6 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = DCGZ9P88MJ; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 13.0; @@ -936,7 +934,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.amazonaws.amplify.quelija.amplifyDatastoreExample; + PRODUCT_BUNDLE_IDENTIFIER = com.amazonaws.amplify.amplifyDatastoreExample; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -952,7 +950,6 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = DCGZ9P88MJ; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 13.0; @@ -960,7 +957,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.amazonaws.amplify.quelija.amplifyDatastoreExample; + PRODUCT_BUNDLE_IDENTIFIER = com.amazonaws.amplify.amplifyDatastoreExample; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0;