Skip to content

Commit a9d5a93

Browse files
[google_sign_in] Switch to internal method channels (flutter#5396)
1 parent 3e43f59 commit a9d5a93

File tree

15 files changed

+595
-66
lines changed

15 files changed

+595
-66
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 5.2.6
2+
3+
* Switches to an internal method channel, rather than the default.
4+
15
## 5.2.5
26

37
* Splits from `video_player` as a federated implementation.

packages/google_sign_in/google_sign_in_android/android/src/main/java/io/flutter/plugins/googlesignin/GoogleSignInPlugin.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444

4545
/** Google sign-in plugin for Flutter. */
4646
public class GoogleSignInPlugin implements MethodCallHandler, FlutterPlugin, ActivityAware {
47-
private static final String CHANNEL_NAME = "plugins.flutter.io/google_sign_in";
47+
private static final String CHANNEL_NAME = "plugins.flutter.io/google_sign_in_android";
4848

4949
private static final String METHOD_INIT = "init";
5050
private static final String METHOD_SIGN_IN_SILENTLY = "signInSilently";

packages/google_sign_in/google_sign_in_android/example/integration_test/google_sign_in_test.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,12 @@ void main() {
1313
final GoogleSignInPlatform signIn = GoogleSignInPlatform.instance;
1414
expect(signIn, isNotNull);
1515
});
16+
17+
testWidgets('Method channel handler is present', (WidgetTester tester) async {
18+
// isSignedIn can be called without initialization, so use it to validate
19+
// that the native method handler is present (e.g., that the channel name
20+
// is correct).
21+
final GoogleSignInPlatform signIn = GoogleSignInPlatform.instance;
22+
await expectLater(signIn.isSignedIn(), completes);
23+
});
1624
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'dart:async';
6+
7+
import 'package:flutter/foundation.dart' show visibleForTesting;
8+
import 'package:flutter/services.dart';
9+
import 'package:google_sign_in_platform_interface/google_sign_in_platform_interface.dart';
10+
11+
import 'src/utils.dart';
12+
13+
/// Android implementation of [GoogleSignInPlatform].
14+
class GoogleSignInAndroid extends GoogleSignInPlatform {
15+
/// This is only exposed for test purposes. It shouldn't be used by clients of
16+
/// the plugin as it may break or change at any time.
17+
@visibleForTesting
18+
MethodChannel channel =
19+
const MethodChannel('plugins.flutter.io/google_sign_in_android');
20+
21+
/// Registers this class as the default instance of [GoogleSignInPlatform].
22+
static void registerWith() {
23+
GoogleSignInPlatform.instance = GoogleSignInAndroid();
24+
}
25+
26+
@override
27+
Future<void> init({
28+
List<String> scopes = const <String>[],
29+
SignInOption signInOption = SignInOption.standard,
30+
String? hostedDomain,
31+
String? clientId,
32+
}) {
33+
return channel.invokeMethod<void>('init', <String, dynamic>{
34+
'signInOption': signInOption.toString(),
35+
'scopes': scopes,
36+
'hostedDomain': hostedDomain,
37+
'clientId': clientId,
38+
});
39+
}
40+
41+
@override
42+
Future<GoogleSignInUserData?> signInSilently() {
43+
return channel
44+
.invokeMapMethod<String, dynamic>('signInSilently')
45+
.then(getUserDataFromMap);
46+
}
47+
48+
@override
49+
Future<GoogleSignInUserData?> signIn() {
50+
return channel
51+
.invokeMapMethod<String, dynamic>('signIn')
52+
.then(getUserDataFromMap);
53+
}
54+
55+
@override
56+
Future<GoogleSignInTokenData> getTokens(
57+
{required String email, bool? shouldRecoverAuth = true}) {
58+
return channel
59+
.invokeMapMethod<String, dynamic>('getTokens', <String, dynamic>{
60+
'email': email,
61+
'shouldRecoverAuth': shouldRecoverAuth,
62+
}).then((Map<String, dynamic>? result) => getTokenDataFromMap(result!));
63+
}
64+
65+
@override
66+
Future<void> signOut() {
67+
return channel.invokeMapMethod<String, dynamic>('signOut');
68+
}
69+
70+
@override
71+
Future<void> disconnect() {
72+
return channel.invokeMapMethod<String, dynamic>('disconnect');
73+
}
74+
75+
@override
76+
Future<bool> isSignedIn() async {
77+
return (await channel.invokeMethod<bool>('isSignedIn'))!;
78+
}
79+
80+
@override
81+
Future<void> clearAuthCache({String? token}) {
82+
return channel.invokeMethod<void>(
83+
'clearAuthCache',
84+
<String, String?>{'token': token},
85+
);
86+
}
87+
88+
@override
89+
Future<bool> requestScopes(List<String> scopes) async {
90+
return (await channel.invokeMethod<bool>(
91+
'requestScopes',
92+
<String, List<String>>{'scopes': scopes},
93+
))!;
94+
}
95+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:google_sign_in_platform_interface/google_sign_in_platform_interface.dart';
6+
7+
/// Converts user data coming from native code into the proper platform interface type.
8+
GoogleSignInUserData? getUserDataFromMap(Map<String, dynamic>? data) {
9+
if (data == null) {
10+
return null;
11+
}
12+
return GoogleSignInUserData(
13+
email: data['email']! as String,
14+
id: data['id']! as String,
15+
displayName: data['displayName'] as String?,
16+
photoUrl: data['photoUrl'] as String?,
17+
idToken: data['idToken'] as String?,
18+
serverAuthCode: data['serverAuthCode'] as String?);
19+
}
20+
21+
/// Converts token data coming from native code into the proper platform interface type.
22+
GoogleSignInTokenData getTokenDataFromMap(Map<String, dynamic> data) {
23+
return GoogleSignInTokenData(
24+
idToken: data['idToken'] as String?,
25+
accessToken: data['accessToken'] as String?,
26+
serverAuthCode: data['serverAuthCode'] as String?,
27+
);
28+
}

packages/google_sign_in/google_sign_in_android/pubspec.yaml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,18 @@ name: google_sign_in_android
22
description: Android implementation of the google_sign_in plugin.
33
repository: https://github.com/flutter/plugins/tree/main/packages/google_sign_in/google_sign_in_android
44
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+google_sign_in%22
5-
version: 5.2.5
5+
version: 5.2.6
66

77
environment:
88
sdk: ">=2.14.0 <3.0.0"
9-
flutter: ">=2.5.0"
9+
flutter: ">=2.8.0"
1010

1111
flutter:
1212
plugin:
1313
implements: google_sign_in
1414
platforms:
1515
android:
16+
dartPluginClass: GoogleSignInAndroid
1617
package: io.flutter.plugins.googlesignin
1718
pluginClass: GoogleSignInPlugin
1819

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:flutter/services.dart';
6+
import 'package:flutter_test/flutter_test.dart';
7+
import 'package:google_sign_in_android/google_sign_in_android.dart';
8+
import 'package:google_sign_in_android/src/utils.dart';
9+
import 'package:google_sign_in_platform_interface/google_sign_in_platform_interface.dart';
10+
11+
const Map<String, String> kUserData = <String, String>{
12+
'email': 'john.doe@gmail.com',
13+
'id': '8162538176523816253123',
14+
'photoUrl': 'https://lh5.googleusercontent.com/photo.jpg',
15+
'displayName': 'John Doe',
16+
'idToken': '123',
17+
'serverAuthCode': '789',
18+
};
19+
20+
const Map<dynamic, dynamic> kTokenData = <String, dynamic>{
21+
'idToken': '123',
22+
'accessToken': '456',
23+
'serverAuthCode': '789',
24+
};
25+
26+
const Map<String, dynamic> kDefaultResponses = <String, dynamic>{
27+
'init': null,
28+
'signInSilently': kUserData,
29+
'signIn': kUserData,
30+
'signOut': null,
31+
'disconnect': null,
32+
'isSignedIn': true,
33+
'getTokens': kTokenData,
34+
'requestScopes': true,
35+
};
36+
37+
final GoogleSignInUserData? kUser = getUserDataFromMap(kUserData);
38+
final GoogleSignInTokenData kToken =
39+
getTokenDataFromMap(kTokenData as Map<String, dynamic>);
40+
41+
void main() {
42+
TestWidgetsFlutterBinding.ensureInitialized();
43+
44+
final GoogleSignInAndroid googleSignIn = GoogleSignInAndroid();
45+
final MethodChannel channel = googleSignIn.channel;
46+
47+
final List<MethodCall> log = <MethodCall>[];
48+
late Map<String, dynamic>
49+
responses; // Some tests mutate some kDefaultResponses
50+
51+
setUp(() {
52+
responses = Map<String, dynamic>.from(kDefaultResponses);
53+
channel.setMockMethodCallHandler((MethodCall methodCall) {
54+
log.add(methodCall);
55+
final dynamic response = responses[methodCall.method];
56+
if (response != null && response is Exception) {
57+
return Future<dynamic>.error('$response');
58+
}
59+
return Future<dynamic>.value(response);
60+
});
61+
log.clear();
62+
});
63+
64+
test('registered instance', () {
65+
GoogleSignInAndroid.registerWith();
66+
expect(GoogleSignInPlatform.instance, isA<GoogleSignInAndroid>());
67+
});
68+
69+
test('signInSilently transforms platform data to GoogleSignInUserData',
70+
() async {
71+
final dynamic response = await googleSignIn.signInSilently();
72+
expect(response, kUser);
73+
});
74+
test('signInSilently Exceptions -> throws', () async {
75+
responses['signInSilently'] = Exception('Not a user');
76+
expect(googleSignIn.signInSilently(),
77+
throwsA(isInstanceOf<PlatformException>()));
78+
});
79+
80+
test('signIn transforms platform data to GoogleSignInUserData', () async {
81+
final dynamic response = await googleSignIn.signIn();
82+
expect(response, kUser);
83+
});
84+
test('signIn Exceptions -> throws', () async {
85+
responses['signIn'] = Exception('Not a user');
86+
expect(googleSignIn.signIn(), throwsA(isInstanceOf<PlatformException>()));
87+
});
88+
89+
test('getTokens transforms platform data to GoogleSignInTokenData', () async {
90+
final dynamic response = await googleSignIn.getTokens(
91+
email: 'example@example.com', shouldRecoverAuth: false);
92+
expect(response, kToken);
93+
expect(
94+
log[0],
95+
isMethodCall('getTokens', arguments: <String, dynamic>{
96+
'email': 'example@example.com',
97+
'shouldRecoverAuth': false,
98+
}));
99+
});
100+
101+
test('Other functions pass through arguments to the channel', () async {
102+
final Map<Function, Matcher> tests = <Function, Matcher>{
103+
() {
104+
googleSignIn.init(
105+
hostedDomain: 'example.com',
106+
scopes: <String>['two', 'scopes'],
107+
signInOption: SignInOption.games,
108+
clientId: 'fakeClientId');
109+
}: isMethodCall('init', arguments: <String, dynamic>{
110+
'hostedDomain': 'example.com',
111+
'scopes': <String>['two', 'scopes'],
112+
'signInOption': 'SignInOption.games',
113+
'clientId': 'fakeClientId',
114+
}),
115+
() {
116+
googleSignIn.getTokens(
117+
email: 'example@example.com', shouldRecoverAuth: false);
118+
}: isMethodCall('getTokens', arguments: <String, dynamic>{
119+
'email': 'example@example.com',
120+
'shouldRecoverAuth': false,
121+
}),
122+
() {
123+
googleSignIn.clearAuthCache(token: 'abc');
124+
}: isMethodCall('clearAuthCache', arguments: <String, dynamic>{
125+
'token': 'abc',
126+
}),
127+
() {
128+
googleSignIn.requestScopes(<String>['newScope', 'anotherScope']);
129+
}: isMethodCall('requestScopes', arguments: <String, dynamic>{
130+
'scopes': <String>['newScope', 'anotherScope'],
131+
}),
132+
googleSignIn.signOut: isMethodCall('signOut', arguments: null),
133+
googleSignIn.disconnect: isMethodCall('disconnect', arguments: null),
134+
googleSignIn.isSignedIn: isMethodCall('isSignedIn', arguments: null),
135+
};
136+
137+
for (final Function f in tests.keys) {
138+
f();
139+
}
140+
141+
expect(log, tests.values);
142+
});
143+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 5.2.6
2+
3+
* Switches to an internal method channel, rather than the default.
4+
15
## 5.2.5
26

37
* Splits from `video_player` as a federated implementation.

packages/google_sign_in/google_sign_in_ios/example/integration_test/google_sign_in_test.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,12 @@ void main() {
1313
final GoogleSignInPlatform signIn = GoogleSignInPlatform.instance;
1414
expect(signIn, isNotNull);
1515
});
16+
17+
testWidgets('Method channel handler is present', (WidgetTester tester) async {
18+
// isSignedIn can be called without initialization, so use it to validate
19+
// that the native method handler is present (e.g., that the channel name
20+
// is correct).
21+
final GoogleSignInPlatform signIn = GoogleSignInPlatform.instance;
22+
await expectLater(signIn.isSignedIn(), completes);
23+
});
1624
}

packages/google_sign_in/google_sign_in_ios/example/ios/RunnerTests/GoogleSignInTests.m

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -73,35 +73,8 @@ - (void)testDisconnect {
7373
OCMVerify([self.mockSignIn disconnect]);
7474
}
7575

76-
- (void)testClearAuthCache {
77-
FlutterMethodCall *methodCall = [FlutterMethodCall methodCallWithMethodName:@"clearAuthCache"
78-
arguments:nil];
79-
80-
XCTestExpectation *expectation = [self expectationWithDescription:@"expect result returns true"];
81-
[self.plugin handleMethodCall:methodCall
82-
result:^(id result) {
83-
XCTAssertNil(result);
84-
[expectation fulfill];
85-
}];
86-
[self waitForExpectationsWithTimeout:5.0 handler:nil];
87-
}
88-
8976
#pragma mark - Init
9077

91-
- (void)testInitGamesSignInUnsupported {
92-
FlutterMethodCall *methodCall =
93-
[FlutterMethodCall methodCallWithMethodName:@"init"
94-
arguments:@{@"signInOption" : @"SignInOption.games"}];
95-
96-
XCTestExpectation *expectation = [self expectationWithDescription:@"expect result returns true"];
97-
[self.plugin handleMethodCall:methodCall
98-
result:^(FlutterError *result) {
99-
XCTAssertEqualObjects(result.code, @"unsupported-options");
100-
[expectation fulfill];
101-
}];
102-
[self waitForExpectationsWithTimeout:5.0 handler:nil];
103-
}
104-
10578
- (void)testInitGoogleServiceInfoPlist {
10679
FlutterMethodCall *methodCall = [FlutterMethodCall
10780
methodCallWithMethodName:@"init"

0 commit comments

Comments
 (0)