Google 官方在 Flutter 的 google_sign_in 插件中进行了较大更新,尤其是认证流程的设计方式、授权 scope 的处理方式,以及 Web/移动端统一化的 API。本篇博客将结合官方示例代码,带你系统梳理新版 Google 登录插件的使用方法与技术要点。
一、插件核心变化概览
新版 google_sign_in 围绕「状态监听统一化」「多端 API 一致性」「授权流程灵活化」三大方向进行了重构,核心变化如下:
1. 引入 Authentication Events(认证事件流)
不再依赖旧版的 signIn()、signOut() Future 方法判断登录状态,而是通过流(Stream) 实时监听认证状态变化,适用于跨页面、复杂结构的 App。
dart
// 监听登录/登出等所有认证事件
GoogleSignIn.instance.authenticationEvents.listen(_handleAuthenticationEvent);
2. 新的授权核心:authorizationClient
新增 GoogleSignInAuthorizationClient 类,统一处理授权相关操作,核心能力包括:
- 请求额外的授权 Scope
- 获取 API 调用所需的 Headers
- 获取 Server Auth Code(用于后端验证)
示例:请求指定 Scope 的授权
dart
final authorization = await user.authorizationClient.authorizeScopes([
'https://www.googleapis.com/auth/contacts.readonly'
]);
3. 强化 Web 支持(多端 API 统一)
移动端与 Web 端共享 90% 以上的核心 API,Web 端无需单独封装逻辑,仅需通过 web.renderButton() 渲染 Google 官方登录按钮即可:
dart
// Web 端渲染原生登录按钮
GoogleSignInWeb.renderButton(
container: document.getElementById('google-sign-in-button'),
onPressed: () => GoogleSignIn.instance.authenticate(),
);
4. 轻量级认证(免弹窗自动登录)
新增 attemptLightweightAuthentication() 方法,可在用户已授权过的场景下直接恢复登录状态,无需重复弹窗,提升用户体验:
dart
// 初始化后尝试自动登录
GoogleSignIn.instance.initialize(clientId: 'YOUR_CLIENT_ID').then((_) {
GoogleSignIn.instance.attemptLightweightAuthentication();
});
二、示例功能介绍
本文配套示例覆盖 google_sign_in 新版核心能力,可直接作为项目模板使用,包含:
- 实时监听登录/登出状态(基于
AuthenticationEvent) - 检查用户是否已授权目标 Scope
- 调用 Google People API 获取联系人信息
- 动态请求未授权的 Scope(非首次登录时补充授权)
- 获取 Server Auth Code(用于后端与 Google 服务器验证)
- 适配 Web/Android/iOS 三端的 UI 界面
三、核心流程解析
1. 初始化与事件监听
核心步骤 :通过 initialize() 配置客户端 ID,同时监听认证事件流,是所有操作的基础。
dart
// 1. 初始化 GoogleSignIn 实例
final GoogleSignIn _googleSignIn = GoogleSignIn.instance;
// 2. 配置客户端 ID并初始化(替换为你的实际 Client ID)
Future<void> initGoogleSignIn() async {
await _googleSignIn.initialize(
clientId: 'YOUR_ANDROID_IOS_CLIENT_ID', // 移动端 Client ID
serverClientId: 'YOUR_SERVER_CLIENT_ID', // 后端验证用 Server Client ID(可选)
);
// 3. 监听认证事件(登录/登出/授权变化)
_googleSignIn.authenticationEvents
.listen(_handleAuthenticationEvent)
.onError((error) {
// 处理事件监听异常(如网络错误)
print('Authentication event error: $error');
});
// 4. 尝试自动登录(免弹窗)
_googleSignIn.attemptLightweightAuthentication();
}
// 5. 处理认证事件
void _handleAuthenticationEvent(GoogleSignInAuthenticationEvent event) {
switch (event) {
// 登录事件
case GoogleSignInAuthenticationEventSignIn(:final user):
_currentUser = user; // 保存当前登录用户
_fetchContacts(); // 登录后拉取联系人(示例逻辑)
break;
// 登出事件
case GoogleSignInAuthenticationEventSignOut():
_currentUser = null; // 清空用户信息
break;
// 其他事件(如授权过期)
default:
print('Unhandled event type: ${event.runtimeType}');
}
}
关键说明:
- 新版弃用了旧版通过构造函数传参的方式,必须使用
initialize()初始化 authenticationEvents包含所有认证相关事件,比旧版onCurrentUserChanged覆盖场景更全
2. 检查 Scope 授权状态
在调用需要权限的 API(如 People API)前,需先检查用户是否已授权目标 Scope:
dart
// 目标 Scope(获取联系人只读权限)
final List<String> _requiredScopes = [
'https://www.googleapis.com/auth/contacts.readonly'
];
// 检查是否已授权
Future<bool> _checkScopeAuthorization() async {
if (_currentUser == null) return false;
// 通过 authorizationClient 检查授权状态
final authorization = await _currentUser!.authorizationClient
.authorizationForScopes(_requiredScopes);
// authorization.isAuthorized 为 true 表示已授权
return authorization.isAuthorized;
}
3. 调用 Google People API 获取联系人
前提 :需先获取 contacts.readonly Scope 授权,再通过 authorizationHeaders 获取请求头,发起 HTTP 请求。
dart
import 'package:http/http.dart' as http;
import 'dart:convert';
// 拉取联系人信息
Future<void> _fetchContacts() async {
// 1. 先检查是否已授权
final isAuthorized = await _checkScopeAuthorization();
if (!isAuthorized) {
// 未授权:提示用户授权(后续步骤会讲动态授权)
_showAuthorizationDialog();
return;
}
// 2. 获取 API 请求所需的 Headers
final headers = await _currentUser!.authorizationClient
.authorizationHeaders(_requiredScopes);
// 3. 发起 People API 请求(获取联系人姓名)
final response = await http.get(
Uri.parse(
'https://people.googleapis.com/v1/people/me/connections?requestMask.includeField=person.names'
),
headers: headers,
);
// 4. 解析响应数据
if (response.statusCode == 200) {
final data = json.decode(response.body);
final contacts = (data['connections'] as List)
.map((item) => item['names']?[0]['displayName'] ?? 'Unknown')
.toList();
_updateContactsList(contacts); // 更新 UI 展示联系人
} else {
print('Failed to fetch contacts: ${response.statusCode}');
}
}
4. 动态请求 Scope 授权
若用户首次登录未授权目标 Scope,可通过按钮触发动态授权:
dart
// 动态请求 Scope 授权
Future<void> _requestScopeAuthorization() async {
if (_currentUser == null) return;
// 发起授权请求
final authorization = await _currentUser!.authorizationClient
.authorizeScopes(_requiredScopes);
// 授权成功后重新拉取联系人
if (authorization.isAuthorized) {
_fetchContacts();
} else {
// 授权失败:提示用户
print('Scope authorization denied');
}
}
5. 获取 Server Auth Code(后端验证用)
若需要后端验证用户身份(如绑定账号),可通过 authorizeServer 获取 Server Auth Code,后端再用该 Code 向 Google 服务器交换 Access Token/ID Token:
dart
// 获取 Server Auth Code
Future<void> _getRequestServerAuthCode() async {
if (_currentUser == null) return;
// 注意:需传入 serverClientId(从 Google Cloud 控制台获取)
final serverAuth = await _currentUser!.authorizationClient
.authorizeServer(_requiredScopes, 'YOUR_SERVER_CLIENT_ID');
final serverAuthCode = serverAuth?.serverAuthCode ?? '';
if (serverAuthCode.isNotEmpty) {
// 将 Code 发送到后端进行验证
_sendServerAuthCodeToBackend(serverAuthCode);
} else {
print('Failed to get Server Auth Code');
}
}
// 发送 Code 到后端(示例)
void _sendServerAuthCodeToBackend(String code) {
http.post(
Uri.parse('YOUR_BACKEND_API_URL/verify-google-code'),
body: {'server_auth_code': code},
);
}
6. 登录与登出操作
新版登录/登出 API 更简洁,且语义更清晰:
dart
// 登录(触发弹窗授权)
Future<void> _signIn() async {
try {
await GoogleSignIn.instance.authenticate();
} catch (e) {
print('Sign in failed: $e');
}
}
// 登出(清除本地授权记录)
Future<void> _signOut() async {
try {
// disconnect() 会彻底清除授权,适合需要完全登出的场景
// 若需保留授权(下次自动登录),可使用 signOut() 替代
await GoogleSignIn.instance.disconnect();
} catch (e) {
print('Sign out failed: $e');
}
}
关键区别:
disconnect():清除本地所有授权信息,下次登录需重新弹窗授权signOut():仅切换登录状态,保留授权信息,下次可通过attemptLightweightAuthentication()自动登录
四、UI 构成(三端适配)
UI 设计围绕「登录状态」切换,核心是区分「未登录」和「已登录」两种场景,同时适配 Web 端原生按钮。
1. 未登录状态
- 移动端 :显示自定义登录按钮,点击触发
_signIn() - Web 端 :通过
GoogleSignInWeb.renderButton()渲染 Google 官方按钮,样式与 Google 生态一致
dart
Widget _buildUnauthenticatedUI() {
return Platform.isWeb
? // Web 端:渲染原生按钮
HtmlElementView(
viewType: 'google-sign-in-button',
onPlatformViewCreated: (int viewId) {
// 初始化 Web 按钮
GoogleSignInWeb.renderButton(
container: document.getElementById('google-sign-in-button'),
onPressed: _signIn,
text: 'signin_with', // 按钮文本(signin_with/continue_with)
theme: 'outline', // 按钮样式(outline/filled_black/filled_white)
);
},
)
: // 移动端:自定义按钮
ElevatedButton(
onPressed: _signIn,
child: const Text('Sign in with Google'),
);
}
2. 已登录状态
展示用户信息 + 功能按钮,核心元素包括:
- 用户头像(
user.photoUrl)与用户名(user.displayName) - 联系人列表 + 刷新按钮(触发
_fetchContacts()) - 授权 Scope 按钮(触发
_requestScopeAuthorization()) - 获取 Server Code 按钮(触发
_getRequestServerAuthCode()) - 登出按钮(触发
_signOut())
dart
Widget _buildAuthenticatedUI() {
return Column(
children: [
// 用户信息
CircleAvatar(
backgroundImage: NetworkImage(_currentUser!.photoUrl ?? ''),
),
Text('Welcome, ${_currentUser!.displayName}'),
const SizedBox(height: 20),
// 联系人相关
ElevatedButton(
onPressed: _fetchContacts,
child: const Text('Refresh Contacts'),
),
Expanded(
child: ListView.builder(
itemCount: _contacts.length,
itemBuilder: (context, index) => ListTile(
title: Text(_contacts[index]),
),
),
),
// 其他功能按钮
ElevatedButton(
onPressed: _requestScopeAuthorization,
child: const Text('Authorize Contacts Scope'),
),
ElevatedButton(
onPressed: _getRequestServerAuthCode,
child: const Text('Get Server Auth Code'),
),
ElevatedButton(
onPressed: _signOut,
child: const Text('Sign Out'),
),
],
);
}
五、关键注意事项(避坑指南)
1. 必须配置正确的 Client ID
这是最常见的错误来源,不同平台的 Client ID 完全独立,配置错误会直接导致授权失败(401/403 错误):
- Android :从
google-services.json中获取client_id(需放在app/目录下) - iOS :从
GoogleService-Info.plist中获取CLIENT_ID(需添加到 Xcode 项目) - Web :在 Google Cloud 控制台 创建「Web 应用」类型的 Client ID
- Server Client ID:用于获取 Server Auth Code,需创建「后端应用」类型的 Client ID
2. 启用 Google People API(如需获取联系人)
若需调用 People API,需在 Google Cloud 控制台手动启用:
- 进入 API 库
- 搜索「Google People API」并启用
- 确保已为 Client ID 添加「People API」的访问权限
3. Web 端跨域问题
Web 端调试时需配置跨域允许列表:
- 进入 Google Cloud 控制台 → 你的项目 →「API 和服务」→「凭据」
- 找到 Web 端 Client ID,编辑「已获授权的 JavaScript 来源」
- 添加调试地址(如
http://localhost:5555)
六、总结
新版 google_sign_in 插件通过「事件流监听」「统一授权客户端」「多端 API 对齐」三大设计,解决了旧版在复杂场景下的痛点,核心优势可总结为:
| 能力 | 说明 |
|---|---|
| 认证事件流 | 基于 Stream 实时响应登录/登出,适配跨页面状态同步 |
authorizationClient |
统一处理 Scope 授权、Headers 获取、Server Code 生成,API 更聚合 |
| 多端适配 | Web/移动端共享核心逻辑,Web 端原生按钮降低开发成本 |
| 轻量级认证 | 免弹窗自动登录,提升用户体验 |
| 动态 Scope 授权 | 支持非首次登录时补充授权,无需重新登录 |
整体而言,新版 API 更符合 Flutter 跨平台设计理念,推荐所有使用 Google 登录的 Flutter 项目尽快迁移。如需完整示例代码,可参考 google_sign_in 官方示例仓库。