[google_sign_in] Switch to internal method channels (#5396)
diff --git a/packages/google_sign_in/google_sign_in_android/CHANGELOG.md b/packages/google_sign_in/google_sign_in_android/CHANGELOG.md
index e7c6847..3ffa6b5 100644
--- a/packages/google_sign_in/google_sign_in_android/CHANGELOG.md
+++ b/packages/google_sign_in/google_sign_in_android/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 5.2.6
+
+* Switches to an internal method channel, rather than the default.
+
## 5.2.5
* Splits from `video_player` as a federated implementation.
diff --git a/packages/google_sign_in/google_sign_in_android/android/src/main/java/io/flutter/plugins/googlesignin/GoogleSignInPlugin.java b/packages/google_sign_in/google_sign_in_android/android/src/main/java/io/flutter/plugins/googlesignin/GoogleSignInPlugin.java
index 1be023c..a1237f0 100644
--- a/packages/google_sign_in/google_sign_in_android/android/src/main/java/io/flutter/plugins/googlesignin/GoogleSignInPlugin.java
+++ b/packages/google_sign_in/google_sign_in_android/android/src/main/java/io/flutter/plugins/googlesignin/GoogleSignInPlugin.java
@@ -44,7 +44,7 @@
/** Google sign-in plugin for Flutter. */
public class GoogleSignInPlugin implements MethodCallHandler, FlutterPlugin, ActivityAware {
- private static final String CHANNEL_NAME = "plugins.flutter.io/google_sign_in";
+ private static final String CHANNEL_NAME = "plugins.flutter.io/google_sign_in_android";
private static final String METHOD_INIT = "init";
private static final String METHOD_SIGN_IN_SILENTLY = "signInSilently";
diff --git a/packages/google_sign_in/google_sign_in_android/example/integration_test/google_sign_in_test.dart b/packages/google_sign_in/google_sign_in_android/example/integration_test/google_sign_in_test.dart
index d4631f6..f1388ce 100644
--- a/packages/google_sign_in/google_sign_in_android/example/integration_test/google_sign_in_test.dart
+++ b/packages/google_sign_in/google_sign_in_android/example/integration_test/google_sign_in_test.dart
@@ -13,4 +13,12 @@
final GoogleSignInPlatform signIn = GoogleSignInPlatform.instance;
expect(signIn, isNotNull);
});
+
+ testWidgets('Method channel handler is present', (WidgetTester tester) async {
+ // isSignedIn can be called without initialization, so use it to validate
+ // that the native method handler is present (e.g., that the channel name
+ // is correct).
+ final GoogleSignInPlatform signIn = GoogleSignInPlatform.instance;
+ await expectLater(signIn.isSignedIn(), completes);
+ });
}
diff --git a/packages/google_sign_in/google_sign_in_android/lib/google_sign_in_android.dart b/packages/google_sign_in/google_sign_in_android/lib/google_sign_in_android.dart
new file mode 100644
index 0000000..d96328d
--- /dev/null
+++ b/packages/google_sign_in/google_sign_in_android/lib/google_sign_in_android.dart
@@ -0,0 +1,95 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:async';
+
+import 'package:flutter/foundation.dart' show visibleForTesting;
+import 'package:flutter/services.dart';
+import 'package:google_sign_in_platform_interface/google_sign_in_platform_interface.dart';
+
+import 'src/utils.dart';
+
+/// Android implementation of [GoogleSignInPlatform].
+class GoogleSignInAndroid extends GoogleSignInPlatform {
+ /// This is only exposed for test purposes. It shouldn't be used by clients of
+ /// the plugin as it may break or change at any time.
+ @visibleForTesting
+ MethodChannel channel =
+ const MethodChannel('plugins.flutter.io/google_sign_in_android');
+
+ /// Registers this class as the default instance of [GoogleSignInPlatform].
+ static void registerWith() {
+ GoogleSignInPlatform.instance = GoogleSignInAndroid();
+ }
+
+ @override
+ Future<void> init({
+ List<String> scopes = const <String>[],
+ SignInOption signInOption = SignInOption.standard,
+ String? hostedDomain,
+ String? clientId,
+ }) {
+ return channel.invokeMethod<void>('init', <String, dynamic>{
+ 'signInOption': signInOption.toString(),
+ 'scopes': scopes,
+ 'hostedDomain': hostedDomain,
+ 'clientId': clientId,
+ });
+ }
+
+ @override
+ Future<GoogleSignInUserData?> signInSilently() {
+ return channel
+ .invokeMapMethod<String, dynamic>('signInSilently')
+ .then(getUserDataFromMap);
+ }
+
+ @override
+ Future<GoogleSignInUserData?> signIn() {
+ return channel
+ .invokeMapMethod<String, dynamic>('signIn')
+ .then(getUserDataFromMap);
+ }
+
+ @override
+ Future<GoogleSignInTokenData> getTokens(
+ {required String email, bool? shouldRecoverAuth = true}) {
+ return channel
+ .invokeMapMethod<String, dynamic>('getTokens', <String, dynamic>{
+ 'email': email,
+ 'shouldRecoverAuth': shouldRecoverAuth,
+ }).then((Map<String, dynamic>? result) => getTokenDataFromMap(result!));
+ }
+
+ @override
+ Future<void> signOut() {
+ return channel.invokeMapMethod<String, dynamic>('signOut');
+ }
+
+ @override
+ Future<void> disconnect() {
+ return channel.invokeMapMethod<String, dynamic>('disconnect');
+ }
+
+ @override
+ Future<bool> isSignedIn() async {
+ return (await channel.invokeMethod<bool>('isSignedIn'))!;
+ }
+
+ @override
+ Future<void> clearAuthCache({String? token}) {
+ return channel.invokeMethod<void>(
+ 'clearAuthCache',
+ <String, String?>{'token': token},
+ );
+ }
+
+ @override
+ Future<bool> requestScopes(List<String> scopes) async {
+ return (await channel.invokeMethod<bool>(
+ 'requestScopes',
+ <String, List<String>>{'scopes': scopes},
+ ))!;
+ }
+}
diff --git a/packages/google_sign_in/google_sign_in_android/lib/src/utils.dart b/packages/google_sign_in/google_sign_in_android/lib/src/utils.dart
new file mode 100644
index 0000000..5cd7c20
--- /dev/null
+++ b/packages/google_sign_in/google_sign_in_android/lib/src/utils.dart
@@ -0,0 +1,28 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:google_sign_in_platform_interface/google_sign_in_platform_interface.dart';
+
+/// Converts user data coming from native code into the proper platform interface type.
+GoogleSignInUserData? getUserDataFromMap(Map<String, dynamic>? data) {
+ if (data == null) {
+ return null;
+ }
+ return GoogleSignInUserData(
+ email: data['email']! as String,
+ id: data['id']! as String,
+ displayName: data['displayName'] as String?,
+ photoUrl: data['photoUrl'] as String?,
+ idToken: data['idToken'] as String?,
+ serverAuthCode: data['serverAuthCode'] as String?);
+}
+
+/// Converts token data coming from native code into the proper platform interface type.
+GoogleSignInTokenData getTokenDataFromMap(Map<String, dynamic> data) {
+ return GoogleSignInTokenData(
+ idToken: data['idToken'] as String?,
+ accessToken: data['accessToken'] as String?,
+ serverAuthCode: data['serverAuthCode'] as String?,
+ );
+}
diff --git a/packages/google_sign_in/google_sign_in_android/pubspec.yaml b/packages/google_sign_in/google_sign_in_android/pubspec.yaml
index efd679c..fa3dc14 100644
--- a/packages/google_sign_in/google_sign_in_android/pubspec.yaml
+++ b/packages/google_sign_in/google_sign_in_android/pubspec.yaml
@@ -2,17 +2,18 @@
description: Android implementation of the google_sign_in plugin.
repository: https://github.com/flutter/plugins/tree/main/packages/google_sign_in/google_sign_in_android
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+google_sign_in%22
-version: 5.2.5
+version: 5.2.6
environment:
sdk: ">=2.14.0 <3.0.0"
- flutter: ">=2.5.0"
+ flutter: ">=2.8.0"
flutter:
plugin:
implements: google_sign_in
platforms:
android:
+ dartPluginClass: GoogleSignInAndroid
package: io.flutter.plugins.googlesignin
pluginClass: GoogleSignInPlugin
diff --git a/packages/google_sign_in/google_sign_in_android/test/google_sign_in_android_test.dart b/packages/google_sign_in/google_sign_in_android/test/google_sign_in_android_test.dart
new file mode 100644
index 0000000..7d39ae5
--- /dev/null
+++ b/packages/google_sign_in/google_sign_in_android/test/google_sign_in_android_test.dart
@@ -0,0 +1,143 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:flutter/services.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:google_sign_in_android/google_sign_in_android.dart';
+import 'package:google_sign_in_android/src/utils.dart';
+import 'package:google_sign_in_platform_interface/google_sign_in_platform_interface.dart';
+
+const Map<String, String> kUserData = <String, String>{
+ 'email': 'john.doe@gmail.com',
+ 'id': '8162538176523816253123',
+ 'photoUrl': 'https://lh5.googleusercontent.com/photo.jpg',
+ 'displayName': 'John Doe',
+ 'idToken': '123',
+ 'serverAuthCode': '789',
+};
+
+const Map<dynamic, dynamic> kTokenData = <String, dynamic>{
+ 'idToken': '123',
+ 'accessToken': '456',
+ 'serverAuthCode': '789',
+};
+
+const Map<String, dynamic> kDefaultResponses = <String, dynamic>{
+ 'init': null,
+ 'signInSilently': kUserData,
+ 'signIn': kUserData,
+ 'signOut': null,
+ 'disconnect': null,
+ 'isSignedIn': true,
+ 'getTokens': kTokenData,
+ 'requestScopes': true,
+};
+
+final GoogleSignInUserData? kUser = getUserDataFromMap(kUserData);
+final GoogleSignInTokenData kToken =
+ getTokenDataFromMap(kTokenData as Map<String, dynamic>);
+
+void main() {
+ TestWidgetsFlutterBinding.ensureInitialized();
+
+ final GoogleSignInAndroid googleSignIn = GoogleSignInAndroid();
+ final MethodChannel channel = googleSignIn.channel;
+
+ final List<MethodCall> log = <MethodCall>[];
+ late Map<String, dynamic>
+ responses; // Some tests mutate some kDefaultResponses
+
+ setUp(() {
+ responses = Map<String, dynamic>.from(kDefaultResponses);
+ channel.setMockMethodCallHandler((MethodCall methodCall) {
+ log.add(methodCall);
+ final dynamic response = responses[methodCall.method];
+ if (response != null && response is Exception) {
+ return Future<dynamic>.error('$response');
+ }
+ return Future<dynamic>.value(response);
+ });
+ log.clear();
+ });
+
+ test('registered instance', () {
+ GoogleSignInAndroid.registerWith();
+ expect(GoogleSignInPlatform.instance, isA<GoogleSignInAndroid>());
+ });
+
+ test('signInSilently transforms platform data to GoogleSignInUserData',
+ () async {
+ final dynamic response = await googleSignIn.signInSilently();
+ expect(response, kUser);
+ });
+ test('signInSilently Exceptions -> throws', () async {
+ responses['signInSilently'] = Exception('Not a user');
+ expect(googleSignIn.signInSilently(),
+ throwsA(isInstanceOf<PlatformException>()));
+ });
+
+ test('signIn transforms platform data to GoogleSignInUserData', () async {
+ final dynamic response = await googleSignIn.signIn();
+ expect(response, kUser);
+ });
+ test('signIn Exceptions -> throws', () async {
+ responses['signIn'] = Exception('Not a user');
+ expect(googleSignIn.signIn(), throwsA(isInstanceOf<PlatformException>()));
+ });
+
+ test('getTokens transforms platform data to GoogleSignInTokenData', () async {
+ final dynamic response = await googleSignIn.getTokens(
+ email: 'example@example.com', shouldRecoverAuth: false);
+ expect(response, kToken);
+ expect(
+ log[0],
+ isMethodCall('getTokens', arguments: <String, dynamic>{
+ 'email': 'example@example.com',
+ 'shouldRecoverAuth': false,
+ }));
+ });
+
+ test('Other functions pass through arguments to the channel', () async {
+ final Map<Function, Matcher> tests = <Function, Matcher>{
+ () {
+ googleSignIn.init(
+ hostedDomain: 'example.com',
+ scopes: <String>['two', 'scopes'],
+ signInOption: SignInOption.games,
+ clientId: 'fakeClientId');
+ }: isMethodCall('init', arguments: <String, dynamic>{
+ 'hostedDomain': 'example.com',
+ 'scopes': <String>['two', 'scopes'],
+ 'signInOption': 'SignInOption.games',
+ 'clientId': 'fakeClientId',
+ }),
+ () {
+ googleSignIn.getTokens(
+ email: 'example@example.com', shouldRecoverAuth: false);
+ }: isMethodCall('getTokens', arguments: <String, dynamic>{
+ 'email': 'example@example.com',
+ 'shouldRecoverAuth': false,
+ }),
+ () {
+ googleSignIn.clearAuthCache(token: 'abc');
+ }: isMethodCall('clearAuthCache', arguments: <String, dynamic>{
+ 'token': 'abc',
+ }),
+ () {
+ googleSignIn.requestScopes(<String>['newScope', 'anotherScope']);
+ }: isMethodCall('requestScopes', arguments: <String, dynamic>{
+ 'scopes': <String>['newScope', 'anotherScope'],
+ }),
+ googleSignIn.signOut: isMethodCall('signOut', arguments: null),
+ googleSignIn.disconnect: isMethodCall('disconnect', arguments: null),
+ googleSignIn.isSignedIn: isMethodCall('isSignedIn', arguments: null),
+ };
+
+ for (final Function f in tests.keys) {
+ f();
+ }
+
+ expect(log, tests.values);
+ });
+}
diff --git a/packages/google_sign_in/google_sign_in_ios/CHANGELOG.md b/packages/google_sign_in/google_sign_in_ios/CHANGELOG.md
index e7c6847..3ffa6b5 100644
--- a/packages/google_sign_in/google_sign_in_ios/CHANGELOG.md
+++ b/packages/google_sign_in/google_sign_in_ios/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 5.2.6
+
+* Switches to an internal method channel, rather than the default.
+
## 5.2.5
* Splits from `video_player` as a federated implementation.
diff --git a/packages/google_sign_in/google_sign_in_ios/example/integration_test/google_sign_in_test.dart b/packages/google_sign_in/google_sign_in_ios/example/integration_test/google_sign_in_test.dart
index d4631f6..f1388ce 100644
--- a/packages/google_sign_in/google_sign_in_ios/example/integration_test/google_sign_in_test.dart
+++ b/packages/google_sign_in/google_sign_in_ios/example/integration_test/google_sign_in_test.dart
@@ -13,4 +13,12 @@
final GoogleSignInPlatform signIn = GoogleSignInPlatform.instance;
expect(signIn, isNotNull);
});
+
+ testWidgets('Method channel handler is present', (WidgetTester tester) async {
+ // isSignedIn can be called without initialization, so use it to validate
+ // that the native method handler is present (e.g., that the channel name
+ // is correct).
+ final GoogleSignInPlatform signIn = GoogleSignInPlatform.instance;
+ await expectLater(signIn.isSignedIn(), completes);
+ });
}
diff --git a/packages/google_sign_in/google_sign_in_ios/example/ios/RunnerTests/GoogleSignInTests.m b/packages/google_sign_in/google_sign_in_ios/example/ios/RunnerTests/GoogleSignInTests.m
index dbbd653..3bc08d1 100644
--- a/packages/google_sign_in/google_sign_in_ios/example/ios/RunnerTests/GoogleSignInTests.m
+++ b/packages/google_sign_in/google_sign_in_ios/example/ios/RunnerTests/GoogleSignInTests.m
@@ -73,35 +73,8 @@
OCMVerify([self.mockSignIn disconnect]);
}
-- (void)testClearAuthCache {
- FlutterMethodCall *methodCall = [FlutterMethodCall methodCallWithMethodName:@"clearAuthCache"
- arguments:nil];
-
- XCTestExpectation *expectation = [self expectationWithDescription:@"expect result returns true"];
- [self.plugin handleMethodCall:methodCall
- result:^(id result) {
- XCTAssertNil(result);
- [expectation fulfill];
- }];
- [self waitForExpectationsWithTimeout:5.0 handler:nil];
-}
-
#pragma mark - Init
-- (void)testInitGamesSignInUnsupported {
- FlutterMethodCall *methodCall =
- [FlutterMethodCall methodCallWithMethodName:@"init"
- arguments:@{@"signInOption" : @"SignInOption.games"}];
-
- XCTestExpectation *expectation = [self expectationWithDescription:@"expect result returns true"];
- [self.plugin handleMethodCall:methodCall
- result:^(FlutterError *result) {
- XCTAssertEqualObjects(result.code, @"unsupported-options");
- [expectation fulfill];
- }];
- [self waitForExpectationsWithTimeout:5.0 handler:nil];
-}
-
- (void)testInitGoogleServiceInfoPlist {
FlutterMethodCall *methodCall = [FlutterMethodCall
methodCallWithMethodName:@"init"
diff --git a/packages/google_sign_in/google_sign_in_ios/ios/Classes/FLTGoogleSignInPlugin.m b/packages/google_sign_in/google_sign_in_ios/ios/Classes/FLTGoogleSignInPlugin.m
index d13d64d..5ad69e2 100644
--- a/packages/google_sign_in/google_sign_in_ios/ios/Classes/FLTGoogleSignInPlugin.m
+++ b/packages/google_sign_in/google_sign_in_ios/ios/Classes/FLTGoogleSignInPlugin.m
@@ -50,7 +50,7 @@
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar {
FlutterMethodChannel *channel =
- [FlutterMethodChannel methodChannelWithName:@"plugins.flutter.io/google_sign_in"
+ [FlutterMethodChannel methodChannelWithName:@"plugins.flutter.io/google_sign_in_ios"
binaryMessenger:[registrar messenger]];
FLTGoogleSignInPlugin *instance = [[FLTGoogleSignInPlugin alloc] init];
[registrar addApplicationDelegate:instance];
@@ -78,38 +78,30 @@
- (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result {
if ([call.method isEqualToString:@"init"]) {
- NSString *signInOption = call.arguments[@"signInOption"];
- if ([signInOption isEqualToString:@"SignInOption.games"]) {
- result([FlutterError errorWithCode:@"unsupported-options"
- message:@"Games sign in is not supported on iOS"
- details:nil]);
- } else {
- NSString *path = [[NSBundle mainBundle] pathForResource:@"GoogleService-Info"
- ofType:@"plist"];
- if (path) {
- NSMutableDictionary<NSString *, NSString *> *plist =
- [[NSMutableDictionary alloc] initWithContentsOfFile:path];
- BOOL hasDynamicClientId = [call.arguments[@"clientId"] isKindOfClass:[NSString class]];
+ NSString *path = [[NSBundle mainBundle] pathForResource:@"GoogleService-Info" ofType:@"plist"];
+ if (path) {
+ NSMutableDictionary<NSString *, NSString *> *plist =
+ [[NSMutableDictionary alloc] initWithContentsOfFile:path];
+ BOOL hasDynamicClientId = [call.arguments[@"clientId"] isKindOfClass:[NSString class]];
- if (hasDynamicClientId) {
- self.signIn.clientID = call.arguments[@"clientId"];
- } else {
- self.signIn.clientID = plist[kClientIdKey];
- }
-
- self.signIn.serverClientID = plist[kServerClientIdKey];
- self.signIn.scopes = call.arguments[@"scopes"];
- if (call.arguments[@"hostedDomain"] == [NSNull null]) {
- self.signIn.hostedDomain = nil;
- } else {
- self.signIn.hostedDomain = call.arguments[@"hostedDomain"];
- }
- result(nil);
+ if (hasDynamicClientId) {
+ self.signIn.clientID = call.arguments[@"clientId"];
} else {
- result([FlutterError errorWithCode:@"missing-config"
- message:@"GoogleService-Info.plist file not found"
- details:nil]);
+ self.signIn.clientID = plist[kClientIdKey];
}
+
+ self.signIn.serverClientID = plist[kServerClientIdKey];
+ self.signIn.scopes = call.arguments[@"scopes"];
+ if (call.arguments[@"hostedDomain"] == [NSNull null]) {
+ self.signIn.hostedDomain = nil;
+ } else {
+ self.signIn.hostedDomain = call.arguments[@"hostedDomain"];
+ }
+ result(nil);
+ } else {
+ result([FlutterError errorWithCode:@"missing-config"
+ message:@"GoogleService-Info.plist file not found"
+ details:nil]);
}
} else if ([call.method isEqualToString:@"signInSilently"]) {
if ([self setAccountRequest:result]) {
@@ -144,10 +136,6 @@
if ([self setAccountRequest:result]) {
[self.signIn disconnect];
}
- } else if ([call.method isEqualToString:@"clearAuthCache"]) {
- // There's nothing to be done here on iOS since the expired/invalid
- // tokens are refreshed automatically by getTokensWithHandler.
- result(nil);
} else if ([call.method isEqualToString:@"requestScopes"]) {
GIDGoogleUser *user = self.signIn.currentUser;
if (user == nil) {
diff --git a/packages/google_sign_in/google_sign_in_ios/lib/google_sign_in_ios.dart b/packages/google_sign_in/google_sign_in_ios/lib/google_sign_in_ios.dart
new file mode 100644
index 0000000..ce88656
--- /dev/null
+++ b/packages/google_sign_in/google_sign_in_ios/lib/google_sign_in_ios.dart
@@ -0,0 +1,97 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:async';
+
+import 'package:flutter/foundation.dart' show visibleForTesting;
+import 'package:flutter/services.dart';
+import 'package:google_sign_in_platform_interface/google_sign_in_platform_interface.dart';
+
+import 'src/utils.dart';
+
+/// iOS implementation of [GoogleSignInPlatform].
+class GoogleSignInIOS extends GoogleSignInPlatform {
+ /// This is only exposed for test purposes. It shouldn't be used by clients of
+ /// the plugin as it may break or change at any time.
+ @visibleForTesting
+ MethodChannel channel =
+ const MethodChannel('plugins.flutter.io/google_sign_in_ios');
+
+ /// Registers this class as the default instance of [GoogleSignInPlatform].
+ static void registerWith() {
+ GoogleSignInPlatform.instance = GoogleSignInIOS();
+ }
+
+ @override
+ Future<void> init({
+ List<String> scopes = const <String>[],
+ SignInOption signInOption = SignInOption.standard,
+ String? hostedDomain,
+ String? clientId,
+ }) {
+ if (signInOption == SignInOption.games) {
+ throw PlatformException(
+ code: 'unsupported-options',
+ message: 'Games sign in is not supported on iOS');
+ }
+ return channel.invokeMethod<void>('init', <String, dynamic>{
+ 'scopes': scopes,
+ 'hostedDomain': hostedDomain,
+ 'clientId': clientId,
+ });
+ }
+
+ @override
+ Future<GoogleSignInUserData?> signInSilently() {
+ return channel
+ .invokeMapMethod<String, dynamic>('signInSilently')
+ .then(getUserDataFromMap);
+ }
+
+ @override
+ Future<GoogleSignInUserData?> signIn() {
+ return channel
+ .invokeMapMethod<String, dynamic>('signIn')
+ .then(getUserDataFromMap);
+ }
+
+ @override
+ Future<GoogleSignInTokenData> getTokens(
+ {required String email, bool? shouldRecoverAuth = true}) {
+ return channel
+ .invokeMapMethod<String, dynamic>('getTokens', <String, dynamic>{
+ 'email': email,
+ 'shouldRecoverAuth': shouldRecoverAuth,
+ }).then((Map<String, dynamic>? result) => getTokenDataFromMap(result!));
+ }
+
+ @override
+ Future<void> signOut() {
+ return channel.invokeMapMethod<String, dynamic>('signOut');
+ }
+
+ @override
+ Future<void> disconnect() {
+ return channel.invokeMapMethod<String, dynamic>('disconnect');
+ }
+
+ @override
+ Future<bool> isSignedIn() async {
+ return (await channel.invokeMethod<bool>('isSignedIn'))!;
+ }
+
+ @override
+ Future<void> clearAuthCache({String? token}) async {
+ // There's nothing to be done here on iOS since the expired/invalid
+ // tokens are refreshed automatically by getTokens.
+ }
+
+ @override
+ Future<bool> requestScopes(List<String> scopes) async {
+ return (await channel.invokeMethod<bool>(
+ 'requestScopes',
+ <String, List<String>>{'scopes': scopes},
+ ))!;
+ }
+}
diff --git a/packages/google_sign_in/google_sign_in_ios/lib/src/utils.dart b/packages/google_sign_in/google_sign_in_ios/lib/src/utils.dart
new file mode 100644
index 0000000..5cd7c20
--- /dev/null
+++ b/packages/google_sign_in/google_sign_in_ios/lib/src/utils.dart
@@ -0,0 +1,28 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:google_sign_in_platform_interface/google_sign_in_platform_interface.dart';
+
+/// Converts user data coming from native code into the proper platform interface type.
+GoogleSignInUserData? getUserDataFromMap(Map<String, dynamic>? data) {
+ if (data == null) {
+ return null;
+ }
+ return GoogleSignInUserData(
+ email: data['email']! as String,
+ id: data['id']! as String,
+ displayName: data['displayName'] as String?,
+ photoUrl: data['photoUrl'] as String?,
+ idToken: data['idToken'] as String?,
+ serverAuthCode: data['serverAuthCode'] as String?);
+}
+
+/// Converts token data coming from native code into the proper platform interface type.
+GoogleSignInTokenData getTokenDataFromMap(Map<String, dynamic> data) {
+ return GoogleSignInTokenData(
+ idToken: data['idToken'] as String?,
+ accessToken: data['accessToken'] as String?,
+ serverAuthCode: data['serverAuthCode'] as String?,
+ );
+}
diff --git a/packages/google_sign_in/google_sign_in_ios/pubspec.yaml b/packages/google_sign_in/google_sign_in_ios/pubspec.yaml
index 52c4f77..e5ef383 100644
--- a/packages/google_sign_in/google_sign_in_ios/pubspec.yaml
+++ b/packages/google_sign_in/google_sign_in_ios/pubspec.yaml
@@ -2,17 +2,18 @@
description: Android implementation of the google_sign_in plugin.
repository: https://github.com/flutter/plugins/tree/main/packages/google_sign_in/google_sign_in_ios
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+google_sign_in%22
-version: 5.2.5
+version: 5.2.6
environment:
sdk: ">=2.14.0 <3.0.0"
- flutter: ">=2.5.0"
+ flutter: ">=2.8.0"
flutter:
plugin:
implements: google_sign_in
platforms:
ios:
+ dartPluginClass: GoogleSignInIOS
pluginClass: FLTGoogleSignInPlugin
dependencies:
diff --git a/packages/google_sign_in/google_sign_in_ios/test/google_sign_in_ios_test.dart b/packages/google_sign_in/google_sign_in_ios/test/google_sign_in_ios_test.dart
new file mode 100644
index 0000000..92637e9
--- /dev/null
+++ b/packages/google_sign_in/google_sign_in_ios/test/google_sign_in_ios_test.dart
@@ -0,0 +1,151 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:flutter/services.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:google_sign_in_ios/google_sign_in_ios.dart';
+import 'package:google_sign_in_ios/src/utils.dart';
+import 'package:google_sign_in_platform_interface/google_sign_in_platform_interface.dart';
+
+const Map<String, String> kUserData = <String, String>{
+ 'email': 'john.doe@gmail.com',
+ 'id': '8162538176523816253123',
+ 'photoUrl': 'https://lh5.googleusercontent.com/photo.jpg',
+ 'displayName': 'John Doe',
+ 'idToken': '123',
+ 'serverAuthCode': '789',
+};
+
+const Map<dynamic, dynamic> kTokenData = <String, dynamic>{
+ 'idToken': '123',
+ 'accessToken': '456',
+ 'serverAuthCode': '789',
+};
+
+const Map<String, dynamic> kDefaultResponses = <String, dynamic>{
+ 'init': null,
+ 'signInSilently': kUserData,
+ 'signIn': kUserData,
+ 'signOut': null,
+ 'disconnect': null,
+ 'isSignedIn': true,
+ 'getTokens': kTokenData,
+ 'requestScopes': true,
+};
+
+final GoogleSignInUserData? kUser = getUserDataFromMap(kUserData);
+final GoogleSignInTokenData kToken =
+ getTokenDataFromMap(kTokenData as Map<String, dynamic>);
+
+void main() {
+ TestWidgetsFlutterBinding.ensureInitialized();
+
+ final GoogleSignInIOS googleSignIn = GoogleSignInIOS();
+ final MethodChannel channel = googleSignIn.channel;
+
+ late List<MethodCall> log;
+ late Map<String, dynamic>
+ responses; // Some tests mutate some kDefaultResponses
+
+ setUp(() {
+ responses = Map<String, dynamic>.from(kDefaultResponses);
+ log = <MethodCall>[];
+ channel.setMockMethodCallHandler((MethodCall methodCall) {
+ log.add(methodCall);
+ final dynamic response = responses[methodCall.method];
+ if (response != null && response is Exception) {
+ return Future<dynamic>.error('$response');
+ }
+ return Future<dynamic>.value(response);
+ });
+ });
+
+ test('registered instance', () {
+ GoogleSignInIOS.registerWith();
+ expect(GoogleSignInPlatform.instance, isA<GoogleSignInIOS>());
+ });
+
+ test('init throws for SignInOptions.games', () async {
+ expect(
+ () => googleSignIn.init(
+ hostedDomain: 'example.com',
+ signInOption: SignInOption.games,
+ clientId: 'fakeClientId'),
+ throwsA(isInstanceOf<PlatformException>().having(
+ (PlatformException e) => e.code, 'code', 'unsupported-options')));
+ });
+
+ test('signInSilently transforms platform data to GoogleSignInUserData',
+ () async {
+ final dynamic response = await googleSignIn.signInSilently();
+ expect(response, kUser);
+ });
+ test('signInSilently Exceptions -> throws', () async {
+ responses['signInSilently'] = Exception('Not a user');
+ expect(googleSignIn.signInSilently(),
+ throwsA(isInstanceOf<PlatformException>()));
+ });
+
+ test('signIn transforms platform data to GoogleSignInUserData', () async {
+ final dynamic response = await googleSignIn.signIn();
+ expect(response, kUser);
+ });
+ test('signIn Exceptions -> throws', () async {
+ responses['signIn'] = Exception('Not a user');
+ expect(googleSignIn.signIn(), throwsA(isInstanceOf<PlatformException>()));
+ });
+
+ test('getTokens transforms platform data to GoogleSignInTokenData', () async {
+ final dynamic response = await googleSignIn.getTokens(
+ email: 'example@example.com', shouldRecoverAuth: false);
+ expect(response, kToken);
+ expect(
+ log[0],
+ isMethodCall('getTokens', arguments: <String, dynamic>{
+ 'email': 'example@example.com',
+ 'shouldRecoverAuth': false,
+ }));
+ });
+
+ test('clearAuthCache is a no-op', () async {
+ await googleSignIn.clearAuthCache(token: 'abc');
+ expect(log.isEmpty, true);
+ });
+
+ test('Other functions pass through arguments to the channel', () async {
+ final Map<Function, Matcher> tests = <Function, Matcher>{
+ () {
+ googleSignIn.init(
+ hostedDomain: 'example.com',
+ scopes: <String>['two', 'scopes'],
+ clientId: 'fakeClientId');
+ }: isMethodCall('init', arguments: <String, dynamic>{
+ 'hostedDomain': 'example.com',
+ 'scopes': <String>['two', 'scopes'],
+ 'clientId': 'fakeClientId',
+ }),
+ () {
+ googleSignIn.getTokens(
+ email: 'example@example.com', shouldRecoverAuth: false);
+ }: isMethodCall('getTokens', arguments: <String, dynamic>{
+ 'email': 'example@example.com',
+ 'shouldRecoverAuth': false,
+ }),
+ () {
+ googleSignIn.requestScopes(<String>['newScope', 'anotherScope']);
+ }: isMethodCall('requestScopes', arguments: <String, dynamic>{
+ 'scopes': <String>['newScope', 'anotherScope'],
+ }),
+ googleSignIn.signOut: isMethodCall('signOut', arguments: null),
+ googleSignIn.disconnect: isMethodCall('disconnect', arguments: null),
+ googleSignIn.isSignedIn: isMethodCall('isSignedIn', arguments: null),
+ };
+
+ for (final Function f in tests.keys) {
+ f();
+ }
+
+ expect(log, tests.values);
+ });
+}