Skip to content

[google_sign_in] Redesign API for current identity SDKs #9267

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
432a627
Initial PoC using CredentialManager
stuartmorgan-g Feb 12, 2025
df1cf87
Pathify everything for local development
stuartmorgan-g Mar 20, 2025
6a1ec7c
Remove method channel implementation
stuartmorgan-g Mar 20, 2025
49342c5
First pass rework of interface, impls, and app-facing API
stuartmorgan-g Mar 20, 2025
eba7a8b
Add nonce support
stuartmorgan-g May 2, 2025
f3d472f
Initial README updates and migration guide
stuartmorgan-g May 2, 2025
728a4f9
Rework Android calls to match recommended practice
stuartmorgan-g May 5, 2025
c165834
Synthesize sign-out event for disconnect
stuartmorgan-g May 5, 2025
dba0f3e
platform interface tests
stuartmorgan-g May 5, 2025
83c7b1d
Simplify Android Pigeon API
stuartmorgan-g May 6, 2025
82c0530
Android Dart unit tests
stuartmorgan-g May 8, 2025
4123b18
iOS Dart unit tests
stuartmorgan-g May 9, 2025
165fb78
Web Dart 'unit' tests, and fix lightweight auth return
stuartmorgan-g May 9, 2025
eda66bb
Update example apps
stuartmorgan-g May 9, 2025
21635a0
Update native unit tests, fix an Obj-C selector name
stuartmorgan-g May 13, 2025
39b7505
Update to recent 1.5 release
stuartmorgan-g May 13, 2025
07fe543
Android native unit tests
stuartmorgan-g May 14, 2025
e6c9f62
README, CHANGELOG, and version updates
stuartmorgan-g May 16, 2025
90a63aa
App-facing Dart tests
stuartmorgan-g May 16, 2025
816f614
Revert unrelated Xcode project changes
stuartmorgan-g May 16, 2025
0d46beb
Analysis fix
stuartmorgan-g May 16, 2025
a0cf078
Revert extension package changes
stuartmorgan-g May 16, 2025
0a134ec
Fix Obj-C warnings
stuartmorgan-g May 16, 2025
1b0d4d1
Missing license
stuartmorgan-g May 16, 2025
8f57604
Analyze fix
stuartmorgan-g May 19, 2025
e786998
Update excerpting
stuartmorgan-g May 19, 2025
78602e8
Merge branch 'main' into google-sign-in-authn-authz-redesign
stuartmorgan-g May 23, 2025
8783b72
Merge branch 'main' into google-sign-in-authn-authz-redesign
stuartmorgan-g May 27, 2025
7140fd0
Remove email scope
stuartmorgan-g May 27, 2025
5a587b4
Web README additions
stuartmorgan-g May 27, 2025
5f78e10
Update stream error handling, update main example
stuartmorgan-g May 27, 2025
a6036b8
Fix lightweight auth null return, and add missing tests
stuartmorgan-g May 28, 2025
9c9923c
Re-add web asserts for scope spaces
stuartmorgan-g May 28, 2025
b44ff39
Remove unused People code
stuartmorgan-g May 28, 2025
8640443
Restructure options slightly
stuartmorgan-g May 28, 2025
86a7a1d
Add TODO issue link
stuartmorgan-g May 28, 2025
4fb7fea
Add activity lifecycle tests
stuartmorgan-g May 28, 2025
563680c
Add authorize param tests
stuartmorgan-g May 28, 2025
dab117f
Test safety check, better README
stuartmorgan-g May 29, 2025
81f59f7
Analyzer
stuartmorgan-g May 29, 2025
6b76be0
Java autoformat
stuartmorgan-g May 29, 2025
9817325
excerpt update
stuartmorgan-g May 29, 2025
20a8fc5
Add stack traces to stream errors
stuartmorgan-g May 29, 2025
d6bd8bc
Handle emulators without sign-in support
stuartmorgan-g May 29, 2025
6766860
Add another missing stack
stuartmorgan-g May 29, 2025
8ad0051
Rework the stream tests to make wasm happy
stuartmorgan-g May 29, 2025
98e55b2
Merge branch 'main' into google-sign-in-authn-authz-redesign
stuartmorgan-g Jun 3, 2025
f36a6c8
Minor iOS review fixes
stuartmorgan-g Jun 4, 2025
2823a54
Add authorizationRequiresUserInteraction
stuartmorgan-g Jun 4, 2025
012a6d7
final variable in example
stuartmorgan-g Jun 5, 2025
86e9d00
Review feedback
stuartmorgan-g Jun 11, 2025
fd6a5f8
Merge branch 'main' into google-sign-in-authn-authz-redesign
stuartmorgan-g Jun 23, 2025
9c96d9d
Unwind platform interface overrides
stuartmorgan-g Jun 23, 2025
3c86d9f
Merge branch 'main' into google-sign-in-authn-authz-redesign
stuartmorgan-g Jun 24, 2025
82e3503
Dependency version bumps
stuartmorgan-g Jun 24, 2025
47b86c8
Update exclusion list
stuartmorgan-g Jun 24, 2025
276e1c6
Minor grammar fix
stuartmorgan-g Jun 24, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions packages/google_sign_in/google_sign_in/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
## NEXT

* Updates README to indicate that Andoid SDK <21 is no longer supported.
## 7.0.0

* **BREAKING CHANGE**: Many APIs have changed or been replaced to reflect the
current APIs and best practices of the underlying platform SDKs. For full
details, see the README and migration guide, but notable highlights include:
* The `GoogleSignIn` instance is now a singleton.
* Clients must call and await the new `initialize` method before calling any
other methods on the instance.
* Authentication and authorization are now separate steps.
* Access tokens and server auth codes are obtained via separate calls.

## 6.3.0

63 changes: 63 additions & 0 deletions packages/google_sign_in/google_sign_in/MIGRATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Migrating from `google_sign_in` 6.x to 7.x

The API of `google_sign_in` 6.x and earlier was designed for the Google Sign-In
SDK, which has been deprecated on both Android and Web, and replaced with new
SDKs that have significantly different structures. As a result, the
`google_sign_in` API surface has changed significantly. Notable differences
include:
* `GoogleSignIn` is now a singleton, which is obtained via
`GoogleSignIn.instance`. In practice, creating multiple `GoogleSignIn`
instances in 6.x would not work correctly, so this just enforces an existing
restriction.
* There is now an explicit `initialize` step that must be called exactly once,
before any other methods. On some platforms the future will complete almost
immediately, but on others (for example, web) it may take some time.
* The plugin no longer tracks a single "current" signed in user. Instead,
applications that assume a single signed in user should track this at the
application level using the `authenticationEvents` stream.
* Authentication (signing in) and authorization (allowing access to user data
in the form of scopes) are now separate steps. Recommended practice is to
authenticate as soon as it makes sense for a user to potentially be signed in,
but to delay authorization until the point where the data will actually be
used.
* In applications where these steps should happen at the same time, you can
pass a `scopeHint` during the authentication step. On platforms that support
it this allows for a combined authentication and authorization UI flow.
Not all platforms allow combining these flows, so your application should be
prepared to trigger a separate authorization prompt if necessary.
* Authorization is further separated into client and server authorization.
Applications that need a `serverAuthCode` must now call a separate method,
`authorizeServer`, to obtain that code.
* Client authorization is handled via two new methods:
* `authorizationForScopes`, which returns an access token if the requested
scopes are already authorized, or null if not, and
* `authorizeScopes`, which requests that the user authorize the scopes, and
is expected to show UI.

Clients should generally attempt to get tokens via `authorizationForScopes`,
and if they are unable to do so, show some UI to request authoriaztion that
calls `authorizeScopes`. This is similar to the previously web-only flow
of calling `canAccessScopes` and then calling `addScopes` if necessary.
* `signInSilently` has been replaced with `attemptLightweightAuthentication`.
The intended usage is essentially the same, but the change reflects that it
is no longer guaranteed to be silent. For example, as of the publishing of
7.0, on web this may show a floating sign-in card, and on Android it may show
an account selection sheet.
* This new method is no longer guaranteed to return a future. This allows
clients to distinguish, at runtime:
* platforms where a definitive "signed in" or "not signed in" response
can be returned quickly, and thus `await`-ing completion is reasonable,
in which case a `Future` is returned, and
* platforms (such as web) where it could take an arbitrary amount of time,
in which case no `Future` is returned, and clients should assume a
non-signed-in state until/unless a sign-in event is eventually posted to
the `authenticationEvents` stream.
* `authenticate` replaces the authentication portion of `signIn` on platforms
that support it (see below).
* The new `supportsAuthenticate` method allows clients to determine at runtime
whether the `authenticate` method is supported, as some platforms do not allow
custom UI to trigger explicit authentication. These platforms instead provide
some other platform-specific way of triggering authentication. As of
publishing, the only platform that does not support `authenticate` is web,
where `google_sign_in_web`'s `renderButton` is used to create a sign-in
button.
225 changes: 94 additions & 131 deletions packages/google_sign_in/google_sign_in/README.md
Original file line number Diff line number Diff line change
@@ -6,182 +6,145 @@ A Flutter plugin for [Google Sign In](https://developers.google.com/identity/).
|-------------|---------|-------|--------|-----|
| **Support** | SDK 21+ | 12.0+ | 10.15+ | Any |

## Platform integration
## Setup

### Android integration

To access Google Sign-In, you'll need to make sure to
[register your application](https://firebase.google.com/docs/android/setup).

You don't need to include the google-services.json file in your app unless you
are using Google services that require it. You do need to enable the OAuth APIs
that you want, using the
[Google Cloud Platform API manager](https://console.developers.google.com/). For
example, if you want to mimic the behavior of the Google Sign-In sample app,
you'll need to enable the
[Google People API](https://developers.google.com/people/).

Make sure you've filled out all required fields in the console for
[OAuth consent screen](https://console.developers.google.com/apis/credentials/consent).
Otherwise, you may encounter `APIException` errors.

### iOS integration

Please see [instructions on integrating Google Sign-In for iOS](https://pub.dev/packages/google_sign_in_ios#ios-integration).

#### iOS additional requirement

Note that according to
https://developer.apple.com/sign-in-with-apple/get-started, starting June 30,
2020, apps that use login services must also offer a "Sign in with Apple" option
when submitting to the Apple App Store.

Consider also using an Apple sign in plugin from pub.dev.

The Flutter Favorite
[sign_in_with_apple](https://pub.dev/packages/sign_in_with_apple) plugin could
be an option.

### macOS integration

Please see [instructions on integrating Google Sign-In for macOS](https://pub.dev/packages/google_sign_in_ios#macos-setup).

### Web integration
### Import the package

The new SDK used by the web has fully separated Authentication from Authorization,
so `signIn` and `signInSilently` no longer authorize OAuth `scopes`.
To use this plugin, follow the
[plugin installation instructions](https://pub.dev/packages/google_sign_in/install),
then follow the platform integration steps below for all platforms you support.

Flutter apps must be able to detect what scopes have been granted by their users,
and if the grants are still valid.
### Platform integration

Read below about **Working with scopes, and incremental authorization** for
general information about changes that may be needed on an app, and for more
specific web integration details, see the
[`google_sign_in_web` package](https://pub.dev/packages/google_sign_in_web).
* **Android**: Please see [the `google_sign_in_android` README](https://pub.dev/packages/google_sign_in_android#integration).
* **iOS**: Please see [the `google_sign_in_ios` README](https://pub.dev/packages/google_sign_in_ios#ios-integration).
* **macOS**: Please see [the `google_sign_in_ios` README](https://pub.dev/packages/google_sign_in_ios#macos-integration) (which also supports macOS).
* **Web**: Please see [the `google_sign_in_web` README](https://pub.dev/packages/google_sign_in_web#integration).

## Usage

### Import the package
### Initialization and authentication

To use this plugin, follow the
[plugin installation instructions](https://pub.dev/packages/google_sign_in/install).
Initialize the `GoogleSignIn` instance, and (optionally) start the lightweight
authentication process:

### Use the plugin

Initialize `GoogleSignIn` with the scopes you want:

<?code-excerpt "example/lib/main.dart (Initialize)"?>
<?code-excerpt "example/lib/main.dart (Setup)"?>
```dart
const List<String> scopes = <String>[
'email',
'https://www.googleapis.com/auth/contacts.readonly',
];
GoogleSignIn _googleSignIn = GoogleSignIn(
// Optional clientId
// clientId: 'your-client_id.apps.googleusercontent.com',
scopes: scopes,
);
final GoogleSignIn signIn = GoogleSignIn.instance;
unawaited(signIn
.initialize(clientId: clientId, serverClientId: serverClientId)
.then((_) {
signIn.authenticationEvents
.listen(_handleAuthenticationEvent)
.onError(_handleAuthenticationError);
/// This example always uses the stream-based approach to determining
/// which UI state to show, rather than using the future returned here,
/// if any, to conditionally skip directly to the signed-in state.
signIn.attemptLightweightAuthentication();
}));
```

[Full list of available scopes](https://developers.google.com/identity/protocols/googlescopes).

You can now use the `GoogleSignIn` class to authenticate in your Dart code, e.g.
If the user isn't signed in by the lightweight method, you can show UI to
start a sign-in flow. This uses `authenticate` on platforms that return true
for `supportsAuthenticate`, otherwise applications should fall back to a
platform-specific approach. For instance, user-initiated sign in on web must
use a button rendered by the sign in SDK, rather than application-provided
UI:

<?code-excerpt "example/lib/main.dart (SignIn)"?>
<?code-excerpt "example/lib/main.dart (ExplicitSignIn)"?>
```dart
Future<void> _handleSignIn() async {
try {
await _googleSignIn.signIn();
} catch (error) {
print(error);
}
}
if (GoogleSignIn.instance.supportsAuthenticate())
ElevatedButton(
onPressed: () async {
try {
await GoogleSignIn.instance.authenticate();
} catch (e) {
// ···
}
},
child: const Text('SIGN IN'),
)
else ...<Widget>[
if (kIsWeb)
web.renderButton()
// ···
]
```

In the web, you should use the **Google Sign In button** (and not the `signIn` method)
to guarantee that your user authentication contains a valid `idToken`.

For more details, take a look at the
[`google_sign_in_web` package](https://pub.dev/packages/google_sign_in_web).

## Working with scopes, and incremental authorization.

If your app supports both mobile and web, read this section!
## Authorization

### Checking if scopes have been granted

Users may (or may *not*) grant all the scopes that an application requests at
Sign In. In fact, in the web, no scopes are granted by `signIn`, `silentSignIn`
or the `renderButton` widget anymore.

Applications must be able to:

* Detect if the authenticated user has authorized the scopes they need.
* Determine if the scopes that were granted a few minutes ago are still valid.

There's a new method that enables the checks above, `canAccessScopes`:
If the user has previously authorized the scopes required by your application,
you can silently request an access token for those scopes:

<?code-excerpt "example/lib/main.dart (CanAccessScopes)"?>
<?code-excerpt "example/lib/main.dart (CheckAuthorization)" plaster="none"?>
```dart
// In mobile, being authenticated means being authorized...
bool isAuthorized = account != null;
// However, on web...
if (kIsWeb && account != null) {
isAuthorized = await _googleSignIn.canAccessScopes(scopes);
}
const List<String> scopes = <String>[
'https://www.googleapis.com/auth/contacts.readonly',
];
final GoogleSignInAccount? user = // ...
final GoogleSignInClientAuthorization? authorization =
await user?.authorizationClient.authorizationForScopes(scopes);
```

_(Only implemented in the web platform, from version 6.1.0 of this package)_
[Full list of available scopes](https://developers.google.com/identity/protocols/googlescopes).

### Requesting more scopes when needed

If an app determines that the user hasn't granted the scopes it requires, it
should initiate an Authorization request. (Remember that in the web platform,
this request **must be initiated from an user interaction**, like a button press).
should initiate an Authorization request. On platforms where
`authorizationRequiresUserInteraction()` returns true,
this request **must be initiated from a user interaction** like a button press.

<?code-excerpt "example/lib/main.dart (RequestScopes)" plaster="none"?>
<?code-excerpt "example/lib/main.dart (RequestScopes)"?>
```dart
Future<void> _handleAuthorizeScopes() async {
final bool isAuthorized = await _googleSignIn.requestScopes(scopes);
if (isAuthorized) {
unawaited(_handleGetContact(_currentUser!));
}
final GoogleSignInClientAuthorization authorization =
await user.authorizationClient.authorizeScopes(scopes);
```

The `requestScopes` returns a `boolean` value that is `true` if the user has
granted all the requested scopes or `false` otherwise.

Once your app determines that the current user `isAuthorized` to access the
services for which you need `scopes`, it can proceed normally.

### Authorization expiration

In the web, **the `accessToken` is no longer refreshed**. It expires after 3600
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Upon re-reading this... is this paragraph is a little bit incomplete? IIRC The authentication token (idToken) is also not refreshed, so both authorization and authentication end up expiring, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks that way. I can adjust the README accordingly, but maybe you just didn't mention it because normally (IIUC) clients don't need to keep using ID tokens they way they do access tokens.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a similar note in the authentication section, but pointing out that for authentication generally you should only need to use idToken when it's first received.

seconds (one hour), so your app needs to be able to handle failed REST requests,
and update its UI to prompt the user for a new Authorization round.

This can be done by combining the error responses from your REST requests with
the `canAccessScopes` and `requestScopes` methods described above.
the authorization methods described above.

For more details, take a look at the
[`google_sign_in_web` package](https://pub.dev/packages/google_sign_in_web).

### Does an app always need to check `canAccessScopes`?
### Requesting a server auth code

The new web SDK implicitly grant access to the `email`, `profile` and `openid`
scopes when users complete the sign-in process (either via the One Tap UX or the
Google Sign In button).
If your application needs to access user data from a backend server, you can
request a server auth code to send to the server:

If an app only needs an `idToken`, or only requests permissions to any/all of
the three scopes mentioned above
([OpenID Connect scopes](https://developers.google.com/identity/protocols/oauth2/scopes#openid-connect)),
it won't need to implement any additional scope handling.
<?code-excerpt "example/lib/main.dart (RequestServerAuth)"?>
```dart
final GoogleSignInServerAuthorization? serverAuth =
await user.authorizationClient.authorizeServer(scopes);
```

Server auth codes are not always available on all platforms. For instance, on
some platforms they may only be returned when a user initially signs in, and
not for subsequent authentications via the lightweight process. If you
need a server auth code you should request it as soon as possible after initial
sign-in, and manage server tokens for that user entirely on the server side
unless the signed in user changes.

If an app needs any scope other than `email`, `profile` and `openid`, it **must**
implement a more complete scope handling, as described above.
On platforms where `authorizationRequiresUserInteraction()` returns true,
this request **must be initiated from a user interaction** like a button press.

## Example

Find the example wiring in the
[Google sign-in example application](https://github.com/flutter/packages/blob/main/packages/google_sign_in/google_sign_in/example/lib/main.dart).
The
[Google Sign-In example application](https://github.com/flutter/packages/blob/main/packages/google_sign_in/google_sign_in/example/lib/main.dart) demonstrates one approach to using this
package to sign a user in and authorize access to specific user data.

## Migration from pre-7.0 versions

If you used version 6.x or earlier of `google_sign_in`, see
[the migration guide](https://github.com/flutter/packages/blob/main/packages/google_sign_in/google_sign_in/MIGRATION.md)
for more information about the changes.
Original file line number Diff line number Diff line change
@@ -10,7 +10,9 @@ void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();

testWidgets('Can initialize the plugin', (WidgetTester tester) async {
final GoogleSignIn signIn = GoogleSignIn();
final GoogleSignIn signIn = GoogleSignIn.instance;
expect(signIn, isNotNull);

await signIn.initialize();
});
}
330 changes: 214 additions & 116 deletions packages/google_sign_in/google_sign_in/example/lib/main.dart
Original file line number Diff line number Diff line change
@@ -12,21 +12,22 @@ import 'package:flutter/material.dart';
import 'package:google_sign_in/google_sign_in.dart';
import 'package:http/http.dart' as http;

import 'src/sign_in_button.dart';
import 'src/web_wrapper.dart' as web;

/// To run this example, replace this value with your client ID, and/or
/// update the relevant configuration files, as described in the README.
String? clientId;

/// To run this example, replace this value with your server client ID, and/or
/// update the relevant configuration files, as described in the README.
String? serverClientId;

/// The scopes required by this application.
// #docregion Initialize
// #docregion CheckAuthorization
const List<String> scopes = <String>[
'email',
'https://www.googleapis.com/auth/contacts.readonly',
];

GoogleSignIn _googleSignIn = GoogleSignIn(
// Optional clientId
// clientId: 'your-client_id.apps.googleusercontent.com',
scopes: scopes,
);
// #enddocregion Initialize
// #enddocregion CheckAuthorization

void main() {
runApp(
@@ -50,58 +51,102 @@ class _SignInDemoState extends State<SignInDemo> {
GoogleSignInAccount? _currentUser;
bool _isAuthorized = false; // has granted permissions?
String _contactText = '';
String _errorMessage = '';
String _serverAuthCode = '';

@override
void initState() {
super.initState();

_googleSignIn.onCurrentUserChanged
.listen((GoogleSignInAccount? account) async {
// #docregion CanAccessScopes
// In mobile, being authenticated means being authorized...
bool isAuthorized = account != null;
// However, on web...
if (kIsWeb && account != null) {
isAuthorized = await _googleSignIn.canAccessScopes(scopes);
}
// #enddocregion CanAccessScopes
// #docregion Setup
final GoogleSignIn signIn = GoogleSignIn.instance;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm this reminds me of flutter/flutter#168100, if the user connects / disconnects a mouse / keyboard on Android the activity will restart and that would result in the element tree being re-inflated anew.

I guess that does not break the new flow (from reading the docs initialize has to be called exactly once in a program) since the new activity will spin up a new dart vm? Still it would be an annoyance that every time I unplug my keyboard I have to go over the sign-in process again.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or when the activity restarts, the lightweight authentication path will be taken and the flow will typically be invisible to user / require no additional user interactions?

EDIT: Just read the design doc and it mentioned "Fully silent sign in is no longer available"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still it would be an annoyance that every time I unplug my keyboard I have to go over the sign-in process again.

That's up to whether the app developer chooses to include auth state in their state restoration.

Or when the activity restarts, the lightweight authentication path will be taken

That's up to the app developer too.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

include auth state in their state restoration

Oh I thought the developer would have to restore GoogleSignIn, and state restoration only supports value types. If the dart part of GoogleSignIn doesn't keep any secret state itself then that makes sense.

unawaited(signIn
.initialize(clientId: clientId, serverClientId: serverClientId)
.then((_) {
signIn.authenticationEvents
.listen(_handleAuthenticationEvent)
.onError(_handleAuthenticationError);

setState(() {
_currentUser = account;
_isAuthorized = isAuthorized;
});
/// This example always uses the stream-based approach to determining
/// which UI state to show, rather than using the future returned here,
/// if any, to conditionally skip directly to the signed-in state.
signIn.attemptLightweightAuthentication();
}));
// #enddocregion Setup
}

// Now that we know that the user can access the required scopes, the app
// can call the REST API.
if (isAuthorized) {
unawaited(_handleGetContact(account!));
}
Future<void> _handleAuthenticationEvent(
GoogleSignInAuthenticationEvent event) async {
// #docregion CheckAuthorization
final GoogleSignInAccount? user = // ...
// #enddocregion CheckAuthorization
switch (event) {
GoogleSignInAuthenticationEventSignIn() => event.user,
GoogleSignInAuthenticationEventSignOut() => null,
};

// Check for existing authorization.
// #docregion CheckAuthorization
final GoogleSignInClientAuthorization? authorization =
await user?.authorizationClient.authorizationForScopes(scopes);
// #enddocregion CheckAuthorization

setState(() {
_currentUser = user;
_isAuthorized = authorization != null;
_errorMessage = '';
});

// In the web, _googleSignIn.signInSilently() triggers the One Tap UX.
//
// It is recommended by Google Identity Services to render both the One Tap UX
// and the Google Sign In button together to "reduce friction and improve
// sign-in rates" ([docs](https://developers.google.com/identity/gsi/web/guides/display-button#html)).
_googleSignIn.signInSilently();
// If the user has already granted access to the required scopes, call the
// REST API.
if (user != null && authorization != null) {
unawaited(_handleGetContact(user));
}
}

Future<void> _handleAuthenticationError(Object e) async {
setState(() {
_currentUser = null;
_isAuthorized = false;
_errorMessage = e is GoogleSignInException
? 'GoogleSignInException ${e.code}: ${e.description}'
: 'Unknown error: $e';
});
}

// Calls the People API REST endpoint for the signed-in user to retrieve information.
Future<void> _handleGetContact(GoogleSignInAccount user) async {
setState(() {
_contactText = 'Loading contact info...';
});
final Map<String, String>? headers =
await user.authorizationClient.authorizationHeaders(scopes);
if (headers == null) {
setState(() {
_contactText = '';
_errorMessage = 'Failed to construct authorization headers.';
});
return;
}
final http.Response response = await http.get(
Uri.parse('https://people.googleapis.com/v1/people/me/connections'
'?requestMask.includeField=person.names'),
headers: await user.authHeaders,
headers: headers,
);
if (response.statusCode != 200) {
setState(() {
_contactText = 'People API gave a ${response.statusCode} '
'response. Check logs for details.';
});
print('People API ${response.statusCode} response: ${response.body}');
if (response.statusCode == 401 || response.statusCode == 403) {
setState(() {
_isAuthorized = false;
_errorMessage = 'People API gave a ${response.statusCode} response. '
'Please re-authorize access.';
});
} else {
print('People API ${response.statusCode} response: ${response.body}');
setState(() {
_contactText = 'People API gave a ${response.statusCode} '
'response. Check logs for details.';
});
}
return;
}
final Map<String, dynamic> data =
@@ -136,94 +181,147 @@ class _SignInDemoState extends State<SignInDemo> {
return null;
}

// This is the on-click handler for the Sign In button that is rendered by Flutter.
// Prompts the user to authorize `scopes`.
//
// On the web, the on-click handler of the Sign In button is owned by the JS
// SDK, so this method can be considered mobile only.
// #docregion SignIn
Future<void> _handleSignIn() async {
// If authorizationRequiresUserInteraction() is true, this must be called from
// a user interaction (button click). In this example app, a button is used
// regardless, so authorizationRequiresUserInteraction() is not checked.
Future<void> _handleAuthorizeScopes(GoogleSignInAccount user) async {
try {
await _googleSignIn.signIn();
} catch (error) {
print(error);
// #docregion RequestScopes
final GoogleSignInClientAuthorization authorization =
await user.authorizationClient.authorizeScopes(scopes);
// #enddocregion RequestScopes

// The returned tokens are ignored since _handleGetContact uses the
// authorizationHeaders method to re-read the token cached by
// authorizeScopes. The code above is used as a README excerpt, so shows
// the simpler pattern of getting the authorization for immediate use.
// That results in an unused variable, which this statement suppresses
// (without adding an ignore: directive to the README excerpt).
// ignore: unnecessary_statements
authorization;

setState(() {
_isAuthorized = true;
_errorMessage = '';
});
unawaited(_handleGetContact(_currentUser!));
} on GoogleSignInException catch (e) {
_errorMessage = 'GoogleSignInException ${e.code}: ${e.description}';
}
}
// #enddocregion SignIn

// Prompts the user to authorize `scopes`.
// Requests a server auth code for the authorized scopes.
//
// This action is **required** in platforms that don't perform Authentication
// and Authorization at the same time (like the web).
//
// On the web, this must be called from an user interaction (button click).
// #docregion RequestScopes
Future<void> _handleAuthorizeScopes() async {
final bool isAuthorized = await _googleSignIn.requestScopes(scopes);
// #enddocregion RequestScopes
setState(() {
_isAuthorized = isAuthorized;
});
// #docregion RequestScopes
if (isAuthorized) {
unawaited(_handleGetContact(_currentUser!));
// If authorizationRequiresUserInteraction() is true, this must be called from
// a user interaction (button click). In this example app, a button is used
// regardless, so authorizationRequiresUserInteraction() is not checked.
Future<void> _handleGetAuthCode(GoogleSignInAccount user) async {
try {
// #docregion RequestServerAuth
final GoogleSignInServerAuthorization? serverAuth =
await user.authorizationClient.authorizeServer(scopes);
// #enddocregion RequestServerAuth

setState(() {
_serverAuthCode = serverAuth == null ? '' : serverAuth.serverAuthCode;
});
} on GoogleSignInException catch (e) {
_errorMessage = 'GoogleSignInException ${e.code}: ${e.description}';
}
// #enddocregion RequestScopes
}

Future<void> _handleSignOut() => _googleSignIn.disconnect();
Future<void> _handleSignOut() async {
// Disconnect instead of just signing out, to reset the example state as
// much as possible.
await GoogleSignIn.instance.disconnect();
}

Widget _buildBody() {
final GoogleSignInAccount? user = _currentUser;
if (user != null) {
// The user is Authenticated
return Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
ListTile(
leading: GoogleUserCircleAvatar(
identity: user,
),
title: Text(user.displayName ?? ''),
subtitle: Text(user.email),
),
const Text('Signed in successfully.'),
if (_isAuthorized) ...<Widget>[
// The user has Authorized all required scopes
Text(_contactText),
ElevatedButton(
child: const Text('REFRESH'),
onPressed: () => _handleGetContact(user),
),
],
if (!_isAuthorized) ...<Widget>[
// The user has NOT Authorized all required scopes.
// (Mobile users may never see this button!)
const Text('Additional permissions needed to read your contacts.'),
ElevatedButton(
onPressed: _handleAuthorizeScopes,
child: const Text('REQUEST PERMISSIONS'),
),
],
return Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
if (user != null)
..._buildAuthenticatedWidgets(user)
else
..._buildUnauthenticatedWidgets(),
if (_errorMessage.isNotEmpty) Text(_errorMessage),
],
);
}

/// Returns the list of widgets to include if the user is authenticated.
List<Widget> _buildAuthenticatedWidgets(GoogleSignInAccount user) {
return <Widget>[
// The user is Authenticated.
ListTile(
leading: GoogleUserCircleAvatar(
identity: user,
),
title: Text(user.displayName ?? ''),
subtitle: Text(user.email),
),
const Text('Signed in successfully.'),
if (_isAuthorized) ...<Widget>[
// The user has Authorized all required scopes.
if (_contactText.isNotEmpty) Text(_contactText),
ElevatedButton(
child: const Text('REFRESH'),
onPressed: () => _handleGetContact(user),
),
if (_serverAuthCode.isEmpty)
ElevatedButton(
onPressed: _handleSignOut,
child: const Text('SIGN OUT'),
),
],
);
} else {
// The user is NOT Authenticated
return Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
const Text('You are not currently signed in.'),
// This method is used to separate mobile from web code with conditional exports.
// See: src/sign_in_button.dart
buildSignInButton(
onPressed: _handleSignIn,
),
],
);
}
child: const Text('REQUEST SERVER CODE'),
onPressed: () => _handleGetAuthCode(user),
)
else
Text('Server auth code:\n$_serverAuthCode'),
] else ...<Widget>[
// The user has NOT Authorized all required scopes.
const Text('Authorization needed to read your contacts.'),
ElevatedButton(
onPressed: () => _handleAuthorizeScopes(user),
child: const Text('REQUEST PERMISSIONS'),
),
],
ElevatedButton(
onPressed: _handleSignOut,
child: const Text('SIGN OUT'),
),
];
}

/// Returns the list of widgets to include if the user is not authenticated.
List<Widget> _buildUnauthenticatedWidgets() {
return <Widget>[
const Text('You are not currently signed in.'),
// #docregion ExplicitSignIn
if (GoogleSignIn.instance.supportsAuthenticate())
ElevatedButton(
onPressed: () async {
try {
await GoogleSignIn.instance.authenticate();
} catch (e) {
// #enddocregion ExplicitSignIn
_errorMessage = e.toString();
// #docregion ExplicitSignIn
}
},
child: const Text('SIGN IN'),
)
else ...<Widget>[
if (kIsWeb)
web.renderButton()
// #enddocregion ExplicitSignIn
else
const Text(
'This platform does not have a known authentication method')
Comment on lines +319 to +320
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fancy!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This exposes the biggest flaw in the supports* model for this case, which is that while we can provide runtime detection of when fallback is needed, the actual fallback has to be compile-time switched, so there's not much you can do at runtime if supportsAuthenticate is false in a case you didn't already account for... at which point why not just do the platform switching first and not call the supports method at all. But it does have the slight advantage of allowing for the possibility of a platform adding authenticate support later on (maybe the N+1th auth SDK will support it!) and clients automatically switching to that.

// #docregion ExplicitSignIn
]
// #enddocregion ExplicitSignIn
];
}

@override

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -2,6 +2,4 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

export 'sign_in_button/stub.dart'
if (dart.library.js_util) 'sign_in_button/web.dart'
if (dart.library.io) 'sign_in_button/mobile.dart';
export 'web_wrapper_stub.dart' if (dart.library.js_util) 'web_wrapper_web.dart';
Original file line number Diff line number Diff line change
@@ -3,11 +3,9 @@
// found in the LICENSE file.

import 'package:flutter/material.dart';
import 'package:google_sign_in_web/web_only.dart' as web;

import 'stub.dart';

/// Renders a web-only SIGN IN button.
Widget buildSignInButton({HandleSignInFn? onPressed}) {
return web.renderButton();
/// Stub for the web-only renderButton method, since google_sign_in_web has to
/// be behind a conditional import.
Widget renderButton() {
throw StateError('This should only be called on web');
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

export 'package:google_sign_in_web/web_only.dart';
Original file line number Diff line number Diff line change
@@ -16,7 +16,7 @@ dependencies:
# The example app is bundled with the plugin so we use a path dependency on
# the parent directory to use the current plugin's version.
path: ../
google_sign_in_web: ^0.12.3
google_sign_in_web: ^1.0.0
http: ">=0.13.0 <2.0.0"

dev_dependencies:
768 changes: 413 additions & 355 deletions packages/google_sign_in/google_sign_in/lib/google_sign_in.dart

Large diffs are not rendered by default.

36 changes: 36 additions & 0 deletions packages/google_sign_in/google_sign_in/lib/src/event_types.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:flutter/foundation.dart';

import '../google_sign_in.dart';

export 'package:google_sign_in_platform_interface/google_sign_in_platform_interface.dart'
show GoogleSignInException;

/// A base class for authentication event streams.
@immutable
sealed class GoogleSignInAuthenticationEvent {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these classes needed? It seems the sealed type is a glorified GoogleSignInAccount?.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Originally there were three subclasses, but exceptions moved into onError; now we could make them GoogleSignInAccount?.

It would make things less self-documenting though. It's non-obvious that getting null as an authenticationEvent means "the user signed out". Is that worth the slightly easier usage?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking maybe we can avoid introducing new classes by using typedef or extension types. I played with that idea but found the resulting code harder to read (the user code would still be the same though). So never mind.

const GoogleSignInAuthenticationEvent();
}

/// A sign-in event, corresponding to an authentication flow completing
/// successfully.
@immutable
class GoogleSignInAuthenticationEventSignIn
extends GoogleSignInAuthenticationEvent {
/// Creates an event for a successful sign in.
const GoogleSignInAuthenticationEventSignIn({required this.user});

/// The user that was authenticated.
final GoogleSignInAccount user;
}

/// A sign-out event, corresponding to a user having been signed out.
///
/// Implicit sign-outs (for example, due to server-side authentication
/// revocation, or timeouts) are not guaranteed to send events.
@immutable
class GoogleSignInAuthenticationEventSignOut
extends GoogleSignInAuthenticationEvent {}
Original file line number Diff line number Diff line change
@@ -34,7 +34,4 @@ abstract class GoogleIdentity {
///
/// Not guaranteed to be present for all users, even when configured.
String? get photoUrl;

/// Server auth code used to access Google Login
String? get serverAuthCode;
}
57 changes: 57 additions & 0 deletions packages/google_sign_in/google_sign_in/lib/src/token_types.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:flutter/foundation.dart';

/// Holds authentication tokens.
///
/// Currently there is only an idToken, but this wrapper class allows for the
/// posibility of adding additional information in the future without breaking
/// changes.
@immutable
class GoogleSignInAuthentication {
/// Creates a new token container with the given tokens.
const GoogleSignInAuthentication({required this.idToken});

/// An OpenID Connect ID token that identifies the user.
final String? idToken;

@override
String toString() => 'GoogleSignInAuthentication: $idToken';
}

/// Holds client authorization tokens.
///
/// Currently there is only an accessToken, but this wrapper class allows for
/// the posibility of adding additional information in the future without
/// breaking changes.
@immutable
class GoogleSignInClientAuthorization {
/// Creates a new token container with the given tokens.
const GoogleSignInClientAuthorization({required this.accessToken});

/// The OAuth2 access token to access Google services.
final String accessToken;

@override
String toString() => 'GoogleSignInClientAuthorization: $accessToken';
}

/// Holds server authorization tokens.
///
/// Currently there is only a serverAuthCode, but this wrapper class allows for
/// the posibility of adding additional information in the future without
/// breaking changes.
@immutable
class GoogleSignInServerAuthorization {
/// Creates a new token container with the given tokens.
const GoogleSignInServerAuthorization({required this.serverAuthCode});

/// Auth code to provide to a backend server to exchange for access or
/// refresh tokens.
final String serverAuthCode;

@override
String toString() => 'GoogleSignInServerAuthorization: $serverAuthCode';
}
2 changes: 1 addition & 1 deletion packages/google_sign_in/google_sign_in/lib/widgets.dart
Original file line number Diff line number Diff line change
@@ -6,8 +6,8 @@ import 'dart:typed_data';

import 'package:flutter/material.dart';

import 'src/common.dart';
import 'src/fife.dart' as fife;
import 'src/identity_types.dart';

/// Builds a CircleAvatar profile image of the appropriate resolution
class GoogleUserCircleAvatar extends StatelessWidget {
11 changes: 6 additions & 5 deletions packages/google_sign_in/google_sign_in/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -3,7 +3,7 @@ description: Flutter plugin for Google Sign-In, a secure authentication system
for signing in with a Google account.
repository: https://github.com/flutter/packages/tree/main/packages/google_sign_in/google_sign_in
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+google_sign_in%22
version: 6.3.0
version: 7.0.0

environment:
sdk: ^3.6.0
@@ -24,17 +24,18 @@ flutter:
dependencies:
flutter:
sdk: flutter
google_sign_in_android: ^6.2.0
google_sign_in_ios: ^5.8.1
google_sign_in_platform_interface: ^2.5.0
google_sign_in_web: ^0.12.4+4
google_sign_in_android: ^7.0.0
google_sign_in_ios: ^6.0.0
google_sign_in_platform_interface: ^3.0.0
google_sign_in_web: ^1.0.0

dev_dependencies:
build_runner: ^2.1.10
flutter_test:
sdk: flutter
http: ">=0.13.0 <2.0.0"
mockito: ^5.4.4
plugin_platform_interface: ^2.1.8

topics:
- authentication
917 changes: 539 additions & 378 deletions packages/google_sign_in/google_sign_in/test/google_sign_in_test.dart

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Mocks generated by Mockito 5.4.4 from annotations
// Mocks generated by Mockito 5.4.5 from annotations
// in google_sign_in/test/google_sign_in_test.dart.
// Do not manually edit this file.

@@ -18,20 +18,16 @@ import 'package:mockito/mockito.dart' as _i1;
// ignore_for_file: deprecated_member_use_from_same_package
// ignore_for_file: implementation_imports
// ignore_for_file: invalid_use_of_visible_for_testing_member
// ignore_for_file: must_be_immutable
// ignore_for_file: prefer_const_constructors
// ignore_for_file: unnecessary_parenthesis
// ignore_for_file: camel_case_types
// ignore_for_file: subtype_of_sealed_class

class _FakeGoogleSignInTokenData_0 extends _i1.SmartFake
implements _i2.GoogleSignInTokenData {
_FakeGoogleSignInTokenData_0(
Object parent,
Invocation parentInvocation,
) : super(
parent,
parentInvocation,
);
class _FakeAuthenticationResults_0 extends _i1.SmartFake
implements _i2.AuthenticationResults {
_FakeAuthenticationResults_0(Object parent, Invocation parentInvocation)
: super(parent, parentInvocation);
}

/// A class which mocks [GoogleSignInPlatform].
@@ -44,151 +40,78 @@ class MockGoogleSignInPlatform extends _i1.Mock
}

@override
bool get isMock => (super.noSuchMethod(
Invocation.getter(#isMock),
returnValue: false,
) as bool);

@override
_i4.Future<void> init({
List<String>? scopes = const [],
_i2.SignInOption? signInOption = _i2.SignInOption.standard,
String? hostedDomain,
String? clientId,
}) =>
(super.noSuchMethod(
Invocation.method(
#init,
[],
{
#scopes: scopes,
#signInOption: signInOption,
#hostedDomain: hostedDomain,
#clientId: clientId,
},
),
_i4.Future<void> init(_i2.InitParameters? params) => (super.noSuchMethod(
Invocation.method(#init, [params]),
returnValue: _i4.Future<void>.value(),
returnValueForMissingStub: _i4.Future<void>.value(),
) as _i4.Future<void>);

@override
_i4.Future<void> initWithParams(_i2.SignInInitParameters? params) =>
_i4.Future<_i2.AuthenticationResults?>? attemptLightweightAuthentication(
_i2.AttemptLightweightAuthenticationParameters? params,
) =>
(super.noSuchMethod(
Invocation.method(
#initWithParams,
[params],
),
returnValue: _i4.Future<void>.value(),
returnValueForMissingStub: _i4.Future<void>.value(),
) as _i4.Future<void>);
Invocation.method(#attemptLightweightAuthentication, [params]),
) as _i4.Future<_i2.AuthenticationResults?>?);

@override
_i4.Future<_i2.GoogleSignInUserData?> signInSilently() => (super.noSuchMethod(
Invocation.method(
#signInSilently,
[],
_i4.Future<_i2.AuthenticationResults> authenticate(
_i2.AuthenticateParameters? params,
) =>
(super.noSuchMethod(
Invocation.method(#authenticate, [params]),
returnValue: _i4.Future<_i2.AuthenticationResults>.value(
_FakeAuthenticationResults_0(
this,
Invocation.method(#authenticate, [params]),
),
),
returnValue: _i4.Future<_i2.GoogleSignInUserData?>.value(),
) as _i4.Future<_i2.GoogleSignInUserData?>);
) as _i4.Future<_i2.AuthenticationResults>);

@override
_i4.Future<_i2.GoogleSignInUserData?> signIn() => (super.noSuchMethod(
Invocation.method(
#signIn,
[],
),
returnValue: _i4.Future<_i2.GoogleSignInUserData?>.value(),
) as _i4.Future<_i2.GoogleSignInUserData?>);
bool supportsAuthenticate() => (super.noSuchMethod(
Invocation.method(#supportsAuthenticate, []),
returnValue: false,
) as bool);

@override
_i4.Future<_i2.GoogleSignInTokenData> getTokens({
required String? email,
bool? shouldRecoverAuth,
}) =>
(super.noSuchMethod(
Invocation.method(
#getTokens,
[],
{
#email: email,
#shouldRecoverAuth: shouldRecoverAuth,
},
),
returnValue: _i4.Future<_i2.GoogleSignInTokenData>.value(
_FakeGoogleSignInTokenData_0(
this,
Invocation.method(
#getTokens,
[],
{
#email: email,
#shouldRecoverAuth: shouldRecoverAuth,
},
),
)),
) as _i4.Future<_i2.GoogleSignInTokenData>);
bool authorizationRequiresUserInteraction() => (super.noSuchMethod(
Invocation.method(#authorizationRequiresUserInteraction, []),
returnValue: false,
) as bool);

@override
_i4.Future<void> signOut() => (super.noSuchMethod(
Invocation.method(
#signOut,
[],
),
returnValue: _i4.Future<void>.value(),
returnValueForMissingStub: _i4.Future<void>.value(),
) as _i4.Future<void>);
_i4.Future<_i2.ClientAuthorizationTokenData?>
clientAuthorizationTokensForScopes(
_i2.ClientAuthorizationTokensForScopesParameters? params,
) =>
(super.noSuchMethod(
Invocation.method(#clientAuthorizationTokensForScopes, [params]),
returnValue: _i4.Future<_i2.ClientAuthorizationTokenData?>.value(),
) as _i4.Future<_i2.ClientAuthorizationTokenData?>);

@override
_i4.Future<void> disconnect() => (super.noSuchMethod(
Invocation.method(
#disconnect,
[],
),
_i4.Future<_i2.ServerAuthorizationTokenData?>
serverAuthorizationTokensForScopes(
_i2.ServerAuthorizationTokensForScopesParameters? params,
) =>
(super.noSuchMethod(
Invocation.method(#serverAuthorizationTokensForScopes, [params]),
returnValue: _i4.Future<_i2.ServerAuthorizationTokenData?>.value(),
) as _i4.Future<_i2.ServerAuthorizationTokenData?>);

@override
_i4.Future<void> signOut(_i2.SignOutParams? params) => (super.noSuchMethod(
Invocation.method(#signOut, [params]),
returnValue: _i4.Future<void>.value(),
returnValueForMissingStub: _i4.Future<void>.value(),
) as _i4.Future<void>);

@override
_i4.Future<bool> isSignedIn() => (super.noSuchMethod(
Invocation.method(
#isSignedIn,
[],
),
returnValue: _i4.Future<bool>.value(false),
) as _i4.Future<bool>);

@override
_i4.Future<void> clearAuthCache({required String? token}) =>
_i4.Future<void> disconnect(_i2.DisconnectParams? params) =>
(super.noSuchMethod(
Invocation.method(
#clearAuthCache,
[],
{#token: token},
),
Invocation.method(#disconnect, [params]),
returnValue: _i4.Future<void>.value(),
returnValueForMissingStub: _i4.Future<void>.value(),
) as _i4.Future<void>);

@override
_i4.Future<bool> requestScopes(List<String>? scopes) => (super.noSuchMethod(
Invocation.method(
#requestScopes,
[scopes],
),
returnValue: _i4.Future<bool>.value(false),
) as _i4.Future<bool>);

@override
_i4.Future<bool> canAccessScopes(
List<String>? scopes, {
String? accessToken,
}) =>
(super.noSuchMethod(
Invocation.method(
#canAccessScopes,
[scopes],
{#accessToken: accessToken},
),
returnValue: _i4.Future<bool>.value(false),
) as _i4.Future<bool>);
}
Original file line number Diff line number Diff line change
@@ -28,9 +28,6 @@ class _TestGoogleIdentity extends GoogleIdentity {

@override
String? get displayName => null;

@override
String? get serverAuthCode => null;
}

/// A mocked [HttpClient] which always returns a [_MockHttpRequest].
9 changes: 3 additions & 6 deletions script/configs/exclude_all_packages_app.yaml
Original file line number Diff line number Diff line change
@@ -11,9 +11,6 @@

# This is a permanent entry, as it should never be a direct app dependency.
- plugin_platform_interface
# Breaking change in the process of being landed. This will be removed
# once all the layers have landed.
- google_sign_in_platform_interface
- google_sign_in_android
- google_sign_in_ios
- google_sign_in_web
# Temporarily excluded since it hasn't been updated for the major version
# change in google_sign_in. https://github.com/flutter/flutter/issues/171048
- extension_google_sign_in_as_googleapis_auth