[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);
+  });
+}