Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.

Commit 986d573

Browse files
authored
Add platform_interface package
Add the google_sign_in_platform_interface package, which contains: * The interface that all federated implementations of google_sign_in must extend (`GoogleSignInPlatform`) * The default, platform-message based implementation (`MethodChannelGoogleSignIn`) * Tests
2 parents f0650ed + c55cc30 commit 986d573

10 files changed

Lines changed: 501 additions & 0 deletions

File tree

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
## 1.0.0
2+
3+
* Initial release.
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright 2017 The Chromium Authors. All rights reserved.
2+
//
3+
// Redistribution and use in source and binary forms, with or without
4+
// modification, are permitted provided that the following conditions are
5+
// met:
6+
//
7+
// * Redistributions of source code must retain the above copyright
8+
// notice, this list of conditions and the following disclaimer.
9+
// * Redistributions in binary form must reproduce the above
10+
// copyright notice, this list of conditions and the following disclaimer
11+
// in the documentation and/or other materials provided with the
12+
// distribution.
13+
// * Neither the name of Google Inc. nor the names of its
14+
// contributors may be used to endorse or promote products derived from
15+
// this software without specific prior written permission.
16+
//
17+
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18+
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19+
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20+
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21+
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22+
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23+
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24+
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25+
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26+
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27+
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# google_sign_in_platform_interface
2+
3+
A common platform interface for the [`google_sign_in`][1] plugin.
4+
5+
This interface allows platform-specific implementations of the `google_sign_in`
6+
plugin, as well as the plugin itself, to ensure they are supporting the
7+
same interface.
8+
9+
# Usage
10+
11+
To implement a new platform-specific implementation of `google_sign_in`, extend
12+
[`GoogleSignInPlatform`][2] with an implementation that performs the
13+
platform-specific behavior, and when you register your plugin, set the default
14+
`GoogleSignInPlatform` by calling
15+
`GoogleSignInPlatform.instance = MyPlatformGoogleSignIn()`.
16+
17+
# Note on breaking changes
18+
19+
Strongly prefer non-breaking changes (such as adding a method to the interface)
20+
over breaking changes for this package.
21+
22+
See https://flutter.dev/go/platform-interface-breaking-changes for a discussion
23+
on why a less-clean interface is preferable to a breaking change.
24+
25+
[1]: ../google_sign_in
26+
[2]: lib/google_sign_in_platform_interface.dart
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
// Copyright 2017 The Chromium 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+
import 'package:meta/meta.dart' show required, visibleForTesting;
7+
import 'src/method_channel_google_sign_in.dart';
8+
import 'src/types.dart';
9+
10+
export 'src/method_channel_google_sign_in.dart';
11+
export 'src/types.dart';
12+
13+
/// The interface that implementations of google_sign_in must implement.
14+
///
15+
/// Platform implementations that live in a separate package should extend this
16+
/// class rather than implement it as `google_sign_in` does not consider newly
17+
/// added methods to be breaking changes. Extending this class (using `extends`)
18+
/// ensures that the subclass will get the default implementation, while
19+
/// platform implementations that `implements` this interface will be broken by
20+
/// newly added [GoogleSignInPlatform] methods.
21+
abstract class GoogleSignInPlatform {
22+
/// Only mock implementations should set this to `true`.
23+
///
24+
/// Mockito mocks implement this class with `implements` which is forbidden
25+
/// (see class docs). This property provides a backdoor for mocks to skip the
26+
/// verification that the class isn't implemented with `implements`.
27+
@visibleForTesting
28+
bool get isMock => false;
29+
30+
/// The default instance of [GoogleSignInPlatform] to use.
31+
///
32+
/// Platform-specific plugins should override this with their own
33+
/// platform-specific class that extends [GoogleSignInPlatform] when they
34+
/// register themselves.
35+
///
36+
/// Defaults to [MethodChannelGoogleSignIn].
37+
static GoogleSignInPlatform get instance => _instance;
38+
39+
static GoogleSignInPlatform _instance = MethodChannelGoogleSignIn();
40+
41+
// TODO(amirh): Extract common platform interface logic.
42+
// https://github.com/flutter/flutter/issues/43368
43+
static set instance(GoogleSignInPlatform instance) {
44+
if (!instance.isMock) {
45+
try {
46+
instance._verifyProvidesDefaultImplementations();
47+
} on NoSuchMethodError catch (_) {
48+
throw AssertionError(
49+
'Platform interfaces must not be implemented with `implements`');
50+
}
51+
}
52+
_instance = instance;
53+
}
54+
55+
/// This method ensures that [GoogleSignInPlatform] isn't implemented with `implements`.
56+
///
57+
/// See class docs for more details on why using `implements` to implement
58+
/// [GoogleSignInPlatform] is forbidden.
59+
///
60+
/// This private method is called by the [instance] setter, which should fail
61+
/// if the provided instance is a class implemented with `implements`.
62+
void _verifyProvidesDefaultImplementations() {}
63+
64+
/// Initializes the plugin. You must call this method before calling other methods.
65+
/// See: https://developers.google.com/identity/sign-in/web/reference#gapiauth2initparams
66+
Future<void> init(
67+
{@required String hostedDomain,
68+
List<String> scopes,
69+
SignInOption signInOption,
70+
String clientId}) async {
71+
throw UnimplementedError('init() has not been implemented.');
72+
}
73+
74+
/// Attempts to reuse pre-existing credentials to sign in again, without user interaction.
75+
Future<GoogleSignInUserData> signInSilently() async {
76+
throw UnimplementedError('signInSilently() has not been implemented.');
77+
}
78+
79+
/// Signs in the user with the options specified to [init].
80+
Future<GoogleSignInUserData> signIn() async {
81+
throw UnimplementedError('signIn() has not been implemented.');
82+
}
83+
84+
/// Returns the Tokens used to authenticate other API calls.
85+
Future<GoogleSignInTokenData> getTokens(
86+
{@required String email, bool shouldRecoverAuth}) async {
87+
throw UnimplementedError('getTokens() has not been implemented.');
88+
}
89+
90+
/// Signs out the current account from the application.
91+
Future<void> signOut() async {
92+
throw UnimplementedError('signOut() has not been implemented.');
93+
}
94+
95+
/// Revokes all of the scopes that the user granted.
96+
Future<void> disconnect() async {
97+
throw UnimplementedError('disconnect() has not been implemented.');
98+
}
99+
100+
/// Returns whether the current user is currently signed in.
101+
Future<bool> isSignedIn() async {
102+
throw UnimplementedError('isSignedIn() has not been implemented.');
103+
}
104+
105+
/// Clears any cached information that the plugin may be holding on to.
106+
Future<void> clearAuthCache({@required String token}) async {
107+
throw UnimplementedError('clearAuthCache() has not been implemented.');
108+
}
109+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// Copyright 2017 The Chromium 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/services.dart';
8+
import 'package:meta/meta.dart' show required, visibleForTesting;
9+
10+
import '../google_sign_in_platform_interface.dart';
11+
import 'types.dart';
12+
import 'utils.dart';
13+
14+
/// An implementation of [GoogleSignInPlatform] that uses method channels.
15+
class MethodChannelGoogleSignIn extends GoogleSignInPlatform {
16+
@visibleForTesting
17+
MethodChannel channel = MethodChannel('plugins.flutter.io/google_sign_in');
18+
19+
@override
20+
Future<void> init(
21+
{@required String hostedDomain,
22+
List<String> scopes = const <String>[],
23+
SignInOption signInOption = SignInOption.standard,
24+
String clientId}) {
25+
return channel.invokeMethod<void>('init', <String, dynamic>{
26+
'signInOption': signInOption.toString(),
27+
'scopes': scopes,
28+
'hostedDomain': hostedDomain,
29+
});
30+
}
31+
32+
@override
33+
Future<GoogleSignInUserData> signInSilently() {
34+
return channel
35+
.invokeMapMethod<String, dynamic>('signInSilently')
36+
.then(getUserDataFromMap);
37+
}
38+
39+
@override
40+
Future<GoogleSignInUserData> signIn() {
41+
return channel
42+
.invokeMapMethod<String, dynamic>('signIn')
43+
.then(getUserDataFromMap);
44+
}
45+
46+
@override
47+
Future<GoogleSignInTokenData> getTokens(
48+
{String email, bool shouldRecoverAuth = true}) {
49+
return channel
50+
.invokeMapMethod<String, dynamic>('getTokens', <String, dynamic>{
51+
'email': email,
52+
'shouldRecoverAuth': shouldRecoverAuth,
53+
}).then(getTokenDataFromMap);
54+
}
55+
56+
@override
57+
Future<void> signOut() {
58+
return channel.invokeMapMethod<String, dynamic>('signOut');
59+
}
60+
61+
@override
62+
Future<void> disconnect() {
63+
return channel.invokeMapMethod<String, dynamic>('disconnect');
64+
}
65+
66+
@override
67+
Future<bool> isSignedIn() {
68+
return channel.invokeMethod<bool>('isSignedIn');
69+
}
70+
71+
@override
72+
Future<void> clearAuthCache({String token}) {
73+
return channel.invokeMethod<void>(
74+
'clearAuthCache',
75+
<String, String>{'token': token},
76+
);
77+
}
78+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import 'package:quiver_hashcode/hashcode.dart';
2+
3+
enum SignInOption { standard, games }
4+
5+
class GoogleSignInUserData {
6+
GoogleSignInUserData(
7+
{this.displayName, this.email, this.id, this.photoUrl, this.idToken});
8+
String displayName;
9+
String email;
10+
String id;
11+
String photoUrl;
12+
String idToken;
13+
14+
@override
15+
int get hashCode =>
16+
hashObjects(<String>[displayName, email, id, photoUrl, idToken]);
17+
18+
@override
19+
bool operator ==(dynamic other) {
20+
if (identical(this, other)) return true;
21+
if (other is! GoogleSignInUserData) return false;
22+
final GoogleSignInUserData otherUserData = other;
23+
return otherUserData.displayName == displayName &&
24+
otherUserData.email == email &&
25+
otherUserData.id == id &&
26+
otherUserData.photoUrl == photoUrl &&
27+
otherUserData.idToken == idToken;
28+
}
29+
}
30+
31+
class GoogleSignInTokenData {
32+
GoogleSignInTokenData({this.idToken, this.accessToken});
33+
String idToken;
34+
String accessToken;
35+
36+
@override
37+
int get hashCode => hash2(idToken, accessToken);
38+
39+
@override
40+
bool operator ==(dynamic other) {
41+
if (identical(this, other)) return true;
42+
if (other is! GoogleSignInTokenData) return false;
43+
final GoogleSignInTokenData otherTokenData = other;
44+
return otherTokenData.idToken == idToken &&
45+
otherTokenData.accessToken == accessToken;
46+
}
47+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import '../google_sign_in_platform_interface.dart';
2+
3+
/// Converts user data coming from native code into the proper platform interface type.
4+
GoogleSignInUserData getUserDataFromMap(Map<String, dynamic> data) {
5+
if (data == null) {
6+
return null;
7+
}
8+
return GoogleSignInUserData(
9+
displayName: data['displayName'],
10+
email: data['email'],
11+
id: data['id'],
12+
photoUrl: data['photoUrl'],
13+
idToken: data['idToken']);
14+
}
15+
16+
/// Converts token data coming from native code into the proper platform interface type.
17+
GoogleSignInTokenData getTokenDataFromMap(Map<String, dynamic> data) {
18+
if (data == null) {
19+
return null;
20+
}
21+
return GoogleSignInTokenData(
22+
idToken: data['idToken'],
23+
accessToken: data['accessToken'],
24+
);
25+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
name: google_sign_in_platform_interface
2+
description: A common platform interface for the google_sign_in plugin.
3+
author: Flutter Team <[email protected]>
4+
homepage: https://github.com/flutter/plugins/tree/master/packages/google_sign_in/google_sign_in_platform_interface
5+
# NOTE: We strongly prefer non-breaking changes, even at the expense of a
6+
# less-clean API. See https://flutter.dev/go/platform-interface-breaking-changes
7+
version: 1.0.0
8+
9+
dependencies:
10+
flutter:
11+
sdk: flutter
12+
meta: ^1.0.5
13+
quiver_hashcode: ^2.0.0
14+
15+
dev_dependencies:
16+
flutter_test:
17+
sdk: flutter
18+
mockito: ^4.1.1
19+
20+
environment:
21+
sdk: ">=2.0.0-dev.28.0 <3.0.0"
22+
flutter: ">=1.9.1+hotfix.4 <2.0.0"
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Copyright 2019 The Chromium 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+
import 'package:flutter_test/flutter_test.dart';
7+
import 'package:mockito/mockito.dart';
8+
9+
void main() {
10+
group('$GoogleSignInPlatform', () {
11+
test('$MethodChannelGoogleSignIn is the default instance', () {
12+
expect(GoogleSignInPlatform.instance, isA<MethodChannelGoogleSignIn>());
13+
});
14+
15+
test('Cannot be implemented with `implements`', () {
16+
expect(() {
17+
GoogleSignInPlatform.instance = ImplementsGoogleSignInPlatform();
18+
}, throwsAssertionError);
19+
});
20+
21+
test('Can be extended', () {
22+
GoogleSignInPlatform.instance = ExtendsGoogleSignInPlatform();
23+
});
24+
25+
test('Can be mocked with `implements`', () {
26+
final ImplementsGoogleSignInPlatform mock =
27+
ImplementsGoogleSignInPlatform();
28+
when(mock.isMock).thenReturn(true);
29+
GoogleSignInPlatform.instance = mock;
30+
});
31+
});
32+
}
33+
34+
class ImplementsGoogleSignInPlatform extends Mock
35+
implements GoogleSignInPlatform {}
36+
37+
class ExtendsGoogleSignInPlatform extends GoogleSignInPlatform {}

0 commit comments

Comments
 (0)