[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: