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 官方示例仓库

相关推荐
向哆哆4 小时前
打造高校四六级报名管理系统:基于 Flutter × OpenHarmony 的跨端开发实践
flutter·开源·鸿蒙·openharmony·开源鸿蒙
2501_940007894 小时前
Flutter for OpenHarmony三国杀攻略App实战 - 设置功能实现
flutter
lbb 小魔仙5 小时前
【Harmonyos】开源鸿蒙跨平台训练营DAY9:获取分类数据并渲染
flutter·华为·harmonyos
mocoding6 小时前
Flutter 3D 翻转动画flip_card三方库在鸿蒙版天气预报卡片中的实战教程
flutter·3d·harmonyos
JMchen1237 小时前
现代Android图像处理管道:从CameraX到OpenGL的60fps实时滤镜架构
android·图像处理·架构·kotlin·android studio·opengl·camerax
2601_949809597 小时前
flutter_for_openharmony家庭相册app实战+我的Tab实现
java·javascript·flutter
快点好好学习吧8 小时前
phpize 依赖 php-config 获取 PHP 信息的庖丁解牛
android·开发语言·php
是誰萆微了承諾8 小时前
php 对接deepseek
android·开发语言·php
2601_949868368 小时前
Flutter for OpenHarmony 电子合同签署App实战 - 已签合同实现
java·开发语言·flutter
Dxy12393102168 小时前
MySQL如何加唯一索引
android·数据库·mysql