Flutter 新版 Google Sign-In 插件完整解析(含示例讲解)

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 新版核心能力,可直接作为项目模板使用,包含:

  1. 实时监听登录/登出状态(基于 AuthenticationEvent
  2. 检查用户是否已授权目标 Scope
  3. 调用 Google People API 获取联系人信息
  4. 动态请求未授权的 Scope(非首次登录时补充授权)
  5. 获取 Server Auth Code(用于后端与 Google 服务器验证)
  6. 适配 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 控制台手动启用:

  1. 进入 API 库
  2. 搜索「Google People API」并启用
  3. 确保已为 Client ID 添加「People API」的访问权限

3. Web 端跨域问题

Web 端调试时需配置跨域允许列表:

  1. 进入 Google Cloud 控制台 → 你的项目 →「API 和服务」→「凭据」
  2. 找到 Web 端 Client ID,编辑「已获授权的 JavaScript 来源」
  3. 添加调试地址(如 http://localhost:5555

六、总结

新版 google_sign_in 插件通过「事件流监听」「统一授权客户端」「多端 API 对齐」三大设计,解决了旧版在复杂场景下的痛点,核心优势可总结为:

能力 说明
认证事件流 基于 Stream 实时响应登录/登出,适配跨页面状态同步
authorizationClient 统一处理 Scope 授权、Headers 获取、Server Code 生成,API 更聚合
多端适配 Web/移动端共享核心逻辑,Web 端原生按钮降低开发成本
轻量级认证 免弹窗自动登录,提升用户体验
动态 Scope 授权 支持非首次登录时补充授权,无需重新登录

整体而言,新版 API 更符合 Flutter 跨平台设计理念,推荐所有使用 Google 登录的 Flutter 项目尽快迁移。如需完整示例代码,可参考 google_sign_in 官方示例仓库

相关推荐
nono牛12 小时前
Gatekeeper 的精确定义
android
stevenzqzq14 小时前
android启动初始化和注入理解3
android
LawrenceLan15 小时前
Flutter 零基础入门(九):构造函数、命名构造函数与 this 关键字
开发语言·flutter·dart
一豆羹15 小时前
macOS 环境下 ADB 无线调试连接失败、Protocol Fault 及端口占用的深度排查
flutter
行者9615 小时前
OpenHarmony上Flutter粒子效果组件的深度适配与实践
flutter·交互·harmonyos·鸿蒙
城东米粉儿15 小时前
compose 状态提升 笔记
android
冰淇淋真好吃15 小时前
iOS实现 WKWebView 长截图的优雅方案
ios
粤M温同学16 小时前
Android 实现沉浸式状态栏
android
ljt272496066116 小时前
Compose笔记(六十八)--MutableStateFlow
android·笔记·android jetpack