[google_sign_in] Port plugin to use the federated Platform Interface (#2266)
* Port google_sign_in.dart to use the federated Platform Interface, instead of MethodChannels.
* Ignore google_sign_in_platform_interface on all-plugins-app call.
* Refactor endlessly growing --exclude param value into a string that
gets generated for a list of plugin names.
diff --git a/packages/google_sign_in/google_sign_in/CHANGELOG.md b/packages/google_sign_in/google_sign_in/CHANGELOG.md
index 1f7f5b6..3ea1a59 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 f1e1db2..7556c10 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<String, dynamic> _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<String, dynamic> 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 @@
throw StateError('User is no longer signed in.');
}
- final Map<String, dynamic> response =
- await GoogleSignIn.channel.invokeMapMethod<String, dynamic>(
- 'getTokens',
- <String, dynamic>{
- '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 @@
/// this method and grab `authHeaders` once again.
Future<void> clearAuthCache() async {
final String token = (await authentication).accessToken;
- await GoogleSignIn.channel.invokeMethod<void>(
- 'clearAuthCache',
- <String, dynamic>{'token': token},
- );
+ await GoogleSignInPlatform.instance.clearAuthCache(token: token);
}
@override
@@ -146,7 +139,7 @@
/// 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 @@
/// 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 <String>[],
+ this.hostedDomain,
+ });
/// Factory for creating default sign in user experience.
- factory GoogleSignIn.standard({List<String> scopes, String hostedDomain}) {
+ factory GoogleSignIn.standard({
+ List<String> scopes = const <String>[],
+ 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 @@
/// 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 @@
// Future that completes when we've finished calling `init` on the native side
Future<void> _initialization;
- Future<GoogleSignInAccount> _callMethod(String method) async {
+ Future<GoogleSignInAccount> _callMethod(Function method) async {
await _ensureInitialized();
- final Map<String, dynamic> response =
- await channel.invokeMapMethod<String, dynamic>(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 @@
}
Future<void> _ensureInitialized() {
- return _initialization ??=
- channel.invokeMethod<void>('init', <String, dynamic>{
- 'signInOption': (signInOption ?? SignInOption.standard).toString(),
- 'scopes': scopes ?? <String>[],
- '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 @@
final Completer<void> completer = Completer<void>();
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 @@
///
/// At most one in flight call is allowed to prevent concurrent (out of order)
/// updates to [currentUser] and [onCurrentUserChanged].
- Future<GoogleSignInAccount> _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<GoogleSignInAccount> _addMethodCall(
+ Function method, {
+ bool canSkipCall = false,
+ }) async {
Future<GoogleSignInAccount> 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<String> kSignInMethods = <String>[
- '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 @@
/// 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<GoogleSignInAccount> signInSilently(
- {bool suppressErrors = true}) async {
+ Future<GoogleSignInAccount> 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 @@
/// Returns a future that resolves to whether a user is currently signed in.
Future<bool> isSignedIn() async {
await _ensureInitialized();
- return await channel.invokeMethod<bool>('isSignedIn');
+ return GoogleSignInPlatform.instance.isSignedIn();
}
/// Starts the interactive sign-in process.
@@ -333,16 +332,19 @@
///
/// Re-authentication can be triggered only after [signOut] or [disconnect].
Future<GoogleSignInAccount> signIn() {
- final Future<GoogleSignInAccount> result = _addMethodCall('signIn');
+ final Future<GoogleSignInAccount> 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<GoogleSignInAccount> signOut() => _addMethodCall('signOut');
+ Future<GoogleSignInAccount> signOut() =>
+ _addMethodCall(GoogleSignInPlatform.instance.signOut);
/// Disconnects the current user from the app and revokes previous
/// authentication.
- Future<GoogleSignInAccount> disconnect() => _addMethodCall('disconnect');
+ Future<GoogleSignInAccount> 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 833ef67..30e2b88 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 @@
for signing in with a Google account on Android and iOS.
author: Flutter Team <flutter-dev@googlegroups.com>
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 @@
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 108edf9..a85fb0f 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 '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 @@
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 5ba4b6e..cdcb02a 100755
--- a/script/build_all_plugins_app.sh
+++ b/script/build_all_plugins_app.sh
@@ -10,7 +10,15 @@
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