diff --git a/packages/google_sign_in/google_sign_in/CHANGELOG.md b/packages/google_sign_in/google_sign_in/CHANGELOG.md index 1f7f5b605bb1..3ea1a59c38d3 100644 --- a/packages/google_sign_in/google_sign_in/CHANGELOG.md +++ b/packages/google_sign_in/google_sign_in/CHANGELOG.md @@ -1,3 +1,7 @@ +## 4.0.14 + +* Port plugin code to use the federated Platform Interface, instead of a MethodChannel directly. + ## 4.0.13 * Fix `GoogleUserCircleAvatar` to handle new style profile image URLs. diff --git a/packages/google_sign_in/google_sign_in/lib/google_sign_in.dart b/packages/google_sign_in/google_sign_in/lib/google_sign_in.dart index f1e1db21801e..7556c1006c2f 100644 --- a/packages/google_sign_in/google_sign_in/lib/google_sign_in.dart +++ b/packages/google_sign_in/google_sign_in/lib/google_sign_in.dart @@ -5,38 +5,36 @@ import 'dart:async'; import 'dart:ui' show hashValues; -import 'package:flutter/services.dart' show MethodChannel, PlatformException; -import 'package:meta/meta.dart' show visibleForTesting; +import 'package:flutter/services.dart' show PlatformException; +import 'package:google_sign_in_platform_interface/google_sign_in_platform_interface.dart'; import 'src/common.dart'; export 'src/common.dart'; export 'widgets.dart'; -enum SignInOption { standard, games } - class GoogleSignInAuthentication { GoogleSignInAuthentication._(this._data); - final Map _data; + final GoogleSignInTokenData _data; /// An OpenID Connect ID token that identifies the user. - String get idToken => _data['idToken']; + String get idToken => _data.idToken; /// The OAuth2 access token to access Google services. - String get accessToken => _data['accessToken']; + String get accessToken => _data.accessToken; @override String toString() => 'GoogleSignInAuthentication:$_data'; } class GoogleSignInAccount implements GoogleIdentity { - GoogleSignInAccount._(this._googleSignIn, Map data) - : displayName = data['displayName'], - email = data['email'], - id = data['id'], - photoUrl = data['photoUrl'], - _idToken = data['idToken'] { + GoogleSignInAccount._(this._googleSignIn, GoogleSignInUserData data) + : displayName = data.displayName, + email = data.email, + id = data.id, + photoUrl = data.photoUrl, + _idToken = data.idToken { assert(id != null); } @@ -78,18 +76,16 @@ class GoogleSignInAccount implements GoogleIdentity { throw StateError('User is no longer signed in.'); } - final Map response = - await GoogleSignIn.channel.invokeMapMethod( - 'getTokens', - { - 'email': email, - 'shouldRecoverAuth': true, - }, + final GoogleSignInTokenData response = + await GoogleSignInPlatform.instance.getTokens( + email: email, + shouldRecoverAuth: true, ); + // On Android, there isn't an API for refreshing the idToken, so re-use // the one we obtained on login. - if (response['idToken'] == null) { - response['idToken'] = _idToken; + if (response.idToken == null) { + response.idToken = _idToken; } return GoogleSignInAuthentication._(response); } @@ -108,10 +104,7 @@ class GoogleSignInAccount implements GoogleIdentity { /// this method and grab `authHeaders` once again. Future clearAuthCache() async { final String token = (await authentication).accessToken; - await GoogleSignIn.channel.invokeMethod( - 'clearAuthCache', - {'token': token}, - ); + await GoogleSignInPlatform.instance.clearAuthCache(token: token); } @override @@ -146,7 +139,7 @@ class GoogleSignIn { /// Initializes global sign-in configuration settings. /// /// The [signInOption] determines the user experience. [SigninOption.games] - /// must not be used on iOS. + /// is only supported on Android. /// /// The list of [scopes] are OAuth scope codes to request when signing in. /// These scope codes will determine the level of data access that is granted @@ -157,18 +150,25 @@ class GoogleSignIn { /// The [hostedDomain] argument specifies a hosted domain restriction. By /// setting this, sign in will be restricted to accounts of the user in the /// specified domain. By default, the list of accounts will not be restricted. - GoogleSignIn({this.signInOption, this.scopes, this.hostedDomain}); + GoogleSignIn({ + this.signInOption = SignInOption.standard, + this.scopes = const [], + this.hostedDomain, + }); /// Factory for creating default sign in user experience. - factory GoogleSignIn.standard({List scopes, String hostedDomain}) { + factory GoogleSignIn.standard({ + List scopes = const [], + String hostedDomain, + }) { return GoogleSignIn( signInOption: SignInOption.standard, scopes: scopes, hostedDomain: hostedDomain); } - /// Factory for creating sign in suitable for games. This option must not be - /// used on iOS because the games API is not supported. + /// Factory for creating sign in suitable for games. This option is only + /// supported on Android. factory GoogleSignIn.games() { return GoogleSignIn(signInOption: SignInOption.games); } @@ -186,13 +186,8 @@ class GoogleSignIn { /// Error code indicating that attempt to sign in failed. static const String kSignInFailedError = 'sign_in_failed'; - /// The [MethodChannel] over which this class communicates. - @visibleForTesting - static const MethodChannel channel = - MethodChannel('plugins.flutter.io/google_sign_in'); - - /// Option to determine the sign in user experience. [SignInOption.games] must - /// not be used on iOS. + /// Option to determine the sign in user experience. [SignInOption.games] is + /// only supported on Android. final SignInOption signInOption; /// The list of [scopes] are OAuth scope codes requested when signing in. @@ -211,12 +206,12 @@ class GoogleSignIn { // Future that completes when we've finished calling `init` on the native side Future _initialization; - Future _callMethod(String method) async { + Future _callMethod(Function method) async { await _ensureInitialized(); - final Map response = - await channel.invokeMapMethod(method); - return _setCurrentUser(response != null && response.isNotEmpty + final dynamic response = await method(); + + return _setCurrentUser(response != null && response is GoogleSignInUserData ? GoogleSignInAccount._(this, response) : null); } @@ -230,16 +225,14 @@ class GoogleSignIn { } Future _ensureInitialized() { - return _initialization ??= - channel.invokeMethod('init', { - 'signInOption': (signInOption ?? SignInOption.standard).toString(), - 'scopes': scopes ?? [], - 'hostedDomain': hostedDomain, - }) - ..catchError((dynamic _) { - // Invalidate initialization if it errored out. - _initialization = null; - }); + return _initialization ??= GoogleSignInPlatform.instance.init( + signInOption: signInOption, + scopes: scopes, + hostedDomain: hostedDomain, + )..catchError((dynamic _) { + // Invalidate initialization if it errors out. + _initialization = null; + }); } /// The most recently scheduled method call. @@ -251,6 +244,7 @@ class GoogleSignIn { final Completer completer = Completer(); future.whenComplete(completer.complete).catchError((dynamic _) { // Ignore if previous call completed with an error. + // TODO: Should we log errors here, if debug or similar? }); return completer.future; } @@ -259,26 +253,29 @@ class GoogleSignIn { /// /// At most one in flight call is allowed to prevent concurrent (out of order) /// updates to [currentUser] and [onCurrentUserChanged]. - Future _addMethodCall(String method) async { + /// + /// The optional, named parameter [canSkipCall] lets the plugin know that the + /// method call may be skipped, if there's already [_currentUser] information. + /// This is used from the [signIn] and [signInSilently] methods. + Future _addMethodCall( + Function method, { + bool canSkipCall = false, + }) async { Future response; if (_lastMethodCall == null) { response = _callMethod(method); } else { response = _lastMethodCall.then((_) { // If after the last completed call `currentUser` is not `null` and requested - // method is a sign in method, re-use the same authenticated user + // method can be skipped (`canSkipCall`), re-use the same authenticated user // instead of making extra call to the native side. - const List kSignInMethods = [ - 'signIn', - 'signInSilently' - ]; - if (kSignInMethods.contains(method) && _currentUser != null) { + if (canSkipCall && _currentUser != null) { return _currentUser; - } else { - return _callMethod(method); } + return _callMethod(method); }); } + // Add the current response to the currently running Promise of all pending responses _lastMethodCall = _waitFor(response); return response; } @@ -303,10 +300,12 @@ class GoogleSignIn { /// returned Future completes with [PlatformException] whose `code` can be /// either [kSignInRequiredError] (when there is no authenticated user) or /// [kSignInFailedError] (when an unknown error occurred). - Future signInSilently( - {bool suppressErrors = true}) async { + Future signInSilently({ + bool suppressErrors = true, + }) async { try { - return await _addMethodCall('signInSilently'); + return await _addMethodCall(GoogleSignInPlatform.instance.signInSilently, + canSkipCall: true); } catch (_) { if (suppressErrors) { return null; @@ -319,7 +318,7 @@ class GoogleSignIn { /// Returns a future that resolves to whether a user is currently signed in. Future isSignedIn() async { await _ensureInitialized(); - return await channel.invokeMethod('isSignedIn'); + return GoogleSignInPlatform.instance.isSignedIn(); } /// Starts the interactive sign-in process. @@ -333,16 +332,19 @@ class GoogleSignIn { /// /// Re-authentication can be triggered only after [signOut] or [disconnect]. Future signIn() { - final Future result = _addMethodCall('signIn'); + final Future result = + _addMethodCall(GoogleSignInPlatform.instance.signIn, canSkipCall: true); bool isCanceled(dynamic error) => error is PlatformException && error.code == kSignInCanceledError; return result.catchError((dynamic _) => null, test: isCanceled); } /// Marks current user as being in the signed out state. - Future signOut() => _addMethodCall('signOut'); + Future signOut() => + _addMethodCall(GoogleSignInPlatform.instance.signOut); /// Disconnects the current user from the app and revokes previous /// authentication. - Future disconnect() => _addMethodCall('disconnect'); + Future disconnect() => + _addMethodCall(GoogleSignInPlatform.instance.disconnect); } diff --git a/packages/google_sign_in/google_sign_in/pubspec.yaml b/packages/google_sign_in/google_sign_in/pubspec.yaml index 833ef6733d2d..30e2b88016bf 100644 --- a/packages/google_sign_in/google_sign_in/pubspec.yaml +++ b/packages/google_sign_in/google_sign_in/pubspec.yaml @@ -3,7 +3,7 @@ description: Flutter plugin for Google Sign-In, a secure authentication system for signing in with a Google account on Android and iOS. author: Flutter Team homepage: https://github.com/flutter/plugins/tree/master/packages/google_sign_in/google_sign_in -version: 4.0.13 +version: 4.0.14 flutter: plugin: @@ -12,6 +12,7 @@ flutter: pluginClass: GoogleSignInPlugin dependencies: + google_sign_in_platform_interface: ^1.0.0 flutter: sdk: flutter meta: ^1.0.4 diff --git a/packages/google_sign_in/google_sign_in/test/google_sign_in_test.dart b/packages/google_sign_in/google_sign_in/test/google_sign_in_test.dart index 108edf9c892b..a85fb0f27e42 100755 --- a/packages/google_sign_in/google_sign_in/test/google_sign_in_test.dart +++ b/packages/google_sign_in/google_sign_in/test/google_sign_in_test.dart @@ -6,6 +6,7 @@ import 'dart:async'; 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/google_sign_in.dart'; import 'package:google_sign_in/testing.dart'; @@ -391,7 +392,9 @@ void main() { GoogleSignIn googleSignIn; setUp(() { - GoogleSignIn.channel.setMockMethodCallHandler( + final MethodChannelGoogleSignIn platformInstance = + GoogleSignInPlatform.instance; + platformInstance.channel.setMockMethodCallHandler( (FakeSignInBackend()..user = kUserData).handleMethodCall); googleSignIn = GoogleSignIn(); }); diff --git a/script/build_all_plugins_app.sh b/script/build_all_plugins_app.sh index 5ba4b6e5ce02..cdcb02a02140 100755 --- a/script/build_all_plugins_app.sh +++ b/script/build_all_plugins_app.sh @@ -10,7 +10,15 @@ readonly REPO_DIR="$(dirname "$SCRIPT_DIR")" source "$SCRIPT_DIR/common.sh" check_changed_packages > /dev/null -(cd "$REPO_DIR" && pub global run flutter_plugin_tools all-plugins-app --exclude instrumentation_adapter,url_launcher_platform_interface) +readonly EXCLUDED_PLUGINS_LIST=( + "instrumentation_adapter" + "url_launcher_platform_interface" + "google_sign_in_platform_interface" +) +# Comma-separated string of the list above +readonly EXCLUDED=$(IFS=, ; echo "${EXCLUDED_PLUGINS_LIST[*]}") + +(cd "$REPO_DIR" && pub global run flutter_plugin_tools all-plugins-app --exclude $EXCLUDED) function error() { echo "$@" 1>&2