[google_sign_in] Implement Dart-based configuration and `serverClientId` (#6034)

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 67fb08f..a91f0c0 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,12 @@
+## 6.0.0
+
+* Deprecates `clientId` and adds support for `serverClientId` instead.
+  Historically `clientId` was interpreted as `serverClientId`, but only on Android. On
+  other platforms it was interpreted as the OAuth `clientId` of the app. For backwards-compatibility
+  `clientId` will still be used as a server client ID if `serverClientId` is not provided.
+* **BREAKING CHANGES**:
+  * Adds `serverClientId` parameter to `IDelegate.init` (Java).
+
 ## 5.2.8
 
 * Suppresses `deprecation` warnings (for using Android V1 embedding).
@@ -13,4 +22,4 @@
 
 ## 5.2.5
 
-* Splits from `video_player` as a federated implementation.
+* Splits from `google_sign_in` 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 9bee8fa..2164023 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
@@ -8,6 +8,7 @@
 import android.app.Activity;
 import android.content.Context;
 import android.content.Intent;
+import android.util.Log;
 import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
 import com.google.android.gms.auth.GoogleAuthUtil;
@@ -138,7 +139,9 @@
         List<String> requestedScopes = call.argument("scopes");
         String hostedDomain = call.argument("hostedDomain");
         String clientId = call.argument("clientId");
-        delegate.init(result, signInOption, requestedScopes, hostedDomain, clientId);
+        String serverClientId = call.argument("serverClientId");
+        delegate.init(
+            result, signInOption, requestedScopes, hostedDomain, clientId, serverClientId);
         break;
 
       case METHOD_SIGN_IN_SILENTLY:
@@ -194,7 +197,8 @@
         String signInOption,
         List<String> requestedScopes,
         String hostedDomain,
-        String clientId);
+        String clientId,
+        String serverClientId);
 
     /**
      * Returns the account information for the user who is signed in to this app. If no user is
@@ -321,7 +325,8 @@
         String signInOption,
         List<String> requestedScopes,
         String hostedDomain,
-        String clientId) {
+        String clientId,
+        String serverClientId) {
       try {
         GoogleSignInOptions.Builder optionsBuilder;
 
@@ -338,20 +343,38 @@
             throw new IllegalStateException("Unknown signInOption");
         }
 
-        // Only requests a clientId if google-services.json was present and parsed
-        // by the google-services Gradle script.
-        // TODO(jackson): Perhaps we should provide a mechanism to override this
-        // behavior.
-        int clientIdIdentifier =
-            context
-                .getResources()
-                .getIdentifier("default_web_client_id", "string", context.getPackageName());
+        // The clientId parameter is not supported on Android.
+        // Android apps are identified by their package name and the SHA-1 of their signing key.
+        // https://developers.google.com/android/guides/client-auth
+        // https://developers.google.com/identity/sign-in/android/start#configure-a-google-api-project
         if (!Strings.isNullOrEmpty(clientId)) {
-          optionsBuilder.requestIdToken(clientId);
-          optionsBuilder.requestServerAuthCode(clientId);
-        } else if (clientIdIdentifier != 0) {
-          optionsBuilder.requestIdToken(context.getString(clientIdIdentifier));
-          optionsBuilder.requestServerAuthCode(context.getString(clientIdIdentifier));
+          if (Strings.isNullOrEmpty(serverClientId)) {
+            Log.w(
+                "google_sing_in",
+                "clientId is not supported on Android and is interpreted as serverClientId."
+                    + "Use serverClientId instead to suppress this warning.");
+            serverClientId = clientId;
+          } else {
+            Log.w("google_sing_in", "clientId is not supported on Android and is ignored.");
+          }
+        }
+
+        if (Strings.isNullOrEmpty(serverClientId)) {
+          // Only requests a clientId if google-services.json was present and parsed
+          // by the google-services Gradle script.
+          // TODO(jackson): Perhaps we should provide a mechanism to override this
+          // behavior.
+          int webClientIdIdentifier =
+              context
+                  .getResources()
+                  .getIdentifier("default_web_client_id", "string", context.getPackageName());
+          if (webClientIdIdentifier != 0) {
+            serverClientId = context.getString(webClientIdIdentifier);
+          }
+        }
+        if (!Strings.isNullOrEmpty(serverClientId)) {
+          optionsBuilder.requestIdToken(serverClientId);
+          optionsBuilder.requestServerAuthCode(serverClientId);
         }
         for (String scope : requestedScopes) {
           optionsBuilder.requestScopes(new Scope(scope));
@@ -361,7 +384,7 @@
         }
 
         this.requestedScopes = requestedScopes;
-        signInClient = GoogleSignIn.getClient(context, optionsBuilder.build());
+        signInClient = googleSignInWrapper.getClient(context, optionsBuilder.build());
         result.success(null);
       } catch (Exception e) {
         result.error(ERROR_REASON_EXCEPTION, e.getMessage(), null);
diff --git a/packages/google_sign_in/google_sign_in_android/android/src/main/java/io/flutter/plugins/googlesignin/GoogleSignInWrapper.java b/packages/google_sign_in/google_sign_in_android/android/src/main/java/io/flutter/plugins/googlesignin/GoogleSignInWrapper.java
index 5af0b50..c035329 100644
--- a/packages/google_sign_in/google_sign_in_android/android/src/main/java/io/flutter/plugins/googlesignin/GoogleSignInWrapper.java
+++ b/packages/google_sign_in/google_sign_in_android/android/src/main/java/io/flutter/plugins/googlesignin/GoogleSignInWrapper.java
@@ -8,6 +8,8 @@
 import android.content.Context;
 import com.google.android.gms.auth.api.signin.GoogleSignIn;
 import com.google.android.gms.auth.api.signin.GoogleSignInAccount;
+import com.google.android.gms.auth.api.signin.GoogleSignInClient;
+import com.google.android.gms.auth.api.signin.GoogleSignInOptions;
 import com.google.android.gms.common.api.Scope;
 
 /**
@@ -21,6 +23,10 @@
  */
 public class GoogleSignInWrapper {
 
+  GoogleSignInClient getClient(Context context, GoogleSignInOptions options) {
+    return GoogleSignIn.getClient(context, options);
+  }
+
   GoogleSignInAccount getLastSignedInAccount(Context context) {
     return GoogleSignIn.getLastSignedInAccount(context);
   }
diff --git a/packages/google_sign_in/google_sign_in_android/android/src/test/java/io/flutter/plugins/googlesignin/GoogleSignInTest.java b/packages/google_sign_in/google_sign_in_android/android/src/test/java/io/flutter/plugins/googlesignin/GoogleSignInTest.java
index 3b6ad96..11f8cda 100644
--- a/packages/google_sign_in/google_sign_in_android/android/src/test/java/io/flutter/plugins/googlesignin/GoogleSignInTest.java
+++ b/packages/google_sign_in/google_sign_in_android/android/src/test/java/io/flutter/plugins/googlesignin/GoogleSignInTest.java
@@ -4,6 +4,7 @@
 
 package io.flutter.plugins.googlesignin;
 
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -12,7 +13,10 @@
 import android.app.Activity;
 import android.content.Context;
 import android.content.Intent;
+import android.content.res.Resources;
 import com.google.android.gms.auth.api.signin.GoogleSignInAccount;
+import com.google.android.gms.auth.api.signin.GoogleSignInClient;
+import com.google.android.gms.auth.api.signin.GoogleSignInOptions;
 import com.google.android.gms.common.api.Scope;
 import io.flutter.plugin.common.BinaryMessenger;
 import io.flutter.plugin.common.MethodCall;
@@ -21,6 +25,7 @@
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
+import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
@@ -30,12 +35,14 @@
 
 public class GoogleSignInTest {
   @Mock Context mockContext;
+  @Mock Resources mockResources;
   @Mock Activity mockActivity;
   @Mock PluginRegistry.Registrar mockRegistrar;
   @Mock BinaryMessenger mockMessenger;
   @Spy MethodChannel.Result result;
   @Mock GoogleSignInWrapper mockGoogleSignIn;
   @Mock GoogleSignInAccount account;
+  @Mock GoogleSignInClient mockClient;
   private GoogleSignInPlugin plugin;
 
   @Before
@@ -44,6 +51,7 @@
     when(mockRegistrar.messenger()).thenReturn(mockMessenger);
     when(mockRegistrar.context()).thenReturn(mockContext);
     when(mockRegistrar.activity()).thenReturn(mockActivity);
+    when(mockContext.getResources()).thenReturn(mockResources);
     plugin = new GoogleSignInPlugin();
     plugin.initInstance(mockRegistrar.messenger(), mockRegistrar.context(), mockGoogleSignIn);
     plugin.setUpRegistrar(mockRegistrar);
@@ -195,4 +203,68 @@
 
     plugin.onMethodCall(new MethodCall("signIn", null), null);
   }
+
+  @Test
+  public void init_LoadsServerClientIdFromResources() {
+    final String packageName = "fakePackageName";
+    final String serverClientId = "fakeServerClientId";
+    final int resourceId = 1;
+    MethodCall methodCall = buildInitMethodCall(null, null);
+    when(mockContext.getPackageName()).thenReturn(packageName);
+    when(mockResources.getIdentifier("default_web_client_id", "string", packageName))
+        .thenReturn(resourceId);
+    when(mockContext.getString(resourceId)).thenReturn(serverClientId);
+    initAndAssertServerClientId(methodCall, serverClientId);
+  }
+
+  @Test
+  public void init_InterpretsClientIdAsServerClientId() {
+    final String clientId = "fakeClientId";
+    MethodCall methodCall = buildInitMethodCall(clientId, null);
+    initAndAssertServerClientId(methodCall, clientId);
+  }
+
+  @Test
+  public void init_ForwardsServerClientId() {
+    final String serverClientId = "fakeServerClientId";
+    MethodCall methodCall = buildInitMethodCall(null, serverClientId);
+    initAndAssertServerClientId(methodCall, serverClientId);
+  }
+
+  @Test
+  public void init_IgnoresClientIdIfServerClientIdIsProvided() {
+    final String clientId = "fakeClientId";
+    final String serverClientId = "fakeServerClientId";
+    MethodCall methodCall = buildInitMethodCall(clientId, serverClientId);
+    initAndAssertServerClientId(methodCall, serverClientId);
+  }
+
+  public void initAndAssertServerClientId(MethodCall methodCall, String serverClientId) {
+    ArgumentCaptor<GoogleSignInOptions> optionsCaptor =
+        ArgumentCaptor.forClass(GoogleSignInOptions.class);
+    when(mockGoogleSignIn.getClient(any(Context.class), optionsCaptor.capture()))
+        .thenReturn(mockClient);
+    plugin.onMethodCall(methodCall, result);
+    verify(result).success(null);
+    Assert.assertEquals(serverClientId, optionsCaptor.getValue().getServerClientId());
+  }
+
+  private static MethodCall buildInitMethodCall(String clientId, String serverClientId) {
+    return buildInitMethodCall(
+        "SignInOption.standard", Collections.<String>emptyList(), clientId, serverClientId);
+  }
+
+  private static MethodCall buildInitMethodCall(
+      String signInOption, List<String> scopes, String clientId, String serverClientId) {
+    HashMap<String, Object> arguments = new HashMap<>();
+    arguments.put("signInOption", signInOption);
+    arguments.put("scopes", scopes);
+    if (clientId != null) {
+      arguments.put("clientId", clientId);
+    }
+    if (serverClientId != null) {
+      arguments.put("serverClientId", serverClientId);
+    }
+    return new MethodCall("init", arguments);
+  }
 }
diff --git a/packages/google_sign_in/google_sign_in_android/example/lib/main.dart b/packages/google_sign_in/google_sign_in_android/example/lib/main.dart
index 526cf8b..5818b60 100644
--- a/packages/google_sign_in/google_sign_in_android/example/lib/main.dart
+++ b/packages/google_sign_in/google_sign_in_android/example/lib/main.dart
@@ -41,14 +41,16 @@
   }
 
   Future<void> _ensureInitialized() {
-    return _initialization ??= GoogleSignInPlatform.instance.init(
+    return _initialization ??=
+        GoogleSignInPlatform.instance.initWithParams(const SignInInitParameters(
       scopes: <String>[
         'email',
         'https://www.googleapis.com/auth/contacts.readonly',
       ],
-    )..catchError((dynamic _) {
-        _initialization = null;
-      });
+    ))
+          ..catchError((dynamic _) {
+            _initialization = null;
+          });
   }
 
   void _setUser(GoogleSignInUserData? user) {
diff --git a/packages/google_sign_in/google_sign_in_android/example/pubspec.yaml b/packages/google_sign_in/google_sign_in_android/example/pubspec.yaml
index 3aa8a80..4d12bba 100644
--- a/packages/google_sign_in/google_sign_in_android/example/pubspec.yaml
+++ b/packages/google_sign_in/google_sign_in_android/example/pubspec.yaml
@@ -16,7 +16,7 @@
     # The example app is bundled with the plugin so we use a path dependency on
     # the parent directory to use the current plugin's version.
     path: ../
-  google_sign_in_platform_interface: ^2.1.0
+  google_sign_in_platform_interface: ^2.2.0
   http: ^0.13.0
 
 dev_dependencies:
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 c4c30cd..4424ce3 100644
--- a/packages/google_sign_in/google_sign_in_android/pubspec.yaml
+++ b/packages/google_sign_in/google_sign_in_android/pubspec.yaml
@@ -2,7 +2,7 @@
 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.8
+version: 6.0.0
 
 environment:
   sdk: ">=2.14.0 <3.0.0"
@@ -20,7 +20,7 @@
 dependencies:
   flutter:
     sdk: flutter
-  google_sign_in_platform_interface: ^2.1.0
+  google_sign_in_platform_interface: ^2.2.0
 
 dev_dependencies:
   flutter_driver:
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 c17f141..5ed38de 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,8 @@
+## 5.4.0
+
+* Adds support for `serverClientId` configuration option.
+* Makes `Google-Services.info` file optional.
+
 ## 5.3.1
 
 * Suppresses warnings for pre-iOS-13 codepaths.
@@ -17,4 +22,4 @@
 
 ## 5.2.5
 
-* Splits from `video_player` as a federated implementation.
+* Splits from `google_sign_in` as a federated implementation.
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 7efd490..5738b7f 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
@@ -96,6 +96,25 @@
 
 #pragma mark - Init
 
+- (void)testInitNoClientIdError {
+  // Init plugin without GoogleService-Info.plist.
+  self.plugin = [[FLTGoogleSignInPlugin alloc] initWithSignIn:self.mockSignIn
+                                  withGoogleServiceProperties:nil];
+
+  // init call does not provide a clientId.
+  FlutterMethodCall *initMethodCall = [FlutterMethodCall methodCallWithMethodName:@"init"
+                                                                        arguments:@{}];
+
+  XCTestExpectation *initExpectation =
+      [self expectationWithDescription:@"expect result returns true"];
+  [self.plugin handleMethodCall:initMethodCall
+                         result:^(FlutterError *result) {
+                           XCTAssertEqualObjects(result.code, @"missing-config");
+                           [initExpectation fulfill];
+                         }];
+  [self waitForExpectationsWithTimeout:5.0 handler:nil];
+}
+
 - (void)testInitGoogleServiceInfoPlist {
   FlutterMethodCall *initMethodCall =
       [FlutterMethodCall methodCallWithMethodName:@"init"
@@ -133,6 +152,10 @@
 }
 
 - (void)testInitDynamicClientIdNullDomain {
+  // Init plugin without GoogleService-Info.plist.
+  self.plugin = [[FLTGoogleSignInPlugin alloc] initWithSignIn:self.mockSignIn
+                                  withGoogleServiceProperties:nil];
+
   FlutterMethodCall *initMethodCall = [FlutterMethodCall
       methodCallWithMethodName:@"init"
                      arguments:@{@"hostedDomain" : [NSNull null], @"clientId" : @"mockClientId"}];
@@ -163,6 +186,40 @@
                       callback:OCMOCK_ANY]);
 }
 
+- (void)testInitDynamicServerClientIdNullDomain {
+  FlutterMethodCall *initMethodCall =
+      [FlutterMethodCall methodCallWithMethodName:@"init"
+                                        arguments:@{
+                                          @"hostedDomain" : [NSNull null],
+                                          @"serverClientId" : @"mockServerClientId"
+                                        }];
+
+  XCTestExpectation *initExpectation =
+      [self expectationWithDescription:@"expect result returns true"];
+  [self.plugin handleMethodCall:initMethodCall
+                         result:^(id result) {
+                           XCTAssertNil(result);
+                           [initExpectation fulfill];
+                         }];
+  [self waitForExpectationsWithTimeout:5.0 handler:nil];
+
+  // Initialization values used in the next sign in request.
+  FlutterMethodCall *signInMethodCall = [FlutterMethodCall methodCallWithMethodName:@"signIn"
+                                                                          arguments:nil];
+  [self.plugin handleMethodCall:signInMethodCall
+                         result:^(id r){
+                         }];
+  OCMVerify([self.mockSignIn
+       signInWithConfiguration:[OCMArg checkWithBlock:^BOOL(GIDConfiguration *configuration) {
+         return configuration.hostedDomain == nil &&
+                [configuration.serverClientID isEqualToString:@"mockServerClientId"];
+       }]
+      presentingViewController:[OCMArg isKindOfClass:[FlutterViewController class]]
+                          hint:nil
+              additionalScopes:OCMOCK_ANY
+                      callback:OCMOCK_ANY]);
+}
+
 #pragma mark - Is signed in
 
 - (void)testIsNotSignedIn {
diff --git a/packages/google_sign_in/google_sign_in_ios/example/lib/main.dart b/packages/google_sign_in/google_sign_in_ios/example/lib/main.dart
index 526cf8b..e23935d 100644
--- a/packages/google_sign_in/google_sign_in_ios/example/lib/main.dart
+++ b/packages/google_sign_in/google_sign_in_ios/example/lib/main.dart
@@ -31,7 +31,8 @@
 class SignInDemoState extends State<SignInDemo> {
   GoogleSignInUserData? _currentUser;
   String _contactText = '';
-  // Future that completes when `init` has completed on the sign in instance.
+  // Future that completes when `initWithParams` has completed on the sign in
+  // instance.
   Future<void>? _initialization;
 
   @override
@@ -41,14 +42,16 @@
   }
 
   Future<void> _ensureInitialized() {
-    return _initialization ??= GoogleSignInPlatform.instance.init(
+    return _initialization ??=
+        GoogleSignInPlatform.instance.initWithParams(const SignInInitParameters(
       scopes: <String>[
         'email',
         'https://www.googleapis.com/auth/contacts.readonly',
       ],
-    )..catchError((dynamic _) {
-        _initialization = null;
-      });
+    ))
+          ..catchError((dynamic _) {
+            _initialization = null;
+          });
   }
 
   void _setUser(GoogleSignInUserData? user) {
diff --git a/packages/google_sign_in/google_sign_in_ios/example/pubspec.yaml b/packages/google_sign_in/google_sign_in_ios/example/pubspec.yaml
index ed51e3b..d17c929 100644
--- a/packages/google_sign_in/google_sign_in_ios/example/pubspec.yaml
+++ b/packages/google_sign_in/google_sign_in_ios/example/pubspec.yaml
@@ -16,7 +16,7 @@
     # The example app is bundled with the plugin so we use a path dependency on
     # the parent directory to use the current plugin's version.
     path: ../
-  google_sign_in_platform_interface: ^2.1.0
+  google_sign_in_platform_interface: ^2.2.0
   http: ^0.13.0
 
 dev_dependencies:
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 608cdc2..7beb604 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
@@ -14,6 +14,15 @@
 
 static NSString *const kServerClientIdKey = @"SERVER_CLIENT_ID";
 
+static NSDictionary<NSString *, id> *loadGoogleServiceInfo() {
+  NSString *plistPath = [[NSBundle mainBundle] pathForResource:@"GoogleService-Info"
+                                                        ofType:@"plist"];
+  if (plistPath) {
+    return [[NSDictionary alloc] initWithContentsOfFile:plistPath];
+  }
+  return nil;
+}
+
 // These error codes must match with ones declared on Android and Dart sides.
 static NSString *const kErrorReasonSignInRequired = @"sign_in_required";
 static NSString *const kErrorReasonSignInCanceled = @"sign_in_canceled";
@@ -52,6 +61,9 @@
 // sign in, sign out, and requesting additional scopes.
 @property(strong, readonly) GIDSignIn *signIn;
 
+// The contents of GoogleService-Info.plist, if it exists.
+@property(strong, nullable) NSDictionary<NSString *, id> *googleServiceProperties;
+
 // Redeclared as not a designated initializer.
 - (instancetype)init;
 
@@ -73,9 +85,15 @@
 }
 
 - (instancetype)initWithSignIn:(GIDSignIn *)signIn {
+  return [self initWithSignIn:signIn withGoogleServiceProperties:loadGoogleServiceInfo()];
+}
+
+- (instancetype)initWithSignIn:(GIDSignIn *)signIn
+    withGoogleServiceProperties:(nullable NSDictionary<NSString *, id> *)googleServiceProperties {
   self = [super init];
   if (self) {
     _signIn = signIn;
+    _googleServiceProperties = googleServiceProperties;
 
     // On the iOS simulator, we get "Broken pipe" errors after sign-in for some
     // unknown reason. We can avoid crashing the app by ignoring them.
@@ -91,6 +109,7 @@
   if ([call.method isEqualToString:@"init"]) {
     GIDConfiguration *configuration =
         [self configurationWithClientIdArgument:call.arguments[@"clientId"]
+                         serverClientIdArgument:call.arguments[@"serverClientId"]
                            hostedDomainArgument:call.arguments[@"hostedDomain"]];
     if (configuration != nil) {
       if ([call.arguments[@"scopes"] isKindOfClass:[NSArray class]]) {
@@ -100,7 +119,8 @@
       result(nil);
     } else {
       result([FlutterError errorWithCode:@"missing-config"
-                                 message:@"GoogleService-Info.plist file not found"
+                                 message:@"GoogleService-Info.plist file not found and clientId "
+                                         @"was not provided programmatically."
                                  details:nil]);
     }
   } else if ([call.method isEqualToString:@"signInSilently"]) {
@@ -113,6 +133,7 @@
     @try {
       GIDConfiguration *configuration = self.configuration
                                             ?: [self configurationWithClientIdArgument:nil
+                                                                serverClientIdArgument:nil
                                                                   hostedDomainArgument:nil];
       [self.signIn signInWithConfiguration:configuration
                   presentingViewController:[self topViewController]
@@ -198,25 +219,32 @@
 
 #pragma mark - private methods
 
-/// @return @c nil if GoogleService-Info.plist not found.
+/// @return @c nil if GoogleService-Info.plist not found and clientId is not provided.
 - (GIDConfiguration *)configurationWithClientIdArgument:(id)clientIDArg
+                                 serverClientIdArgument:(id)serverClientIDArg
                                    hostedDomainArgument:(id)hostedDomainArg {
-  NSString *plistPath = [NSBundle.mainBundle pathForResource:@"GoogleService-Info" ofType:@"plist"];
-  if (plistPath == nil) {
+  NSString *clientID;
+  BOOL hasDynamicClientId = [clientIDArg isKindOfClass:[NSString class]];
+  if (hasDynamicClientId) {
+    clientID = clientIDArg;
+  } else if (self.googleServiceProperties) {
+    clientID = self.googleServiceProperties[kClientIdKey];
+  } else {
+    // We couldn't resolve a clientId, without which we cannot create a GIDConfiguration.
     return nil;
   }
 
-  NSDictionary<NSString *, id> *plist = [[NSDictionary alloc] initWithContentsOfFile:plistPath];
-
-  BOOL hasDynamicClientId = [clientIDArg isKindOfClass:[NSString class]];
-  NSString *clientID = hasDynamicClientId ? clientIDArg : plist[kClientIdKey];
+  BOOL hasDynamicServerClientId = [serverClientIDArg isKindOfClass:[NSString class]];
+  NSString *serverClientID = hasDynamicServerClientId
+                                 ? serverClientIDArg
+                                 : self.googleServiceProperties[kServerClientIdKey];
 
   NSString *hostedDomain = nil;
   if (hostedDomainArg != [NSNull null]) {
     hostedDomain = hostedDomainArg;
   }
   return [[GIDConfiguration alloc] initWithClientID:clientID
-                                     serverClientID:plist[kServerClientIdKey]
+                                     serverClientID:serverClientID
                                        hostedDomain:hostedDomain
                                         openIDRealm:nil];
 }
diff --git a/packages/google_sign_in/google_sign_in_ios/ios/Classes/FLTGoogleSignInPlugin_Test.h b/packages/google_sign_in/google_sign_in_ios/ios/Classes/FLTGoogleSignInPlugin_Test.h
index f8d5be6..17ddb7f 100644
--- a/packages/google_sign_in/google_sign_in_ios/ios/Classes/FLTGoogleSignInPlugin_Test.h
+++ b/packages/google_sign_in/google_sign_in_ios/ios/Classes/FLTGoogleSignInPlugin_Test.h
@@ -6,12 +6,21 @@
 
 #import <google_sign_in_ios/FLTGoogleSignInPlugin.h>
 
+NS_ASSUME_NONNULL_BEGIN
+
 @class GIDSignIn;
 
 /// Methods exposed for unit testing.
 @interface FLTGoogleSignInPlugin ()
 
 /// Inject @c GIDSignIn for testing.
-- (instancetype)initWithSignIn:(GIDSignIn *)signIn NS_DESIGNATED_INITIALIZER;
+- (instancetype)initWithSignIn:(GIDSignIn *)signIn;
+
+/// Inject @c GIDSignIn and @c googleServiceProperties for testing.
+- (instancetype)initWithSignIn:(GIDSignIn *)signIn
+    withGoogleServiceProperties:(nullable NSDictionary<NSString *, id> *)googleServiceProperties
+    NS_DESIGNATED_INITIALIZER;
 
 @end
+
+NS_ASSUME_NONNULL_END
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 13f045b..65c8928 100644
--- a/packages/google_sign_in/google_sign_in_ios/pubspec.yaml
+++ b/packages/google_sign_in/google_sign_in_ios/pubspec.yaml
@@ -2,7 +2,7 @@
 description: iOS 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.3.1
+version: 5.4.0
 
 environment:
   sdk: ">=2.14.0 <3.0.0"
@@ -19,7 +19,7 @@
 dependencies:
   flutter:
     sdk: flutter
-  google_sign_in_platform_interface: ^2.1.0
+  google_sign_in_platform_interface: ^2.2.0
 
 dev_dependencies:
   flutter_driver:
diff --git a/packages/google_sign_in/google_sign_in_web/CHANGELOG.md b/packages/google_sign_in/google_sign_in_web/CHANGELOG.md
index 672b1b2..12e6d96 100644
--- a/packages/google_sign_in/google_sign_in_web/CHANGELOG.md
+++ b/packages/google_sign_in/google_sign_in_web/CHANGELOG.md
@@ -1,3 +1,8 @@
+## 0.10.2
+
+* Migrates to new platform-interface `initWithParams` method.
+* Throws when unsupported `serverClientId` option is provided.
+
 ## 0.10.1+3
 
 * Updates references to the obsolete master branch.
diff --git a/packages/google_sign_in/google_sign_in_web/README.md b/packages/google_sign_in/google_sign_in_web/README.md
index 6f6dcf2..7c02379 100644
--- a/packages/google_sign_in/google_sign_in_web/README.md
+++ b/packages/google_sign_in/google_sign_in_web/README.md
@@ -63,8 +63,11 @@
   ],
 );
 ```
+
 [Full list of available scopes](https://developers.google.com/identity/protocols/googlescopes).
 
+Note that the `serverClientId` parameter of the `GoogleSignIn` constructor is not supported on Web.
+
 You can now use the `GoogleSignIn` class to authenticate in your Dart code, e.g.
 
 ```dart
diff --git a/packages/google_sign_in/google_sign_in_web/example/integration_test/auth2_legacy_init_test.dart b/packages/google_sign_in/google_sign_in_web/example/integration_test/auth2_legacy_init_test.dart
new file mode 100644
index 0000000..12f8f2f
--- /dev/null
+++ b/packages/google_sign_in/google_sign_in_web/example/integration_test/auth2_legacy_init_test.dart
@@ -0,0 +1,223 @@
+// 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.
+
+// This file is a copy of `auth2_test.dart`, before it was migrated to the
+// new `initWithParams` method, and is kept to ensure test coverage of the
+// deprecated `init` method, until it is removed.
+
+import 'dart:html' as html;
+
+import 'package:flutter/services.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:google_sign_in_platform_interface/google_sign_in_platform_interface.dart';
+import 'package:google_sign_in_web/google_sign_in_web.dart';
+import 'package:integration_test/integration_test.dart';
+import 'package:js/js_util.dart' as js_util;
+
+import 'gapi_mocks/gapi_mocks.dart' as gapi_mocks;
+import 'src/test_utils.dart';
+
+void main() {
+  IntegrationTestWidgetsFlutterBinding.ensureInitialized();
+
+  final GoogleSignInTokenData expectedTokenData =
+      GoogleSignInTokenData(idToken: '70k3n', accessToken: 'access_70k3n');
+
+  final GoogleSignInUserData expectedUserData = GoogleSignInUserData(
+    displayName: 'Foo Bar',
+    email: 'foo@example.com',
+    id: '123',
+    photoUrl: 'http://example.com/img.jpg',
+    idToken: expectedTokenData.idToken,
+  );
+
+  late GoogleSignInPlugin plugin;
+
+  group('plugin.initialize() throws a catchable exception', () {
+    setUp(() {
+      // The pre-configured use case for the instances of the plugin in this test
+      gapiUrl = toBase64Url(gapi_mocks.auth2InitError());
+      plugin = GoogleSignInPlugin();
+    });
+
+    testWidgets('initialize throws PlatformException',
+        (WidgetTester tester) async {
+      await expectLater(
+          plugin.init(
+            hostedDomain: 'foo',
+            scopes: <String>['some', 'scope'],
+            clientId: '1234',
+          ),
+          throwsA(isA<PlatformException>()));
+    });
+
+    testWidgets('initialize forwards error code from JS',
+        (WidgetTester tester) async {
+      try {
+        await plugin.init(
+          hostedDomain: 'foo',
+          scopes: <String>['some', 'scope'],
+          clientId: '1234',
+        );
+        fail('plugin.initialize should have thrown an exception!');
+      } catch (e) {
+        final String code = js_util.getProperty<String>(e, 'code');
+        expect(code, 'idpiframe_initialization_failed');
+      }
+    });
+  });
+
+  group('other methods also throw catchable exceptions on initialize fail', () {
+    // This function ensures that initialize gets called, but for some reason,
+    // we ignored that it has thrown stuff...
+    Future<void> _discardInit() async {
+      try {
+        await plugin.init(
+          hostedDomain: 'foo',
+          scopes: <String>['some', 'scope'],
+          clientId: '1234',
+        );
+      } catch (e) {
+        // Noop so we can call other stuff
+      }
+    }
+
+    setUp(() {
+      gapiUrl = toBase64Url(gapi_mocks.auth2InitError());
+      plugin = GoogleSignInPlugin();
+    });
+
+    testWidgets('signInSilently throws', (WidgetTester tester) async {
+      await _discardInit();
+      await expectLater(
+          plugin.signInSilently(), throwsA(isA<PlatformException>()));
+    });
+
+    testWidgets('signIn throws', (WidgetTester tester) async {
+      await _discardInit();
+      await expectLater(plugin.signIn(), throwsA(isA<PlatformException>()));
+    });
+
+    testWidgets('getTokens throws', (WidgetTester tester) async {
+      await _discardInit();
+      await expectLater(plugin.getTokens(email: 'test@example.com'),
+          throwsA(isA<PlatformException>()));
+    });
+    testWidgets('requestScopes', (WidgetTester tester) async {
+      await _discardInit();
+      await expectLater(plugin.requestScopes(<String>['newScope']),
+          throwsA(isA<PlatformException>()));
+    });
+  });
+
+  group('auth2 Init Successful', () {
+    setUp(() {
+      // The pre-configured use case for the instances of the plugin in this test
+      gapiUrl = toBase64Url(gapi_mocks.auth2InitSuccess(expectedUserData));
+      plugin = GoogleSignInPlugin();
+    });
+
+    testWidgets('Init requires clientId', (WidgetTester tester) async {
+      expect(plugin.init(hostedDomain: ''), throwsAssertionError);
+    });
+
+    testWidgets("Init doesn't accept spaces in scopes",
+        (WidgetTester tester) async {
+      expect(
+          plugin.init(
+            hostedDomain: '',
+            clientId: '',
+            scopes: <String>['scope with spaces'],
+          ),
+          throwsAssertionError);
+    });
+
+    // See: https://github.com/flutter/flutter/issues/88084
+    testWidgets('Init passes plugin_name parameter with the expected value',
+        (WidgetTester tester) async {
+      await plugin.init(
+        hostedDomain: 'foo',
+        scopes: <String>['some', 'scope'],
+        clientId: '1234',
+      );
+
+      final Object? initParameters =
+          js_util.getProperty(html.window, 'gapi2.init.parameters');
+      expect(initParameters, isNotNull);
+
+      final Object? pluginNameParameter =
+          js_util.getProperty(initParameters!, 'plugin_name');
+      expect(pluginNameParameter, isA<String>());
+      expect(pluginNameParameter, 'dart-google_sign_in_web');
+    });
+
+    group('Successful .initialize, then', () {
+      setUp(() async {
+        await plugin.init(
+          hostedDomain: 'foo',
+          scopes: <String>['some', 'scope'],
+          clientId: '1234',
+        );
+        await plugin.initialized;
+      });
+
+      testWidgets('signInSilently', (WidgetTester tester) async {
+        final GoogleSignInUserData actualUser =
+            (await plugin.signInSilently())!;
+
+        expect(actualUser, expectedUserData);
+      });
+
+      testWidgets('signIn', (WidgetTester tester) async {
+        final GoogleSignInUserData actualUser = (await plugin.signIn())!;
+
+        expect(actualUser, expectedUserData);
+      });
+
+      testWidgets('getTokens', (WidgetTester tester) async {
+        final GoogleSignInTokenData actualToken =
+            await plugin.getTokens(email: expectedUserData.email);
+
+        expect(actualToken, expectedTokenData);
+      });
+
+      testWidgets('requestScopes', (WidgetTester tester) async {
+        final bool scopeGranted =
+            await plugin.requestScopes(<String>['newScope']);
+
+        expect(scopeGranted, isTrue);
+      });
+    });
+  });
+
+  group('auth2 Init successful, but exception on signIn() method', () {
+    setUp(() async {
+      // The pre-configured use case for the instances of the plugin in this test
+      gapiUrl = toBase64Url(gapi_mocks.auth2SignInError());
+      plugin = GoogleSignInPlugin();
+      await plugin.init(
+        hostedDomain: 'foo',
+        scopes: <String>['some', 'scope'],
+        clientId: '1234',
+      );
+      await plugin.initialized;
+    });
+
+    testWidgets('User aborts sign in flow, throws PlatformException',
+        (WidgetTester tester) async {
+      await expectLater(plugin.signIn(), throwsA(isA<PlatformException>()));
+    });
+
+    testWidgets('User aborts sign in flow, error code is forwarded from JS',
+        (WidgetTester tester) async {
+      try {
+        await plugin.signIn();
+        fail('plugin.signIn() should have thrown an exception!');
+      } catch (e) {
+        final String code = js_util.getProperty<String>(e, 'code');
+        expect(code, 'popup_closed_by_user');
+      }
+    });
+  });
+}
diff --git a/packages/google_sign_in/google_sign_in_web/example/integration_test/auth2_test.dart b/packages/google_sign_in/google_sign_in_web/example/integration_test/auth2_test.dart
index d8c7655..81d9f14 100644
--- a/packages/google_sign_in/google_sign_in_web/example/integration_test/auth2_test.dart
+++ b/packages/google_sign_in/google_sign_in_web/example/integration_test/auth2_test.dart
@@ -30,32 +30,31 @@
 
   late GoogleSignInPlugin plugin;
 
-  group('plugin.init() throws a catchable exception', () {
+  group('plugin.initWithParams() throws a catchable exception', () {
     setUp(() {
       // The pre-configured use case for the instances of the plugin in this test
       gapiUrl = toBase64Url(gapi_mocks.auth2InitError());
       plugin = GoogleSignInPlugin();
     });
 
-    testWidgets('init throws PlatformException', (WidgetTester tester) async {
+    testWidgets('throws PlatformException', (WidgetTester tester) async {
       await expectLater(
-          plugin.init(
+          plugin.initWithParams(const SignInInitParameters(
             hostedDomain: 'foo',
             scopes: <String>['some', 'scope'],
             clientId: '1234',
-          ),
+          )),
           throwsA(isA<PlatformException>()));
     });
 
-    testWidgets('init forwards error code from JS',
-        (WidgetTester tester) async {
+    testWidgets('forwards error code from JS', (WidgetTester tester) async {
       try {
-        await plugin.init(
+        await plugin.initWithParams(const SignInInitParameters(
           hostedDomain: 'foo',
           scopes: <String>['some', 'scope'],
           clientId: '1234',
-        );
-        fail('plugin.init should have thrown an exception!');
+        ));
+        fail('plugin.initWithParams should have thrown an exception!');
       } catch (e) {
         final String code = js_util.getProperty<String>(e, 'code');
         expect(code, 'idpiframe_initialization_failed');
@@ -63,16 +62,17 @@
     });
   });
 
-  group('other methods also throw catchable exceptions on init fail', () {
-    // This function ensures that init gets called, but for some reason, we
-    // ignored that it has thrown stuff...
+  group('other methods also throw catchable exceptions on initWithParams fail',
+      () {
+    // This function ensures that initWithParams gets called, but for some
+    // reason, we ignored that it has thrown stuff...
     Future<void> _discardInit() async {
       try {
-        await plugin.init(
+        await plugin.initWithParams(const SignInInitParameters(
           hostedDomain: 'foo',
           scopes: <String>['some', 'scope'],
           clientId: '1234',
-        );
+        ));
       } catch (e) {
         // Noop so we can call other stuff
       }
@@ -114,28 +114,40 @@
     });
 
     testWidgets('Init requires clientId', (WidgetTester tester) async {
-      expect(plugin.init(hostedDomain: ''), throwsAssertionError);
+      expect(
+          plugin.initWithParams(const SignInInitParameters(hostedDomain: '')),
+          throwsAssertionError);
+    });
+
+    testWidgets("Init doesn't accept serverClientId",
+        (WidgetTester tester) async {
+      expect(
+          plugin.initWithParams(const SignInInitParameters(
+            clientId: '',
+            serverClientId: '',
+          )),
+          throwsAssertionError);
     });
 
     testWidgets("Init doesn't accept spaces in scopes",
         (WidgetTester tester) async {
       expect(
-          plugin.init(
+          plugin.initWithParams(const SignInInitParameters(
             hostedDomain: '',
             clientId: '',
             scopes: <String>['scope with spaces'],
-          ),
+          )),
           throwsAssertionError);
     });
 
     // See: https://github.com/flutter/flutter/issues/88084
     testWidgets('Init passes plugin_name parameter with the expected value',
         (WidgetTester tester) async {
-      await plugin.init(
+      await plugin.initWithParams(const SignInInitParameters(
         hostedDomain: 'foo',
         scopes: <String>['some', 'scope'],
         clientId: '1234',
-      );
+      ));
 
       final Object? initParameters =
           js_util.getProperty(html.window, 'gapi2.init.parameters');
@@ -147,13 +159,13 @@
       expect(pluginNameParameter, 'dart-google_sign_in_web');
     });
 
-    group('Successful .init, then', () {
+    group('Successful .initWithParams, then', () {
       setUp(() async {
-        await plugin.init(
+        await plugin.initWithParams(const SignInInitParameters(
           hostedDomain: 'foo',
           scopes: <String>['some', 'scope'],
           clientId: '1234',
-        );
+        ));
         await plugin.initialized;
       });
 
@@ -191,11 +203,11 @@
       // The pre-configured use case for the instances of the plugin in this test
       gapiUrl = toBase64Url(gapi_mocks.auth2SignInError());
       plugin = GoogleSignInPlugin();
-      await plugin.init(
+      await plugin.initWithParams(const SignInInitParameters(
         hostedDomain: 'foo',
         scopes: <String>['some', 'scope'],
         clientId: '1234',
-      );
+      ));
       await plugin.initialized;
     });
 
diff --git a/packages/google_sign_in/google_sign_in_web/example/integration_test/gapi_load_legacy_init_test.dart b/packages/google_sign_in/google_sign_in_web/example/integration_test/gapi_load_legacy_init_test.dart
new file mode 100644
index 0000000..7bfef53
--- /dev/null
+++ b/packages/google_sign_in/google_sign_in_web/example/integration_test/gapi_load_legacy_init_test.dart
@@ -0,0 +1,51 @@
+// 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.
+
+// This file is a copy of `gapi_load_test.dart`, before it was migrated to the
+// new `initWithParams` method, and is kept to ensure test coverage of the
+// deprecated `init` method, until it is removed.
+
+import 'dart:html' as html;
+
+import 'package:flutter_test/flutter_test.dart';
+import 'package:google_sign_in_platform_interface/google_sign_in_platform_interface.dart';
+import 'package:google_sign_in_web/google_sign_in_web.dart';
+import 'package:integration_test/integration_test.dart';
+
+import 'gapi_mocks/gapi_mocks.dart' as gapi_mocks;
+import 'src/test_utils.dart';
+
+void main() {
+  IntegrationTestWidgetsFlutterBinding.ensureInitialized();
+
+  gapiUrl = toBase64Url(gapi_mocks.auth2InitSuccess(
+      GoogleSignInUserData(email: 'test@test.com', id: '1234')));
+
+  testWidgets('Plugin is initialized after GAPI fully loads and init is called',
+      (WidgetTester tester) async {
+    expect(
+      html.querySelector('script[src^="data:"]'),
+      isNull,
+      reason: 'Mock script not present before instantiating the plugin',
+    );
+    final GoogleSignInPlugin plugin = GoogleSignInPlugin();
+    expect(
+      html.querySelector('script[src^="data:"]'),
+      isNotNull,
+      reason: 'Mock script should be injected',
+    );
+    expect(() {
+      plugin.initialized;
+    }, throwsStateError,
+        reason:
+            'The plugin should throw if checking for `initialized` before calling .init');
+    await plugin.init(hostedDomain: '', clientId: '');
+    await plugin.initialized;
+    expect(
+      plugin.initialized,
+      completes,
+      reason: 'The plugin should complete the future once initialized.',
+    );
+  });
+}
diff --git a/packages/google_sign_in/google_sign_in_web/example/integration_test/gapi_load_test.dart b/packages/google_sign_in/google_sign_in_web/example/integration_test/gapi_load_test.dart
index 5da4228..fc753e2 100644
--- a/packages/google_sign_in/google_sign_in_web/example/integration_test/gapi_load_test.dart
+++ b/packages/google_sign_in/google_sign_in_web/example/integration_test/gapi_load_test.dart
@@ -34,9 +34,12 @@
     expect(() {
       plugin.initialized;
     }, throwsStateError,
-        reason:
-            'The plugin should throw if checking for `initialized` before calling .init');
-    await plugin.init(hostedDomain: '', clientId: '');
+        reason: 'The plugin should throw if checking for `initialized` before '
+            'calling .initWithParams');
+    await plugin.initWithParams(const SignInInitParameters(
+      hostedDomain: '',
+      clientId: '',
+    ));
     await plugin.initialized;
     expect(
       plugin.initialized,
diff --git a/packages/google_sign_in/google_sign_in_web/lib/google_sign_in_web.dart b/packages/google_sign_in/google_sign_in_web/lib/google_sign_in_web.dart
index ae6180d..c305cae 100644
--- a/packages/google_sign_in/google_sign_in_web/lib/google_sign_in_web.dart
+++ b/packages/google_sign_in/google_sign_in_web/lib/google_sign_in_web.dart
@@ -41,13 +41,15 @@
   late Future<void> _isAuthInitialized;
   bool _isInitCalled = false;
 
-  // This method throws if init hasn't been called at some point in the past.
-  // It is used by the [initialized] getter to ensure that users can't await
-  // on a Future that will never resolve.
+  // This method throws if init or initWithParams hasn't been called at some
+  // point in the past. It is used by the [initialized] getter to ensure that
+  // users can't await on a Future that will never resolve.
   void _assertIsInitCalled() {
     if (!_isInitCalled) {
       throw StateError(
-          'GoogleSignInPlugin::init() must be called before any other method in this plugin.');
+        'GoogleSignInPlugin::init() or GoogleSignInPlugin::initWithParams() '
+        'must be called before any other method in this plugin.',
+      );
     }
   }
 
@@ -71,16 +73,29 @@
     SignInOption signInOption = SignInOption.standard,
     String? hostedDomain,
     String? clientId,
-  }) async {
-    final String? appClientId = clientId ?? _autoDetectedClientId;
+  }) {
+    return initWithParams(SignInInitParameters(
+      scopes: scopes,
+      signInOption: signInOption,
+      hostedDomain: hostedDomain,
+      clientId: clientId,
+    ));
+  }
+
+  @override
+  Future<void> initWithParams(SignInInitParameters params) async {
+    final String? appClientId = params.clientId ?? _autoDetectedClientId;
     assert(
         appClientId != null,
         'ClientID not set. Either set it on a '
         '<meta name="google-signin-client_id" content="CLIENT_ID" /> tag,'
-        ' or pass clientId when calling init()');
+        ' or pass clientId when initializing GoogleSignIn');
+
+    assert(params.serverClientId == null,
+        'serverClientId is not supported on Web.');
 
     assert(
-        !scopes.any((String scope) => scope.contains(' ')),
+        !params.scopes.any((String scope) => scope.contains(' ')),
         "OAuth 2.0 Scopes for Google APIs can't contain spaces. "
         'Check https://developers.google.com/identity/protocols/googlescopes '
         'for a list of valid OAuth 2.0 scopes.');
@@ -88,9 +103,9 @@
     await _isGapiInitialized;
 
     final auth2.GoogleAuth auth = auth2.init(auth2.ClientConfig(
-      hosted_domain: hostedDomain,
+      hosted_domain: params.hostedDomain,
       // The js lib wants a space-separated list of values
-      scope: scopes.join(' '),
+      scope: params.scopes.join(' '),
       client_id: appClientId!,
       plugin_name: 'dart-google_sign_in_web',
     ));
diff --git a/packages/google_sign_in/google_sign_in_web/pubspec.yaml b/packages/google_sign_in/google_sign_in_web/pubspec.yaml
index 907cc90..1dedd6d 100644
--- a/packages/google_sign_in/google_sign_in_web/pubspec.yaml
+++ b/packages/google_sign_in/google_sign_in_web/pubspec.yaml
@@ -3,7 +3,7 @@
   for signing in with a Google account on Android, iOS and Web.
 repository: https://github.com/flutter/plugins/tree/main/packages/google_sign_in/google_sign_in_web
 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+google_sign_in%22
-version: 0.10.1+3
+version: 0.10.2
 
 environment:
   sdk: ">=2.12.0 <3.0.0"
@@ -22,7 +22,7 @@
     sdk: flutter
   flutter_web_plugins:
     sdk: flutter
-  google_sign_in_platform_interface: ^2.0.0
+  google_sign_in_platform_interface: ^2.2.0
   js: ^0.6.3
 
 dev_dependencies: