From b9e582eb29f327e5609518c39743ad6b63177861 Mon Sep 17 00:00:00 2001 From: Elijah Quartey Date: Thu, 23 Jun 2022 11:39:46 -0500 Subject: [PATCH] chore(api): API Native Bridge for .addPlugin() (#1756) --- packages/api/amplify_api/Makefile | 4 + packages/api/amplify_api/example/pubspec.yaml | 3 +- packages/api/amplify_api/lib/amplify_api.dart | 21 ++-- .../amplify_api/lib/src/api_plugin_impl.dart | 81 ++++++++++++++ .../lib/src/native_api_plugin.dart | 63 +++++++++++ .../pigeons/native_api_plugin.dart | 43 ++++++++ packages/api/amplify_api/pubspec.yaml | 10 +- .../amplify/amplify_api/AmplifyApi.kt | 52 +++++---- .../amplify_api/NativeApiPluginBindings.java | 87 +++++++++++++++ packages/api/amplify_api_android/pubspec.yaml | 3 +- .../ios/Classes/NativeApiPlugin.h | 35 ++++++ .../ios/Classes/NativeApiPlugin.m | 102 ++++++++++++++++++ .../ios/Classes/SwiftAmplifyApiPlugin.swift | 43 ++++---- .../ios/Classes/amplify_api_ios.h | 21 ++++ .../ios/amplify_api_ios.podspec | 14 +++ .../api/amplify_api_ios/ios/module.modulemap | 6 ++ packages/api/amplify_api_ios/pubspec.yaml | 3 +- 17 files changed, 526 insertions(+), 65 deletions(-) create mode 100644 packages/api/amplify_api/Makefile create mode 100644 packages/api/amplify_api/lib/src/api_plugin_impl.dart create mode 100644 packages/api/amplify_api/lib/src/native_api_plugin.dart create mode 100644 packages/api/amplify_api/pigeons/native_api_plugin.dart create mode 100644 packages/api/amplify_api_android/android/src/main/kotlin/com/amazonaws/amplify/amplify_api/NativeApiPluginBindings.java create mode 100644 packages/api/amplify_api_ios/ios/Classes/NativeApiPlugin.h create mode 100644 packages/api/amplify_api_ios/ios/Classes/NativeApiPlugin.m create mode 100644 packages/api/amplify_api_ios/ios/Classes/amplify_api_ios.h create mode 100644 packages/api/amplify_api_ios/ios/module.modulemap diff --git a/packages/api/amplify_api/Makefile b/packages/api/amplify_api/Makefile new file mode 100644 index 0000000000..f1c3ac38ba --- /dev/null +++ b/packages/api/amplify_api/Makefile @@ -0,0 +1,4 @@ +.PHONY: pigeons +pigeons: + flutter pub run pigeon --input pigeons/native_api_plugin.dart + flutter format --fix lib/src/native_api_plugin.dart diff --git a/packages/api/amplify_api/example/pubspec.yaml b/packages/api/amplify_api/example/pubspec.yaml index 8b2d58e92d..3303c4f462 100644 --- a/packages/api/amplify_api/example/pubspec.yaml +++ b/packages/api/amplify_api/example/pubspec.yaml @@ -32,7 +32,8 @@ dependencies: sdk: flutter dev_dependencies: - amplify_lints: ^2.0.0 + amplify_lints: + path: ../../../amplify_lints amplify_test: path: ../../../amplify_test flutter_driver: diff --git a/packages/api/amplify_api/lib/amplify_api.dart b/packages/api/amplify_api/lib/amplify_api.dart index f0ca3c2c4f..a4db7b1e97 100644 --- a/packages/api/amplify_api/lib/amplify_api.dart +++ b/packages/api/amplify_api/lib/amplify_api.dart @@ -15,9 +15,7 @@ library amplify_api_plugin; -import 'dart:io'; - -import 'package:amplify_api/src/method_channel_api.dart'; +import 'package:amplify_api/src/api_plugin_impl.dart'; import 'package:amplify_core/amplify_core.dart'; import 'package:meta/meta.dart'; @@ -32,18 +30,11 @@ export './model_subscriptions.dart'; /// {@endtemplate} abstract class AmplifyAPI extends APIPluginInterface { /// {@macro amplify_api.amplify_api} - factory AmplifyAPI({ - List authProviders = const [], - ModelProviderInterface? modelProvider, - }) { - if (zIsWeb || Platform.isWindows || Platform.isMacOS || Platform.isLinux) { - throw UnsupportedError('This platform is not supported yet'); - } - return AmplifyAPIMethodChannel( - authProviders: authProviders, - modelProvider: modelProvider, - ); - } + factory AmplifyAPI( + {List authProviders = const [], + ModelProviderInterface? modelProvider}) => + AmplifyAPIDart( + authProviders: authProviders, modelProvider: modelProvider); /// Protected constructor for subclasses. @protected diff --git a/packages/api/amplify_api/lib/src/api_plugin_impl.dart b/packages/api/amplify_api/lib/src/api_plugin_impl.dart new file mode 100644 index 0000000000..5ac4fc36ff --- /dev/null +++ b/packages/api/amplify_api/lib/src/api_plugin_impl.dart @@ -0,0 +1,81 @@ +// Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +library amplify_api; + +import 'dart:io'; + +import 'package:amplify_api/amplify_api.dart'; +import 'package:amplify_api/src/native_api_plugin.dart'; +import 'package:amplify_core/amplify_core.dart'; +import 'package:flutter/services.dart'; + +/// {@template amplify_api.amplify_api_dart} +/// The AWS implementation of the Amplify API category. +/// {@endtemplate} +class AmplifyAPIDart extends AmplifyAPI { + late final AWSApiPluginConfig _apiConfig; + + /// The registered [APIAuthProvider] instances. + final Map _authProviders = {}; + + /// {@macro amplify_api.amplify_api_dart} + AmplifyAPIDart({ + List authProviders = const [], + this.modelProvider, + }) : super.protected() { + authProviders.forEach(registerAuthProvider); + } + + @override + Future configure({AmplifyConfig? config}) async { + final apiConfig = config?.api?.awsPlugin; + if (apiConfig == null) { + throw const ApiException('No AWS API config found', + recoverySuggestion: 'Add API from the Amplify CLI. See ' + 'https://docs.amplify.aws/lib/graphqlapi/getting-started/q/platform/flutter/#configure-api'); + } + _apiConfig = apiConfig; + } + + @override + Future addPlugin() async { + if (zIsWeb || !(Platform.isAndroid || Platform.isIOS)) { + return; + } + + final nativeBridge = NativeApiBridge(); + try { + final authProvidersList = + _authProviders.keys.map((key) => key.rawValue).toList(); + await nativeBridge.addPlugin(authProvidersList); + } on PlatformException catch (e) { + if (e.code == 'AmplifyAlreadyConfiguredException') { + throw const AmplifyAlreadyConfiguredException( + AmplifyExceptionMessages.alreadyConfiguredDefaultMessage, + recoverySuggestion: + AmplifyExceptionMessages.alreadyConfiguredDefaultSuggestion); + } + throw AmplifyException.fromMap((e.details as Map).cast()); + } + } + + @override + final ModelProviderInterface? modelProvider; + + @override + void registerAuthProvider(APIAuthProvider authProvider) { + _authProviders[authProvider.type] = authProvider; + } +} diff --git a/packages/api/amplify_api/lib/src/native_api_plugin.dart b/packages/api/amplify_api/lib/src/native_api_plugin.dart new file mode 100644 index 0000000000..e7c5af4d04 --- /dev/null +++ b/packages/api/amplify_api/lib/src/native_api_plugin.dart @@ -0,0 +1,63 @@ +// +// Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). +// You may not use this file except in compliance with the License. +// A copy of the License is located at +// +// http://aws.amazon.com/apache2.0 +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. +// +// Autogenerated from Pigeon (v3.1.5), do not edit directly. +// See also: https://pub.dev/packages/pigeon +// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name +// @dart = 2.12 +import 'dart:async'; +import 'dart:typed_data' show Uint8List, Int32List, Int64List, Float64List; + +import 'package:flutter/foundation.dart' show WriteBuffer, ReadBuffer; +import 'package:flutter/services.dart'; + +class _NativeApiBridgeCodec extends StandardMessageCodec { + const _NativeApiBridgeCodec(); +} + +class NativeApiBridge { + /// Constructor for [NativeApiBridge]. The [binaryMessenger] named argument is + /// available for dependency injection. If it is left null, the default + /// BinaryMessenger will be used which routes to the host platform. + NativeApiBridge({BinaryMessenger? binaryMessenger}) + : _binaryMessenger = binaryMessenger; + + final BinaryMessenger? _binaryMessenger; + + static const MessageCodec codec = _NativeApiBridgeCodec(); + + Future addPlugin(List arg_authProvidersList) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.NativeApiBridge.addPlugin', codec, + binaryMessenger: _binaryMessenger); + final Map? replyMap = await channel + .send([arg_authProvidersList]) as Map?; + if (replyMap == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyMap['error'] != null) { + final Map error = + (replyMap['error'] as Map?)!; + throw PlatformException( + code: (error['code'] as String?)!, + message: error['message'] as String?, + details: error['details'], + ); + } else { + return; + } + } +} diff --git a/packages/api/amplify_api/pigeons/native_api_plugin.dart b/packages/api/amplify_api/pigeons/native_api_plugin.dart new file mode 100644 index 0000000000..a36f7397f9 --- /dev/null +++ b/packages/api/amplify_api/pigeons/native_api_plugin.dart @@ -0,0 +1,43 @@ +// +// Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). +// You may not use this file except in compliance with the License. +// A copy of the License is located at +// +// http://aws.amazon.com/apache2.0 +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. +// + +// ignore_for_file: avoid_positional_boolean_parameters + +@ConfigurePigeon( + PigeonOptions( + copyrightHeader: '../../../tool/license.txt', + dartOptions: DartOptions(), + dartOut: 'lib/src/native_Api_plugin.dart', + javaOptions: JavaOptions( + className: 'NativeApiPluginBindings', + package: 'com.amazonaws.amplify.amplify_api', + ), + javaOut: + '../amplify_api_android/android/src/main/kotlin/com/amazonaws/amplify/amplify_api/NativeApiPluginBindings.java', + objcOptions: ObjcOptions( + header: 'NativeApiPlugin.h', + ), + objcHeaderOut: '../amplify_api_ios/ios/Classes/NativeApiPlugin.h', + objcSourceOut: '../amplify_api_ios/ios/Classes/NativeApiPlugin.m', + ), +) +library native_api_plugin; + +import 'package:pigeon/pigeon.dart'; + +@HostApi() +abstract class NativeApiBridge { + void addPlugin(List authProvidersList); +} diff --git a/packages/api/amplify_api/pubspec.yaml b/packages/api/amplify_api/pubspec.yaml index 0df1623be7..1f4879a466 100644 --- a/packages/api/amplify_api/pubspec.yaml +++ b/packages/api/amplify_api/pubspec.yaml @@ -23,14 +23,22 @@ dependencies: meta: ^1.7.0 plugin_platform_interface: ^2.0.0 +dependency_overrides: + # TODO(dnys1): Remove when pigeon is bumped + # https://github.com/flutter/flutter/issues/105090 + analyzer: ^3.0.0 + + dev_dependencies: - amplify_lints: ^2.0.0 + amplify_lints: + path: ../../amplify_lints amplify_test: path: ../../amplify_test build_runner: ^2.0.0 flutter_test: sdk: flutter mocktail: ^0.3.0 + pigeon: ^3.1.5 # The following section is specific to Flutter. flutter: diff --git a/packages/api/amplify_api_android/android/src/main/kotlin/com/amazonaws/amplify/amplify_api/AmplifyApi.kt b/packages/api/amplify_api_android/android/src/main/kotlin/com/amazonaws/amplify/amplify_api/AmplifyApi.kt index 02de711722..0205877bf7 100644 --- a/packages/api/amplify_api_android/android/src/main/kotlin/com/amazonaws/amplify/amplify_api/AmplifyApi.kt +++ b/packages/api/amplify_api_android/android/src/main/kotlin/com/amazonaws/amplify/amplify_api/AmplifyApi.kt @@ -39,7 +39,7 @@ import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers /** AmplifyApiPlugin */ -class AmplifyApi : FlutterPlugin, MethodCallHandler { +class AmplifyApi : FlutterPlugin, MethodCallHandler, NativeApiPluginBindings.NativeApiBridge { private companion object { /** @@ -83,6 +83,11 @@ class AmplifyApi : FlutterPlugin, MethodCallHandler { "com.amazonaws.amplify/api_observe_events" ) eventchannel!!.setStreamHandler(graphqlSubscriptionStreamHandler) + + NativeApiPluginBindings.NativeApiBridge.setup( + flutterPluginBinding.binaryMessenger, + this + ) } @Suppress("UNCHECKED_CAST") @@ -94,27 +99,6 @@ class AmplifyApi : FlutterPlugin, MethodCallHandler { if (methodName == "cancel") { onCancel(result, (call.arguments as String)) return - } else if (methodName == "addPlugin") { - try { - val authProvidersList: List = - (arguments["authProviders"] as List<*>?)?.cast() ?: listOf() - val authProviders = authProvidersList.map { AuthorizationType.valueOf(it) } - if (flutterAuthProviders == null) { - flutterAuthProviders = FlutterAuthProviders(authProviders) - } - flutterAuthProviders!!.setMethodChannel(channel) - Amplify.addPlugin( - AWSApiPlugin - .builder() - .apiAuthProviders(flutterAuthProviders!!.factory) - .build() - ) - logger.info("Added API plugin") - result.success(null) - } catch (e: Exception) { - handleAddPluginException("API", e, result) - } - return } try { @@ -168,5 +152,29 @@ class AmplifyApi : FlutterPlugin, MethodCallHandler { eventchannel = null graphqlSubscriptionStreamHandler?.close() graphqlSubscriptionStreamHandler = null + + NativeApiPluginBindings.NativeApiBridge.setup( + flutterPluginBinding.binaryMessenger, + null, + ) + } + + override fun addPlugin(authProvidersList: MutableList) { + try { + val authProviders = authProvidersList.map { AuthorizationType.valueOf(it) } + if (flutterAuthProviders == null) { + flutterAuthProviders = FlutterAuthProviders(authProviders) + } + flutterAuthProviders!!.setMethodChannel(channel) + Amplify.addPlugin( + AWSApiPlugin + .builder() + .apiAuthProviders(flutterAuthProviders!!.factory) + .build() + ) + logger.info("Added API plugin") + } catch (e: Exception) { + logger.error(e.message) + } } } diff --git a/packages/api/amplify_api_android/android/src/main/kotlin/com/amazonaws/amplify/amplify_api/NativeApiPluginBindings.java b/packages/api/amplify_api_android/android/src/main/kotlin/com/amazonaws/amplify/amplify_api/NativeApiPluginBindings.java new file mode 100644 index 0000000000..d8d07f4add --- /dev/null +++ b/packages/api/amplify_api_android/android/src/main/kotlin/com/amazonaws/amplify/amplify_api/NativeApiPluginBindings.java @@ -0,0 +1,87 @@ +// +// Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). +// You may not use this file except in compliance with the License. +// A copy of the License is located at +// +// http://aws.amazon.com/apache2.0 +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. +// +// Autogenerated from Pigeon (v3.1.5), do not edit directly. +// See also: https://pub.dev/packages/pigeon + +package com.amazonaws.amplify.amplify_api; + +import android.util.Log; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import io.flutter.plugin.common.BasicMessageChannel; +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugin.common.MessageCodec; +import io.flutter.plugin.common.StandardMessageCodec; +import java.io.ByteArrayOutputStream; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.HashMap; + +/** Generated class from Pigeon. */ +@SuppressWarnings({"unused", "unchecked", "CodeBlock2Expr", "RedundantSuppression"}) +public class NativeApiPluginBindings { + private static class NativeApiBridgeCodec extends StandardMessageCodec { + public static final NativeApiBridgeCodec INSTANCE = new NativeApiBridgeCodec(); + private NativeApiBridgeCodec() {} + } + + /** Generated interface from Pigeon that represents a handler of messages from Flutter.*/ + public interface NativeApiBridge { + void addPlugin(@NonNull List authProvidersList); + + /** The codec used by NativeApiBridge. */ + static MessageCodec getCodec() { + return NativeApiBridgeCodec.INSTANCE; + } + + /** Sets up an instance of `NativeApiBridge` to handle messages through the `binaryMessenger`. */ + static void setup(BinaryMessenger binaryMessenger, NativeApiBridge api) { + { + BasicMessageChannel channel = + new BasicMessageChannel<>(binaryMessenger, "dev.flutter.pigeon.NativeApiBridge.addPlugin", getCodec()); + if (api != null) { + channel.setMessageHandler((message, reply) -> { + Map wrapped = new HashMap<>(); + try { + ArrayList args = (ArrayList)message; + List authProvidersListArg = (List)args.get(0); + if (authProvidersListArg == null) { + throw new NullPointerException("authProvidersListArg unexpectedly null."); + } + api.addPlugin(authProvidersListArg); + wrapped.put("result", null); + } + catch (Error | RuntimeException exception) { + wrapped.put("error", wrapError(exception)); + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + } + } + private static Map wrapError(Throwable exception) { + Map errorMap = new HashMap<>(); + errorMap.put("message", exception.toString()); + errorMap.put("code", exception.getClass().getSimpleName()); + errorMap.put("details", "Cause: " + exception.getCause() + ", Stacktrace: " + Log.getStackTraceString(exception)); + return errorMap; + } +} diff --git a/packages/api/amplify_api_android/pubspec.yaml b/packages/api/amplify_api_android/pubspec.yaml index 1047613c82..72272c62a5 100644 --- a/packages/api/amplify_api_android/pubspec.yaml +++ b/packages/api/amplify_api_android/pubspec.yaml @@ -14,7 +14,8 @@ dependencies: sdk: flutter dev_dependencies: - amplify_lints: ^2.0.0 + amplify_lints: + path: ../../amplify_lints flutter_test: sdk: flutter diff --git a/packages/api/amplify_api_ios/ios/Classes/NativeApiPlugin.h b/packages/api/amplify_api_ios/ios/Classes/NativeApiPlugin.h new file mode 100644 index 0000000000..7b3bad24ed --- /dev/null +++ b/packages/api/amplify_api_ios/ios/Classes/NativeApiPlugin.h @@ -0,0 +1,35 @@ +// +// Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). +// You may not use this file except in compliance with the License. +// A copy of the License is located at +// +// http://aws.amazon.com/apache2.0 +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. +// +// Autogenerated from Pigeon (v3.1.5), do not edit directly. +// See also: https://pub.dev/packages/pigeon +#import +@protocol FlutterBinaryMessenger; +@protocol FlutterMessageCodec; +@class FlutterError; +@class FlutterStandardTypedData; + +NS_ASSUME_NONNULL_BEGIN + + +/// The codec used by NativeApiBridge. +NSObject *NativeApiBridgeGetCodec(void); + +@protocol NativeApiBridge +- (void)addPluginAuthProvidersList:(NSArray *)authProvidersList error:(FlutterError *_Nullable *_Nonnull)error; +@end + +extern void NativeApiBridgeSetup(id binaryMessenger, NSObject *_Nullable api); + +NS_ASSUME_NONNULL_END diff --git a/packages/api/amplify_api_ios/ios/Classes/NativeApiPlugin.m b/packages/api/amplify_api_ios/ios/Classes/NativeApiPlugin.m new file mode 100644 index 0000000000..c936591be5 --- /dev/null +++ b/packages/api/amplify_api_ios/ios/Classes/NativeApiPlugin.m @@ -0,0 +1,102 @@ +// +// Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). +// You may not use this file except in compliance with the License. +// A copy of the License is located at +// +// http://aws.amazon.com/apache2.0 +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. +// +// Autogenerated from Pigeon (v3.1.5), do not edit directly. +// See also: https://pub.dev/packages/pigeon +#import "NativeApiPlugin.h" +#import + +#if !__has_feature(objc_arc) +#error File requires ARC to be enabled. +#endif + +static NSDictionary *wrapResult(id result, FlutterError *error) { + NSDictionary *errorDict = (NSDictionary *)[NSNull null]; + if (error) { + errorDict = @{ + @"code": (error.code ?: [NSNull null]), + @"message": (error.message ?: [NSNull null]), + @"details": (error.details ?: [NSNull null]), + }; + } + return @{ + @"result": (result ?: [NSNull null]), + @"error": errorDict, + }; +} +static id GetNullableObject(NSDictionary* dict, id key) { + id result = dict[key]; + return (result == [NSNull null]) ? nil : result; +} +static id GetNullableObjectAtIndex(NSArray* array, NSInteger key) { + id result = array[key]; + return (result == [NSNull null]) ? nil : result; +} + + + +@interface NativeApiBridgeCodecReader : FlutterStandardReader +@end +@implementation NativeApiBridgeCodecReader +@end + +@interface NativeApiBridgeCodecWriter : FlutterStandardWriter +@end +@implementation NativeApiBridgeCodecWriter +@end + +@interface NativeApiBridgeCodecReaderWriter : FlutterStandardReaderWriter +@end +@implementation NativeApiBridgeCodecReaderWriter +- (FlutterStandardWriter *)writerWithData:(NSMutableData *)data { + return [[NativeApiBridgeCodecWriter alloc] initWithData:data]; +} +- (FlutterStandardReader *)readerWithData:(NSData *)data { + return [[NativeApiBridgeCodecReader alloc] initWithData:data]; +} +@end + +NSObject *NativeApiBridgeGetCodec() { + static dispatch_once_t sPred = 0; + static FlutterStandardMessageCodec *sSharedObject = nil; + dispatch_once(&sPred, ^{ + NativeApiBridgeCodecReaderWriter *readerWriter = [[NativeApiBridgeCodecReaderWriter alloc] init]; + sSharedObject = [FlutterStandardMessageCodec codecWithReaderWriter:readerWriter]; + }); + return sSharedObject; +} + + +void NativeApiBridgeSetup(id binaryMessenger, NSObject *api) { + { + FlutterBasicMessageChannel *channel = + [[FlutterBasicMessageChannel alloc] + initWithName:@"dev.flutter.pigeon.NativeApiBridge.addPlugin" + binaryMessenger:binaryMessenger + codec:NativeApiBridgeGetCodec() ]; + if (api) { + NSCAssert([api respondsToSelector:@selector(addPluginAuthProvidersList:error:)], @"NativeApiBridge api (%@) doesn't respond to @selector(addPluginAuthProvidersList:error:)", api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + NSArray *args = message; + NSArray *arg_authProvidersList = GetNullableObjectAtIndex(args, 0); + FlutterError *error; + [api addPluginAuthProvidersList:arg_authProvidersList error:&error]; + callback(wrapResult(nil, error)); + }]; + } + else { + [channel setMessageHandler:nil]; + } + } +} diff --git a/packages/api/amplify_api_ios/ios/Classes/SwiftAmplifyApiPlugin.swift b/packages/api/amplify_api_ios/ios/Classes/SwiftAmplifyApiPlugin.swift index 7ad1accd1a..63ce5c373c 100644 --- a/packages/api/amplify_api_ios/ios/Classes/SwiftAmplifyApiPlugin.swift +++ b/packages/api/amplify_api_ios/ios/Classes/SwiftAmplifyApiPlugin.swift @@ -20,7 +20,7 @@ import AmplifyPlugins import amplify_flutter_ios import AWSPluginsCore -public class SwiftAmplifyApiPlugin: NSObject, FlutterPlugin { +public class SwiftAmplifyApiPlugin: NSObject, FlutterPlugin, NativeApiBridge { private let bridge: ApiBridge private let graphQLSubscriptionsStreamHandler: GraphQLSubscriptionsStreamHandler static var methodChannel: FlutterMethodChannel! @@ -43,6 +43,7 @@ public class SwiftAmplifyApiPlugin: NSObject, FlutterPlugin { let instance = SwiftAmplifyApiPlugin() eventchannel.setStreamHandler(instance.graphQLSubscriptionsStreamHandler) registrar.addMethodCallDelegate(instance, channel: methodChannel) + NativeApiBridgeSetup(registrar.messenger(), instance) } public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { @@ -62,33 +63,26 @@ public class SwiftAmplifyApiPlugin: NSObject, FlutterPlugin { let arguments = try FlutterApiRequest.getMap(args: callArgs) - if method == "addPlugin"{ - let authProvidersList = arguments["authProviders"] as? [String] ?? [] - let authProviders = authProvidersList.compactMap { - AWSAuthorizationType(rawValue: $0) - } - addPlugin(authProviders: authProviders, result: result) - return - } - try innerHandle(method: method, arguments: arguments, result: result) } catch { print("Failed to parse query arguments with \(error)") FlutterApiErrorHandler.handleApiError(error: APIError(error: error), flutterResult: result) } } - - private func addPlugin(authProviders: [AWSAuthorizationType], result: FlutterResult) { + + public func addPluginAuthProvidersList(_ authProvidersList: [String], error: AutoreleasingUnsafeMutablePointer) { do { + let authProviders = authProvidersList.compactMap { + AWSAuthorizationType(rawValue: $0) + } try Amplify.add( plugin: AWSAPIPlugin( sessionFactory: FlutterURLSessionBehaviorFactory(), apiAuthProviderFactory: FlutterAuthProviders(authProviders))) - result(true) } catch let apiError as APIError { - ErrorUtil.postErrorToFlutterChannel( - result: result, - errorCode: "APIException", + error.pointee = FlutterError( + code: "APIException", + message: apiError.localizedDescription, details: [ "message": apiError.errorDescription, "recoverySuggestion": apiError.recoverySuggestion, @@ -100,20 +94,21 @@ public class SwiftAmplifyApiPlugin: NSObject, FlutterPlugin { if case .amplifyAlreadyConfigured = configError { errorCode = "AmplifyAlreadyConfiguredException" } - ErrorUtil.postErrorToFlutterChannel( - result: result, - errorCode: errorCode, + error.pointee = FlutterError( + code: errorCode, + message: configError.localizedDescription, details: [ "message": configError.errorDescription, "recoverySuggestion": configError.recoverySuggestion, "underlyingError": configError.underlyingError?.localizedDescription ?? "" ] ) - } catch { - ErrorUtil.postErrorToFlutterChannel( - result: result, - errorCode: "UNKNOWN", - details: ["message": error.localizedDescription]) + } catch let e { + error.pointee = FlutterError( + code: "UNKNOWN", + message: e.localizedDescription, + details: nil + ) } } diff --git a/packages/api/amplify_api_ios/ios/Classes/amplify_api_ios.h b/packages/api/amplify_api_ios/ios/Classes/amplify_api_ios.h new file mode 100644 index 0000000000..0b890efd4f --- /dev/null +++ b/packages/api/amplify_api_ios/ios/Classes/amplify_api_ios.h @@ -0,0 +1,21 @@ +// Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef amplify_api_ios_h +#define amplify_api_ios_h + +#import "NativeApiPlugin.h" +#import "AmplifyApi.h" + +#endif /* amplify_api_ios_h */ diff --git a/packages/api/amplify_api_ios/ios/amplify_api_ios.podspec b/packages/api/amplify_api_ios/ios/amplify_api_ios.podspec index 181063b97c..276c97012b 100644 --- a/packages/api/amplify_api_ios/ios/amplify_api_ios.podspec +++ b/packages/api/amplify_api_ios/ios/amplify_api_ios.podspec @@ -21,6 +21,20 @@ The API module for Amplify Flutter. s.platform = :ios, '11.0' s.swift_version = '5.0' + # Use a custom module map with a manually written umbrella header. + # + # Since we use `package:pigeon` to generate our platform interface + # in ObjC, and since the rest of the module is written in Swift, we + # fall victim to this issue: https://github.com/CocoaPods/CocoaPods/issues/10544 + # + # This is because we have an ObjC -> Swift -> ObjC import cycle: + # ApiPlugin -> SwiftAmplifyApiPlugin -> NativeApiPlugin + # + # The easiest solution to this problem is to create the umbrella + # header which would otherwise be auto-generated by Cocoapods but + # name it what's expected by the Swift compiler (amplify_api_ios.h). + s.module_map = 'module.modulemap' + # Flutter.framework does not contain a i386 slice. s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } end diff --git a/packages/api/amplify_api_ios/ios/module.modulemap b/packages/api/amplify_api_ios/ios/module.modulemap new file mode 100644 index 0000000000..acac87c311 --- /dev/null +++ b/packages/api/amplify_api_ios/ios/module.modulemap @@ -0,0 +1,6 @@ +framework module amplify_api_ios { + umbrella header "amplify_api_ios.h" + + export * + module * { export * } +} diff --git a/packages/api/amplify_api_ios/pubspec.yaml b/packages/api/amplify_api_ios/pubspec.yaml index 6b640af7b0..ff8ccbf5ca 100644 --- a/packages/api/amplify_api_ios/pubspec.yaml +++ b/packages/api/amplify_api_ios/pubspec.yaml @@ -15,7 +15,8 @@ dependencies: sdk: flutter dev_dependencies: - amplify_lints: ^2.0.0 + amplify_lints: + path: ../../amplify_lints flutter_test: sdk: flutter