From ab777f269f0ec4cac59c7cf48bb1628ef459f74e Mon Sep 17 00:00:00 2001 From: Elijah Quartey Date: Wed, 15 May 2024 12:03:17 -0500 Subject: [PATCH] chore(datastore): add query & mutate impl (#4858) --- .gitattributes | 1 + .../AmplifyDataStorePlugin.kt | 8 +++++ .../amplify_datastore/example/ios/Podfile | 6 ++-- .../DataStoreHubEventStreamHandlerTests.swift | 23 ++++++++------- .../unit_tests/DataStorePluginUnitTests.swift | 2 +- .../example/lib/queries_display_widgets.dart | 8 ++++- .../DataStoreObserveEventStreamHandler.swift | 8 +++-- .../ios/Classes/FlutterApiPlugin.swift | 29 ++++++++++++++++--- .../Classes/SwiftAmplifyDataStorePlugin.swift | 11 +++++++ .../Classes/types/hub/FlutterHubElement.swift | 1 - .../lib/amplify_datastore.dart | 10 +++++-- .../lib/src/utils/native_api_helpers.dart | 12 ++++++++ .../AmplifyNativeLegacyWrapperPlugin.swift | 2 +- 13 files changed, 94 insertions(+), 27 deletions(-) diff --git a/.gitattributes b/.gitattributes index 45707e37eb..65a69428c6 100644 --- a/.gitattributes +++ b/.gitattributes @@ -50,6 +50,7 @@ ## Platform files generated by Flutter during `flutter create` **/example/android/** linguist-generated **/example/ios/** linguist-generated +**/example/ios/unit_tests/** linguist-generated=false **/example/linux/** linguist-generated **/example/macos/** linguist-generated **/example/windows/** linguist-generated diff --git a/packages/amplify_datastore/android/src/main/kotlin/com/amazonaws/amplify/amplify_datastore/AmplifyDataStorePlugin.kt b/packages/amplify_datastore/android/src/main/kotlin/com/amazonaws/amplify/amplify_datastore/AmplifyDataStorePlugin.kt index a122e7909f..0ad51df167 100644 --- a/packages/amplify_datastore/android/src/main/kotlin/com/amazonaws/amplify/amplify_datastore/AmplifyDataStorePlugin.kt +++ b/packages/amplify_datastore/android/src/main/kotlin/com/amazonaws/amplify/amplify_datastore/AmplifyDataStorePlugin.kt @@ -19,6 +19,7 @@ import com.amazonaws.amplify.amplify_datastore.pigeons.NativeApiPlugin import com.amazonaws.amplify.amplify_datastore.pigeons.NativeAuthBridge import com.amazonaws.amplify.amplify_datastore.pigeons.NativeAuthPlugin import com.amazonaws.amplify.amplify_datastore.pigeons.NativeAuthUser +import com.amazonaws.amplify.amplify_datastore.pigeons.NativeGraphQLSubscriptionResponse import com.amazonaws.amplify.amplify_datastore.types.model.FlutterCustomTypeSchema import com.amazonaws.amplify.amplify_datastore.types.model.FlutterModelSchema import com.amazonaws.amplify.amplify_datastore.types.model.FlutterSerializedModel @@ -920,6 +921,13 @@ class AmplifyDataStorePlugin : callback(kotlin.Result.failure(e)) } } + + override fun sendSubscriptionEvent( + event: NativeGraphQLSubscriptionResponse, + callback: (kotlin.Result) -> Unit + ) { + throw NotImplementedError("Not yet implemented") + } override fun configure( version: String, diff --git a/packages/amplify_datastore/example/ios/Podfile b/packages/amplify_datastore/example/ios/Podfile index 9062b9a57e..c7e9d7d897 100644 --- a/packages/amplify_datastore/example/ios/Podfile +++ b/packages/amplify_datastore/example/ios/Podfile @@ -32,9 +32,9 @@ target 'Runner' do use_modular_headers! # Point pods to internal source files - pod 'Amplify', path: '../../ios/internal/' - pod 'AWSPluginsCore', path: '../../ios/internal/' - pod 'AWSDataStorePlugin', path: '../../ios/internal/' + pod 'Amplify', path: '../../ios/internal/', :inhibit_warnings => true + pod 'AWSPluginsCore', path: '../../ios/internal/', :inhibit_warnings => true + pod 'AWSDataStorePlugin', path: '../../ios/internal/', :inhibit_warnings => true flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) end diff --git a/packages/amplify_datastore/example/ios/unit_tests/DataStoreHubEventStreamHandlerTests.swift b/packages/amplify_datastore/example/ios/unit_tests/DataStoreHubEventStreamHandlerTests.swift index c26480fe9e..c4c66cd225 100644 --- a/packages/amplify_datastore/example/ios/unit_tests/DataStoreHubEventStreamHandlerTests.swift +++ b/packages/amplify_datastore/example/ios/unit_tests/DataStoreHubEventStreamHandlerTests.swift @@ -63,7 +63,7 @@ class DataStoreHubEventStreamHandlerTests: XCTestCase { waitForExpectations(timeout: 1.0) // cancellation needed to make sure that Hub token is invalidated to // prevent collisions between tests - hubHandler.onCancel(withArguments: nil) + let _ = hubHandler.onCancel(withArguments: nil) } func test_hub_readyEvent_success() throws { @@ -88,7 +88,7 @@ class DataStoreHubEventStreamHandlerTests: XCTestCase { let readyEventPayload = HubPayload(eventName: HubPayload.EventName.DataStore.ready) Amplify.Hub.dispatch(to: .dataStore, payload: readyEventPayload) waitForExpectations(timeout: 1.0) - hubHandler.onCancel(withArguments: nil) + let _ = hubHandler.onCancel(withArguments: nil) } func test_hub_subscriptionEstablishedEvent_success() throws { @@ -113,7 +113,7 @@ class DataStoreHubEventStreamHandlerTests: XCTestCase { let subscriptionEstablishedPayload = HubPayload(eventName: HubPayload.EventName.DataStore.subscriptionsEstablished) Amplify.Hub.dispatch(to: .dataStore, payload: subscriptionEstablishedPayload) waitForExpectations(timeout: 1.0) - hubHandler.onCancel(withArguments: nil) + let _ = hubHandler.onCancel(withArguments: nil) } func test_hub_syncQueriesReadyEvent_success() throws { @@ -138,7 +138,7 @@ class DataStoreHubEventStreamHandlerTests: XCTestCase { let syncQueriesReadyPayload = HubPayload(eventName: HubPayload.EventName.DataStore.syncQueriesReady) Amplify.Hub.dispatch(to: .dataStore, payload: syncQueriesReadyPayload) waitForExpectations(timeout: 1.0) - hubHandler.onCancel(withArguments: nil) + let _ = hubHandler.onCancel(withArguments: nil) } func test_hub_networkStatusEvent_success() throws { @@ -166,7 +166,7 @@ class DataStoreHubEventStreamHandlerTests: XCTestCase { let networkStatusPayload = HubPayload(eventName: HubPayload.EventName.DataStore.networkStatus, data: networkStatusEvent) Amplify.Hub.dispatch(to: .dataStore, payload: networkStatusPayload) waitForExpectations(timeout: 1.0) - hubHandler.onCancel(withArguments: nil) + let _ = hubHandler.onCancel(withArguments: nil) } func test_hub_outboxStatusEvent_success() throws { @@ -194,7 +194,7 @@ class DataStoreHubEventStreamHandlerTests: XCTestCase { let outboxStatusPayload = HubPayload(eventName: HubPayload.EventName.DataStore.outboxStatus, data: outboxStatusEvent) Amplify.Hub.dispatch(to: .dataStore, payload: outboxStatusPayload) waitForExpectations(timeout: 1.0) - hubHandler.onCancel(withArguments: nil) + let _ = hubHandler.onCancel(withArguments: nil) } func test_hub_syncQueriesStartedEvent_success() throws { @@ -221,7 +221,7 @@ class DataStoreHubEventStreamHandlerTests: XCTestCase { let syncQueriesStartedPayload = HubPayload(eventName: HubPayload.EventName.DataStore.syncQueriesStarted, data: syncQueriesStartedEvent) Amplify.Hub.dispatch(to: .dataStore, payload: syncQueriesStartedPayload) waitForExpectations(timeout: 1.0) - hubHandler.onCancel(withArguments: nil) + let _ = hubHandler.onCancel(withArguments: nil) } func test_hub_outboxMutationEnqueued_success() throws { @@ -264,7 +264,7 @@ class DataStoreHubEventStreamHandlerTests: XCTestCase { let outboxMutationEnqueuedPayload = HubPayload(eventName: HubPayload.EventName.DataStore.outboxMutationEnqueued, data: outboxMutationEnqueuedEvent) Amplify.Hub.dispatch(to: .dataStore, payload: outboxMutationEnqueuedPayload) waitForExpectations(timeout: 1.0) - hubHandler.onCancel(withArguments: nil) + let _ = hubHandler.onCancel(withArguments: nil) } func test_hub_outboxMutationProcessedEvent_success() throws { @@ -281,7 +281,7 @@ class DataStoreHubEventStreamHandlerTests: XCTestCase { let syncMetaData = element["syncMetadata"] as! [String: Any] XCTAssertEqual(flutterEvent["eventName"] as! String, "outboxMutationProcessed") XCTAssertEqual(flutterEvent["modelName"] as! String, "Post") - XCTAssertEqual(syncMetaData["_lastChangedAt"] as? Int, 123) + XCTAssertEqual(syncMetaData["_lastChangedAt"] as? Int64, 123) XCTAssertEqual(syncMetaData["_version"] as? Int, 1) XCTAssertEqual(syncMetaData["_deleted"] as? Bool, false) XCTAssertEqual(model["__modelName"] as! String, "Post") @@ -300,7 +300,8 @@ class DataStoreHubEventStreamHandlerTests: XCTestCase { let serializedModel = FlutterSerializedModel(map: try FlutterDataStoreRequestUtils.getJSONValue(modelMap), modelName: "Post") - let syncMetadata = MutationSyncMetadata(id: uuid, + let syncMetadata = MutationSyncMetadata(modelId: uuid, + modelName: "MutationSync", deleted: false, lastChangedAt: 123, version: 1) @@ -321,7 +322,7 @@ class DataStoreHubEventStreamHandlerTests: XCTestCase { expect.fulfill() } waitForExpectations(timeout: 1.0) - hubHandler.onCancel(withArguments: nil) + let _ = hubHandler.onCancel(withArguments: nil) } func test_hot_restart_replays_sync_and_ready_events() { diff --git a/packages/amplify_datastore/example/ios/unit_tests/DataStorePluginUnitTests.swift b/packages/amplify_datastore/example/ios/unit_tests/DataStorePluginUnitTests.swift index 30ecbc42cd..beea9ba124 100644 --- a/packages/amplify_datastore/example/ios/unit_tests/DataStorePluginUnitTests.swift +++ b/packages/amplify_datastore/example/ios/unit_tests/DataStorePluginUnitTests.swift @@ -11,7 +11,7 @@ let testSchema: ModelSchema = SchemaData.PostSchema let amplifySuccessResults: [FlutterSerializedModel] = (try! readJsonArray(filePath: "2_results") as! [[String: Any]]).map { serializedModel in FlutterSerializedModel.init( - map: try! getJSONValue(serializedModel as! [String: Any]), + map: try! getJSONValue(serializedModel), modelName: serializedModel["__modelName"] as! String ) } diff --git a/packages/amplify_datastore/example/lib/queries_display_widgets.dart b/packages/amplify_datastore/example/lib/queries_display_widgets.dart index 56aecf7956..1193a266c6 100644 --- a/packages/amplify_datastore/example/lib/queries_display_widgets.dart +++ b/packages/amplify_datastore/example/lib/queries_display_widgets.dart @@ -20,6 +20,7 @@ Widget displayQueryButtons(bool isAmplifyConfigured, String queriesToView, return null; }, style: ButtonStyle( + // ignore: deprecated_member_use backgroundColor: MaterialStateProperty.all( queriesToView == "Blog" ? Colors.white10 : Colors.white60, ), @@ -38,6 +39,7 @@ Widget displayQueryButtons(bool isAmplifyConfigured, String queriesToView, return null; }, style: ButtonStyle( + // ignore: deprecated_member_use backgroundColor: MaterialStateProperty.all( queriesToView == "Post" ? Colors.white10 : Colors.white60, ), @@ -56,6 +58,7 @@ Widget displayQueryButtons(bool isAmplifyConfigured, String queriesToView, return null; }, style: ButtonStyle( + // ignore: deprecated_member_use backgroundColor: MaterialStateProperty.all( queriesToView == "Comment" ? Colors.white10 : Colors.white60, ), @@ -113,7 +116,10 @@ Widget getWidgetToDisplayPost( _postsToView[i].rating.toString() + ", blog: " + allBlogs - .firstWhere((blog) => blog.id == _postsToView[i].blog?.id) + .firstWhere( + (blog) => blog.id == _postsToView[i].blog?.id, + orElse: () => Blog(name: "Blog not found"), + ) .name, style: TextStyle(fontSize: 14.0), ), diff --git a/packages/amplify_datastore/ios/Classes/DataStoreObserveEventStreamHandler.swift b/packages/amplify_datastore/ios/Classes/DataStoreObserveEventStreamHandler.swift index 78bcc66b74..958f5d5394 100644 --- a/packages/amplify_datastore/ios/Classes/DataStoreObserveEventStreamHandler.swift +++ b/packages/amplify_datastore/ios/Classes/DataStoreObserveEventStreamHandler.swift @@ -12,11 +12,15 @@ public class DataStoreObserveEventStreamHandler: NSObject, FlutterStreamHandler } func sendEvent(flutterEvent: [String: Any]) { - eventSink?(flutterEvent) + DispatchQueue.main.async { + self.eventSink?(flutterEvent) + } } func sendError(flutterError: FlutterError) { - eventSink?(flutterError) + DispatchQueue.main.async { + self.eventSink?(flutterError) + } } public func onCancel(withArguments arguments: Any?) -> FlutterError? { diff --git a/packages/amplify_datastore/ios/Classes/FlutterApiPlugin.swift b/packages/amplify_datastore/ios/Classes/FlutterApiPlugin.swift index 5cf8c04753..b7e1510e58 100644 --- a/packages/amplify_datastore/ios/Classes/FlutterApiPlugin.swift +++ b/packages/amplify_datastore/ios/Classes/FlutterApiPlugin.swift @@ -24,14 +24,15 @@ public class FlutterApiPlugin: APICategoryPlugin self.nativeSubscriptionEvents = subscriptionEventBus } - // TODO: Implment in Async Swift v2 public func query(request: GraphQLRequest) async throws -> GraphQLTask.Success where R : Decodable { - preconditionFailure("method not supported") + let response = await asyncQuery(nativeRequest: request.toNativeGraphQLRequest()) + return try decodeGraphQLPayloadJson(request: request, payload: response.payloadJson) } - // TODO: Implment in Async Swift v2 + public func mutate(request: GraphQLRequest) async throws -> GraphQLTask.Success where R : Decodable { - preconditionFailure("method not supported") + let response = await asyncMutate(nativeRequest: request.toNativeGraphQLRequest()) + return try decodeGraphQLPayloadJson(request: request, payload: response.payloadJson) } public func subscribe( @@ -121,6 +122,26 @@ public class FlutterApiPlugin: APICategoryPlugin ) } + func asyncQuery(nativeRequest: NativeGraphQLRequest) async -> NativeGraphQLResponse { + await withCheckedContinuation { continuation in + DispatchQueue.main.async { + self.nativeApiPlugin.query(request: nativeRequest) { response in + continuation.resume(returning: response) + } + } + } + } + + func asyncMutate(nativeRequest: NativeGraphQLRequest) async -> NativeGraphQLResponse{ + await withCheckedContinuation { continuation in + DispatchQueue.main.async { + self.nativeApiPlugin.mutate(request: nativeRequest) { response in + continuation.resume(returning: response) + } + } + } + } + public func configure(using configuration: Any?) throws { } public func apiAuthProviderFactory() -> APIAuthProviderFactory { diff --git a/packages/amplify_datastore/ios/Classes/SwiftAmplifyDataStorePlugin.swift b/packages/amplify_datastore/ios/Classes/SwiftAmplifyDataStorePlugin.swift index 3ef0e01232..5aa7ca0f30 100644 --- a/packages/amplify_datastore/ios/Classes/SwiftAmplifyDataStorePlugin.swift +++ b/packages/amplify_datastore/ios/Classes/SwiftAmplifyDataStorePlugin.swift @@ -299,6 +299,17 @@ public class SwiftAmplifyDataStorePlugin: NSObject, FlutterPlugin, NativeAmplify modelSchemas.forEach { modelSchema in modelSchemaRegistry.addModelSchema(modelName: modelSchema.name, modelSchema: modelSchema) } + + // Amplify Swift DataStore system schemas must be added manually + let systemSchemas = [ + ModelSyncMetadata.schema, + MutationEvent.schema, + MutationSyncMetadata.schema + ] + + systemSchemas.forEach { modelSchema in + modelSchemaRegistry.addModelSchema(modelName: modelSchema.name, modelSchema: modelSchema) + } modelSchemaRegistry.version = modelProviderVersion diff --git a/packages/amplify_datastore/ios/Classes/types/hub/FlutterHubElement.swift b/packages/amplify_datastore/ios/Classes/types/hub/FlutterHubElement.swift index ea61a0d47c..3f23ea36f9 100644 --- a/packages/amplify_datastore/ios/Classes/types/hub/FlutterHubElement.swift +++ b/packages/amplify_datastore/ios/Classes/types/hub/FlutterHubElement.swift @@ -11,7 +11,6 @@ import Combine public struct FlutterHubElement { var model: [String: Any] var version: Int? - // TODO: Migrate to Async Swift v2 -- Is this breaking? var lastChangedAt: Int64? var deleted: Bool diff --git a/packages/amplify_datastore/lib/amplify_datastore.dart b/packages/amplify_datastore/lib/amplify_datastore.dart index e3755f8524..40c2ad1579 100644 --- a/packages/amplify_datastore/lib/amplify_datastore.dart +++ b/packages/amplify_datastore/lib/amplify_datastore.dart @@ -321,18 +321,22 @@ class _NativeAmplifyApi @override Future mutate(NativeGraphQLRequest request) async { - throw UnimplementedError(); + final flutterRequest = nativeRequestToGraphQLRequest(request); + final response = await Amplify.API.mutate(request: flutterRequest).response; + return graphQLResponseToNativeResponse(response); } @override Future query(NativeGraphQLRequest request) async { - throw UnimplementedError(); + final flutterRequest = nativeRequestToGraphQLRequest(request); + final response = await Amplify.API.query(request: flutterRequest).response; + return graphQLResponseToNativeResponse(response); } @override Future subscribe( NativeGraphQLRequest request) async { - final flutterRequest = NativeRequestToGraphQLRequest(request); + final flutterRequest = nativeRequestToGraphQLRequest(request); final operation = Amplify.API.subscribe(flutterRequest, onEstablished: () => sendNativeStartAckEvent(flutterRequest.id)); diff --git a/packages/amplify_datastore/lib/src/utils/native_api_helpers.dart b/packages/amplify_datastore/lib/src/utils/native_api_helpers.dart index a36781deca..f283f4a435 100644 --- a/packages/amplify_datastore/lib/src/utils/native_api_helpers.dart +++ b/packages/amplify_datastore/lib/src/utils/native_api_helpers.dart @@ -2,6 +2,7 @@ import 'dart:convert'; import 'package:amplify_core/amplify_core.dart'; import 'package:amplify_datastore/src/native_plugin.g.dart'; +import 'package:collection/collection.dart'; /// Convert a [NativeGraphQLResponse] to a [GraphQLResponse] GraphQLRequest nativeRequestToGraphQLRequest( @@ -13,6 +14,17 @@ GraphQLRequest nativeRequestToGraphQLRequest( ); } +/// Convert a [GraphQLResponse] to a [NativeGraphQLResponse] +NativeGraphQLResponse graphQLResponseToNativeResponse( + GraphQLResponse response) { + final errorJson = jsonEncode( + response.errors.whereNotNull().map((e) => e.toJson()).toList()); + return NativeGraphQLResponse( + payloadJson: response.data, + errorsJson: errorJson, + ); +} + /// Returns a connecting event [NativeGraphQLResponse] for the given [subscriptionId] NativeGraphQLSubscriptionResponse getConnectingEvent(String subscriptionId) { final event = NativeGraphQLSubscriptionResponse( diff --git a/packages/amplify_native_legacy_wrapper/ios/Classes/AmplifyNativeLegacyWrapperPlugin.swift b/packages/amplify_native_legacy_wrapper/ios/Classes/AmplifyNativeLegacyWrapperPlugin.swift index 0fdc010db0..95ed0df383 100644 --- a/packages/amplify_native_legacy_wrapper/ios/Classes/AmplifyNativeLegacyWrapperPlugin.swift +++ b/packages/amplify_native_legacy_wrapper/ios/Classes/AmplifyNativeLegacyWrapperPlugin.swift @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import Flutter -import AWSDataStorePlugin +import AmplifyPlugins import AWSPluginsCore import Amplify import UIKit