Skip to content

Commit 4d36dc7

Browse files
mehmetfPark Sung Min
authored andcommitted
[google_sign_in] Fix chained async methods in error handling zones (flutter#2059)
1 parent 8c843a9 commit 4d36dc7

File tree

4 files changed

+86
-67
lines changed

4 files changed

+86
-67
lines changed

packages/google_sign_in/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## 4.0.8
2+
3+
* Get rid of `MethodCompleter` and serialize async actions using chained futures.
4+
This prevents a bug when sign in methods are being used in error handling zones.
5+
16
## 4.0.7
27

38
* Switch from using `api` to `implementation` for dependency on `play-services-auth`,

packages/google_sign_in/lib/google_sign_in.dart

100755100644
Lines changed: 53 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -230,50 +230,57 @@ class GoogleSignIn {
230230
}
231231

232232
Future<void> _ensureInitialized() {
233-
if (_initialization == null) {
234-
_initialization = channel.invokeMethod<void>('init', <String, dynamic>{
235-
'signInOption': (signInOption ?? SignInOption.standard).toString(),
236-
'scopes': scopes ?? <String>[],
237-
'hostedDomain': hostedDomain,
238-
})
239-
..catchError((dynamic _) {
240-
// Invalidate initialization if it errored out.
241-
_initialization = null;
242-
});
243-
}
244-
return _initialization;
233+
return _initialization ??=
234+
channel.invokeMethod<void>('init', <String, dynamic>{
235+
'signInOption': (signInOption ?? SignInOption.standard).toString(),
236+
'scopes': scopes ?? <String>[],
237+
'hostedDomain': hostedDomain,
238+
})
239+
..catchError((dynamic _) {
240+
// Invalidate initialization if it errored out.
241+
_initialization = null;
242+
});
245243
}
246244

247-
/// Keeps track of the most recently scheduled method call.
248-
_MethodCompleter _lastMethodCompleter;
245+
/// The most recently scheduled method call.
246+
Future<void> _lastMethodCall;
247+
248+
/// Returns a [Future] that completes with a success after [future], whether
249+
/// it completed with a value or an error.
250+
static Future<void> _waitFor(Future<void> future) {
251+
final Completer<void> completer = Completer<void>();
252+
future.whenComplete(completer.complete).catchError((dynamic _) {
253+
// Ignore if previous call completed with an error.
254+
});
255+
return completer.future;
256+
}
249257

250258
/// Adds call to [method] in a queue for execution.
251259
///
252260
/// At most one in flight call is allowed to prevent concurrent (out of order)
253261
/// updates to [currentUser] and [onCurrentUserChanged].
254-
Future<GoogleSignInAccount> _addMethodCall(String method) {
255-
if (_lastMethodCompleter == null) {
256-
_lastMethodCompleter = _MethodCompleter(method)
257-
..complete(_callMethod(method));
258-
return _lastMethodCompleter.future;
262+
Future<GoogleSignInAccount> _addMethodCall(String method) async {
263+
Future<GoogleSignInAccount> response;
264+
if (_lastMethodCall == null) {
265+
response = _callMethod(method);
266+
} else {
267+
response = _lastMethodCall.then((_) {
268+
// If after the last completed call `currentUser` is not `null` and requested
269+
// method is a sign in method, re-use the same authenticated user
270+
// instead of making extra call to the native side.
271+
const List<String> kSignInMethods = <String>[
272+
'signIn',
273+
'signInSilently'
274+
];
275+
if (kSignInMethods.contains(method) && _currentUser != null) {
276+
return _currentUser;
277+
} else {
278+
return _callMethod(method);
279+
}
280+
});
259281
}
260-
261-
final _MethodCompleter completer = _MethodCompleter(method);
262-
_lastMethodCompleter.future.whenComplete(() {
263-
// If after the last completed call currentUser is not null and requested
264-
// method is a sign in method, re-use the same authenticated user
265-
// instead of making extra call to the native side.
266-
const List<String> kSignInMethods = <String>['signIn', 'signInSilently'];
267-
if (kSignInMethods.contains(method) && _currentUser != null) {
268-
completer.complete(_currentUser);
269-
} else {
270-
completer.complete(_callMethod(method));
271-
}
272-
}).catchError((dynamic _) {
273-
// Ignore if previous call completed with an error.
274-
});
275-
_lastMethodCompleter = completer;
276-
return _lastMethodCompleter.future;
282+
_lastMethodCall = _waitFor(response);
283+
return response;
277284
}
278285

279286
/// The currently signed in account, or null if the user is signed out.
@@ -296,12 +303,17 @@ class GoogleSignIn {
296303
/// returned Future completes with [PlatformException] whose `code` can be
297304
/// either [kSignInRequiredError] (when there is no authenticated user) or
298305
/// [kSignInFailedError] (when an unknown error occurred).
299-
Future<GoogleSignInAccount> signInSilently({bool suppressErrors = true}) {
300-
final Future<GoogleSignInAccount> result = _addMethodCall('signInSilently');
301-
if (suppressErrors) {
302-
return result.catchError((dynamic _) => null);
306+
Future<GoogleSignInAccount> signInSilently(
307+
{bool suppressErrors = true}) async {
308+
try {
309+
return await _addMethodCall('signInSilently');
310+
} catch (_) {
311+
if (suppressErrors) {
312+
return null;
313+
} else {
314+
rethrow;
315+
}
303316
}
304-
return result;
305317
}
306318

307319
/// Returns a future that resolves to whether a user is currently signed in.
@@ -334,26 +346,3 @@ class GoogleSignIn {
334346
/// authentication.
335347
Future<GoogleSignInAccount> disconnect() => _addMethodCall('disconnect');
336348
}
337-
338-
class _MethodCompleter {
339-
_MethodCompleter(this.method);
340-
341-
final String method;
342-
final Completer<GoogleSignInAccount> _completer =
343-
Completer<GoogleSignInAccount>();
344-
345-
Future<void> complete(FutureOr<GoogleSignInAccount> value) async {
346-
if (value is Future<GoogleSignInAccount>) {
347-
try {
348-
_completer.complete(await value);
349-
} catch (e, stacktrace) {
350-
_completer.completeError(e, stacktrace);
351-
}
352-
} else {
353-
_completer.complete(value);
354-
}
355-
}
356-
357-
bool get isCompleted => _completer.isCompleted;
358-
Future<GoogleSignInAccount> get future => _completer.future;
359-
}

packages/google_sign_in/pubspec.yaml

100755100644
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ description: Flutter plugin for Google Sign-In, a secure authentication system
33
for signing in with a Google account on Android and iOS.
44
author: Flutter Team <flutter-dev@googlegroups.com>
55
homepage: https://github.com/flutter/plugins/tree/master/packages/google_sign_in
6-
version: 4.0.7
6+
version: 4.0.8
77

88
flutter:
99
plugin:

packages/google_sign_in/test/google_sign_in_test.dart

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,11 @@ void main() {
4545
responses = Map<String, dynamic>.from(kDefaultResponses);
4646
channel.setMockMethodCallHandler((MethodCall methodCall) {
4747
log.add(methodCall);
48-
return Future<dynamic>.value(responses[methodCall.method]);
48+
final dynamic response = responses[methodCall.method];
49+
if (response != null && response is Exception) {
50+
return Future<dynamic>.error('$response');
51+
}
52+
return Future<dynamic>.value(response);
4953
});
5054
googleSignIn = GoogleSignIn();
5155
log.clear();
@@ -142,6 +146,27 @@ void main() {
142146
]);
143147
});
144148

149+
test('signIn works even if a previous call throws error in other zone',
150+
() async {
151+
responses['signInSilently'] = Exception('Not a user');
152+
await runZoned(() async {
153+
expect(await googleSignIn.signInSilently(), isNull);
154+
}, onError: (dynamic e, dynamic st) {});
155+
expect(await googleSignIn.signIn(), isNotNull);
156+
expect(
157+
log,
158+
<Matcher>[
159+
isMethodCall('init', arguments: <String, dynamic>{
160+
'signInOption': 'SignInOption.standard',
161+
'scopes': <String>[],
162+
'hostedDomain': null,
163+
}),
164+
isMethodCall('signInSilently', arguments: null),
165+
isMethodCall('signIn', arguments: null),
166+
],
167+
);
168+
});
169+
145170
test('concurrent calls of the same method trigger sign in once', () async {
146171
final List<Future<GoogleSignInAccount>> futures =
147172
<Future<GoogleSignInAccount>>[
@@ -170,7 +195,7 @@ void main() {
170195
});
171196

172197
test('can sign in after previously failed attempt', () async {
173-
responses['signInSilently'] = <String, dynamic>{'error': 'Not a user'};
198+
responses['signInSilently'] = Exception('Not a user');
174199
expect(await googleSignIn.signInSilently(), isNull);
175200
expect(await googleSignIn.signIn(), isNotNull);
176201
expect(

0 commit comments

Comments
 (0)