Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
Next Next commit
[extension_google_sign_in_as_googleapis_auth] Update to google_sign_in 7
Updates the extension to work with the newly released `google_sign_in`
7.x, which reworked the API surface.

The extension method can no longer do as many of the steps, so its
utility is lower. Longer term this may not be worth maintaining as a
package, but for now this will simplify migration to `google_sign_in`
7.x for existing clients of this package.

Fixes flutter/flutter#171048
  • Loading branch information
stuartmorgan-g committed Jun 24, 2025
commit 6727ca9568d61827ebf342f0e54e33430e1655d3
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
## NEXT

## 3.0.0

* **BREAKING CHANGES**:
* The extension method is now on `GoogleSignInClientAuthorization` instead of
`GoogleSignIn`, so it must be used after completing an authorization flow.
* The extension method has been renamed to `authClient`.
* The extension method now requires passing `scopes`, matching those used to
request the authorization.
* Updates minimum supported SDK version to Flutter 3.27/Dart 3.6.

## 2.0.13
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,12 @@ import 'package:extension_google_sign_in_as_googleapis_auth/extension_google_sig
import 'package:flutter/material.dart';
import 'package:google_sign_in/google_sign_in.dart';
import 'package:googleapis/people/v1.dart';
// #docregion Import
import 'package:googleapis_auth/googleapis_auth.dart' as auth show AuthClient;
// #enddocregion Import

final GoogleSignIn _googleSignIn = GoogleSignIn(
// Optional clientId
// clientId: '[YOUR_OAUTH_2_CLIENT_ID]',
scopes: <String>[PeopleServiceApi.contactsReadonlyScope],
);
/// The scopes used by this example.
const List<String> scopes = <String>[PeopleServiceApi.contactsReadonlyScope];

void main() {
runApp(
Expand All @@ -38,37 +37,76 @@ class SignInDemo extends StatefulWidget {

/// The state of the main widget.
class SignInDemoState extends State<SignInDemo> {
late Future<void> _signInInitialized;
GoogleSignInAccount? _currentUser;
GoogleSignInClientAuthorization? _authorization;
String _contactText = '';

@override
void initState() {
super.initState();
_googleSignIn.onCurrentUserChanged.listen((GoogleSignInAccount? account) {

final GoogleSignIn signIn = GoogleSignIn.instance;
_signInInitialized = signIn.initialize(
// Add your client IDs here as necessary for your supported platforms.
);
signIn.authenticationEvents.listen((GoogleSignInAuthenticationEvent event) {
setState(() {
_currentUser = account;
switch (event) {
case GoogleSignInAuthenticationEventSignIn():
_currentUser = event.user;
case GoogleSignInAuthenticationEventSignOut():
_currentUser = null;
_authorization = null;
}
});

if (_currentUser != null) {
_handleGetContact();
_checkAuthorization();
}
}).onError((Object error) {
debugPrint(error.toString());
});

_signInInitialized.then((void value) {
signIn.attemptLightweightAuthentication();
});
_googleSignIn.signInSilently();
}

void _updateAuthorization(GoogleSignInClientAuthorization? authorization) {
setState(() {
_authorization = authorization;
});

if (authorization != null) {
unawaited(_handleGetContact());
}
}

Future<void> _checkAuthorization() async {
_updateAuthorization(
await _currentUser?.authorizationClient.authorizationForScopes(scopes));
}

Future<void> _requestAuthorization() async {
_updateAuthorization(await _currentUser?.authorizationClient
.authorizeScopes(<String>[PeopleServiceApi.contactsReadonlyScope]));
}

Future<void> _handleGetContact() async {
setState(() {
_contactText = 'Loading contact info...';
});

// #docregion CreateAPIClient
// Retrieve an [auth.AuthClient] from the current [GoogleSignIn] instance.
final auth.AuthClient? client = await _googleSignIn.authenticatedClient();
// #docregion CreateAPIClient
// Retrieve an [auth.AuthClient] from a GoogleSignInClientAuthorization.
final auth.AuthClient? client = _authorization?.authClient(scopes: scopes);

assert(client != null, 'Authenticated client missing!');

// Prepare a People Service authenticated client.
final PeopleServiceApi peopleApi = PeopleServiceApi(client!);
// Retrieve a list of the `names` of my `connections`
// Retrieve a list of connected contacts' names.
final ListConnectionsResponse response =
await peopleApi.people.connections.list(
'people/me',
Expand Down Expand Up @@ -102,51 +140,71 @@ class SignInDemoState extends State<SignInDemo> {

Future<void> _handleSignIn() async {
try {
await _googleSignIn.signIn();
await GoogleSignIn.instance.authenticate();
} catch (error) {
print(error); // ignore: avoid_print
debugPrint(error.toString());
}
}

Future<void> _handleSignOut() => _googleSignIn.disconnect();
// Call disconnect rather than signOut to more fully reset the example app.
Future<void> _handleSignOut() => GoogleSignIn.instance.disconnect();

Widget _buildBody() {
final GoogleSignInAccount? user = _currentUser;
if (user != null) {
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.'),
Text(_contactText),
ElevatedButton(
onPressed: _handleSignOut,
child: const Text('SIGN OUT'),
),
ElevatedButton(
onPressed: _handleGetContact,
child: const Text('REFRESH'),
),
],
);
} else {
return Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
const Text('You are not currently signed in.'),
ElevatedButton(
onPressed: _handleSignIn,
child: const Text('SIGN IN'),
),
],
);
}
return FutureBuilder<void>(
future: _signInInitialized,
builder: (BuildContext context, AsyncSnapshot<void> snapshot) {
final GoogleSignInAccount? user = _currentUser;
final List<Widget> children;
if (snapshot.hasError) {
children = <Widget>[
const Text('Error initializing sign in.'),
];
} else if (snapshot.connectionState == ConnectionState.done) {
children = <Widget>[
if (user != null) ...<Widget>[
ListTile(
leading: GoogleUserCircleAvatar(
identity: user,
),
title: Text(user.displayName ?? ''),
subtitle: Text(user.email),
),
const Text('Signed in successfully.'),
if (_authorization != null) ...<Widget>[
Text(_contactText),
ElevatedButton(
onPressed: _handleGetContact,
child: const Text('REFRESH'),
),
] else ...<Widget>[
ElevatedButton(
onPressed: _requestAuthorization,
child: const Text('LOAD CONTACTS'),
),
],
ElevatedButton(
onPressed: _handleSignOut,
child: const Text('SIGN OUT'),
),
] else ...<Widget>[
const Text('You are not currently signed in.'),
ElevatedButton(
onPressed: _handleSignIn,
child: const Text('SIGN IN'),
),
],
];
} else {
children = <Widget>[
const CircularProgressIndicator(),
];
}

return Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: children,
);
});
}

@override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ dependencies:
path: ../
flutter:
sdk: flutter
google_sign_in: ^6.0.0
google_sign_in: ^7.0.0
googleapis: ">=10.1.0 <14.0.0"
googleapis_auth: ^1.1.0

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,34 +5,29 @@
import 'package:google_sign_in/google_sign_in.dart';
import 'package:googleapis_auth/googleapis_auth.dart' as gapis;
import 'package:http/http.dart' as http;
import 'package:meta/meta.dart';

/// Extension on [GoogleSignIn] that adds an `authenticatedClient` method.
///
/// This method can be used to retrieve an authenticated [gapis.AuthClient]
/// client that can be used with the rest of the `googleapis` libraries.
extension GoogleApisGoogleSignInAuth on GoogleSignIn {
/// Retrieve a `googleapis` authenticated client.
Future<gapis.AuthClient?> authenticatedClient({
@visibleForTesting GoogleSignInAuthentication? debugAuthentication,
@visibleForTesting List<String>? debugScopes,
}) async {
final GoogleSignInAuthentication? auth =
debugAuthentication ?? await currentUser?.authentication;
final String? oauthTokenString = auth?.accessToken;
if (oauthTokenString == null) {
return null;
}
/// Extension on [GoogleSignInClientAuthorization] that adds an
/// `authClient` method.
extension GoogleApisGoogleSignInAuth on GoogleSignInClientAuthorization {
/// Returns an authenticated [gapis.AuthClient] client that can be used with
/// the rest of the `googleapis` libraries.
///
/// The [scopes] passed here should be the same as the scopes used to request
/// the authorization. Passing scopes here that have not been authorized will
/// likely result in API errors when using the client.
gapis.AuthClient? authClient({
required List<String> scopes,
}) {
final gapis.AccessCredentials credentials = gapis.AccessCredentials(
gapis.AccessToken(
'Bearer',
oauthTokenString,
// TODO(kevmoo): Use the correct value once it's available from authentication
// See https://github.com/flutter/flutter/issues/80905
accessToken,
// The underlying SDKs don't provide expiry information, so set an
// arbitrary distant-future time.
DateTime.now().toUtc().add(const Duration(days: 365)),
),
null, // We don't have a refreshToken
debugScopes ?? scopes,
null, // The underlying SDKs don't provide a refresh token.
scopes,
);

return gapis.authenticatedClient(http.Client(), credentials);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ name: extension_google_sign_in_as_googleapis_auth
description: A bridge package between google_sign_in and googleapis_auth, to create Authenticated Clients from google_sign_in user credentials.
repository: https://github.com/flutter/packages/tree/main/packages/extension_google_sign_in_as_googleapis_auth
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+extension_google_sign_in_as_googleapis_auth%22
version: 2.0.13
version: 3.0.0

environment:
sdk: ^3.6.0
Expand All @@ -17,7 +17,7 @@ environment:
dependencies:
flutter:
sdk: flutter
google_sign_in: ">=5.0.0 <7.0.0"
google_sign_in: ^7.0.0
googleapis_auth: '>=1.1.0 <3.0.0'
http: ">=0.13.0 <2.0.0"
meta: ^1.3.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,50 +8,23 @@ import 'package:google_sign_in/google_sign_in.dart';
import 'package:googleapis_auth/googleapis_auth.dart' as gapis;

const String SOME_FAKE_ACCESS_TOKEN = 'this-is-something-not-null';
const List<String> DEBUG_FAKE_SCOPES = <String>['some-scope', 'another-scope'];
const List<String> SIGN_IN_FAKE_SCOPES = <String>[
'some-scope',
'another-scope'
];

class FakeGoogleSignIn extends Fake implements GoogleSignIn {
@override
final List<String> scopes = SIGN_IN_FAKE_SCOPES;
}

class FakeGoogleSignInAuthentication extends Fake
implements GoogleSignInAuthentication {
class FakeGoogleSignInClientAuthorization extends Fake
implements GoogleSignInClientAuthorization {
@override
final String accessToken = SOME_FAKE_ACCESS_TOKEN;
}

void main() {
final GoogleSignIn signIn = FakeGoogleSignIn();
final FakeGoogleSignInAuthentication authMock =
FakeGoogleSignInAuthentication();

test('authenticatedClient returns an authenticated client', () async {
final gapis.AuthClient client = (await signIn.authenticatedClient(
debugAuthentication: authMock,
))!;
expect(client, isA<gapis.AuthClient>());
});

test('authenticatedClient uses GoogleSignIn scopes by default', () async {
final gapis.AuthClient client = (await signIn.authenticatedClient(
debugAuthentication: authMock,
))!;
expect(client.credentials.accessToken.data, equals(SOME_FAKE_ACCESS_TOKEN));
expect(client.credentials.scopes, equals(SIGN_IN_FAKE_SCOPES));
});

test('authenticatedClient returned client contains the passed-in credentials',
test('authClient returned client contains the expected information',
() async {
final gapis.AuthClient client = (await signIn.authenticatedClient(
debugAuthentication: authMock,
debugScopes: DEBUG_FAKE_SCOPES,
))!;
const List<String> scopes = <String>['some-scope', 'another-scope'];
final FakeGoogleSignInClientAuthorization signInAuth =
FakeGoogleSignInClientAuthorization();
final gapis.AuthClient client = signInAuth.authClient(
scopes: scopes,
)!;
expect(client.credentials.accessToken.data, equals(SOME_FAKE_ACCESS_TOKEN));
expect(client.credentials.scopes, equals(DEBUG_FAKE_SCOPES));
expect(client.credentials.scopes, equals(scopes));
});
}