Flutter Google Sign-In v7.1.1 + Firebase Auth 安裝與設定完整指南(Android/iOS)

Flutter Google Sign-In v7.1.1 + Firebase Auth 安裝與設定完整指南(Android/iOS)

software-development

Flutter Google Sign-In 完整安裝配置指南

本文基於實際開發經驗,詳細介紹如何在 Flutter 應用中正確配置 Google Sign-In 功能,包含 Android 和 iOS 平台的完整設定流程。

概述

Google Sign-In 是現代移動應用中最常用的第三方登入方式之一。本文將詳細介紹如何在 Flutter 應用中整合 Google Sign-In v7.1.1 版本與 Firebase Authentication,確保在 Android 和 iOS 平台上都能正常運作。

技術棧

  • Flutter: 3.9.0+
  • Firebase Auth: 5.3.2+
  • Google Sign-In: 7.1.1
  • Riverpod: 2.5.1(狀態管理)

前置需求

  1. Flutter 開發環境已設定完成
  2. Firebase 專案已建立
  3. Android 和 iOS 應用已在 Firebase Console 中註冊

步驟 1:安裝相依套件

pubspec.yaml 中添加必要的相依套件:

dependencies:
  firebase_auth: ^5.3.2
  google_sign_in: ^7.1.1
  flutter_riverpod: ^2.5.1

dev_dependencies:
  mockito: ^5.4.4  # 用於單元測試

執行安裝:

flutter pub get

步驟 2:Firebase Console 配置

2.1 啟用 Google Sign-In

  1. 前往 https://console.firebase.google.com 進入 Firebase Console
  2. 選擇你的專案
  3. 導航至 Authentication → Sign-in method
  4. 點擊 Google 並啟用
  5. 設定專案的公開名稱和支援電子郵件

2.2 配置 Android 應用

  1. 進入 Project Settings → General → Android App
  2. 添加 SHA 指紋證書

取得 SHA 指紋:

# Debug 金鑰
keytool -list -v -alias androiddebugkey -keystore ~/.android/debug.keystore -storepass android -keypass android

# Release 金鑰(路徑與密碼請替換為你的設定)
keytool -list -v -keystore /path/to/your/upload-keystore.jks -alias upload -storepass YOUR_STORE_PASSWORD -keypass YOUR_KEY_PASSWORD

將取得的 SHA1 與 SHA-256 指紋添加到 Firebase Console:

  • Debug SHA1: 3D:35:44:E4:24:52:E9:90:71:4C:6D:B3:0E:17:11:E8:32:BE:C3:6B
  • Debug SHA-256: AC:FF:91:CB:CF:12:74:A0:B9:61:01:D5:6D:AE:D4:8B:06:02:12:6B:7D:06:3B:6B:00:F0:E9:E6:24:B8:00:22
  • Release SHA1: A3:6E:5B:2B:0D:D5:EA:26:33:F8:0B:36:F8:E8:FB:D8:04:9F:C3:68
  • Release SHA-256: 34:22:C4:C6:8C:97:DA:FB:A7:F7:32:FD:A6:6F:8E:82:E3:E9:27:6A:D6:E5:E3:62:61:16:89:FE:B6:F5:E4:4B
  1. 下載更新後的 google-services.json
  2. 放置於 android/app/google-services.json

2.3 配置 iOS 應用

  1. 在 Firebase Console 找到你的 iOS 應用
  2. 下載 GoogleService-Info.plist
  3. 將檔案加入 Xcode 專案 ios/Runner/ 目錄

步驟 3:Android 平台設定

3.1 檢查 google-services.json

確認包含 OAuth client 配置:

{
  "project_info": { "project_id": "your-project-id" },
  "client": [
    {
      "client_info": {
        "mobilesdk_app_id": "1:123456789:android:abcdef",
        "android_client_info": { "package_name": "com.yourcompany.yourapp" }
      },
      "oauth_client": [
        {
          "client_id": "123456789-abcdef.apps.googleusercontent.com",
          "client_type": 1,
          "android_info": {
            "package_name": "com.yourcompany.yourapp",
            "certificate_hash": "your_sha1_fingerprint"
          }
        }
      ]
    }
  ]
}

3.2 build.gradle 啟用 Google Services 插件

android/app/build.gradle

apply plugin: 'com.google.gms.google-services'

步驟 4:iOS 平台設定

4.1 更新 Info.plist(URL Scheme)

ios/Runner/Info.plist 中加入:

<key>CFBundleURLTypes</key>
<array>
  <dict>
    <key>CFBundleURLSchemes</key>
    <array>
      <!-- REVERSED_CLIENT_ID from GoogleService-Info.plist -->
      <string>com.googleusercontent.apps.123456789-abcdef</string>
    </array>
  </dict>
</array>

4.2 檢查 GoogleService-Info.plist OAuth 欄位

<key>CLIENT_ID</key>
<string>123456789-abcdef.apps.googleusercontent.com</string>
<key>REVERSED_CLIENT_ID</key>
<string>com.googleusercontent.apps.123456789-abcdef</string>

步驟 5:實作 AuthRepository

建立 lib/features/auth/data/repositories/auth_repository.dart

import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:google_sign_in/google_sign_in.dart';

// Custom exception for auth errors
class AuthException implements Exception {
  final String message;
  AuthException(this.message);

  @override
  String toString() => message;
}

// Provider for AuthRepository
final authRepositoryProvider = Provider<AuthRepository>((ref) {
  return AuthRepository(
    FirebaseAuth.instance,
    googleSignIn: GoogleSignIn.instance,
  );
});

// Repository for handling authentication operations
class AuthRepository {
  final FirebaseAuth _firebaseAuth;
  final GoogleSignIn? googleSignIn;

  AuthRepository(
    this._firebaseAuth, {
    this.googleSignIn,
  });

  // Get current user
  User? get currentUser => _firebaseAuth.currentUser;

  // Stream of authentication state changes
  Stream<User?> get authStateChanges => _firebaseAuth.authStateChanges();

  // Sign in with Google - 使用 v7.1.1 API
  Future<User?> signInWithGoogle() async {
    if (googleSignIn == null) {
      throw AuthException('Google Sign-In not configured');
    }

    try {
      // Initialize GoogleSignIn if needed
      await googleSignIn!.initialize();
      
      // Check if platform supports authentication
      if (!googleSignIn!.supportsAuthenticate()) {
        throw AuthException('Google Sign-In not supported on this platform');
      }

      // Trigger the authentication flow
      final googleUser = await googleSignIn!.authenticate();

      // Obtain the auth details from the request
      final GoogleSignInAuthentication googleAuth = googleUser.authentication;

      // Create a new credential using the ID token
      final credential = GoogleAuthProvider.credential(
        idToken: googleAuth.idToken,
      );

      // Sign in to Firebase with the Google credential
      final userCredential = await _firebaseAuth.signInWithCredential(credential);
      
      return userCredential.user;
    } on GoogleSignInException catch (e) {
      if (e.code == GoogleSignInExceptionCode.canceled) {
        throw AuthException('Sign in cancelled by user');
      }
      throw AuthException('Google sign-in failed: ${e.code}');
    } catch (e) {
      if (e is AuthException) rethrow;
      if (e is FirebaseAuthException) rethrow;
      throw AuthException('Google sign-in failed: ${e.toString()}');
    }
  }

  // Sign out
  Future<void> signOut() async {
    // Sign out from Google if signed in
    if (googleSignIn != null) {
      await googleSignIn!.signOut();
    }
    // Sign out from Firebase
    await _firebaseAuth.signOut();
  }

  // Get auth error message in Traditional Chinese
  String getAuthErrorMessage(dynamic e) {
    if (e is FirebaseAuthException) {
      switch (e.code) {
        case 'account-exists-with-different-credential':
          return '此電子郵件已使用其他登入方式註冊';
        case 'invalid-credential':
          return '登入憑證無效或已過期';
        case 'sign_in_failed':
          return 'Google 登入失敗,請稍後再試';
        default:
          return '發生錯誤:${e.message ?? e.code}';
      }
    } else if (e is AuthException) {
      return e.message;
    }
    return '發生未知錯誤';
  }
}

步驟 6:UI 介面實作

建立 lib/features/auth/presentation/pages/auth_page.dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../auth/data/repositories/auth_repository.dart';

class AuthPage extends ConsumerWidget {
  const AuthPage({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return Scaffold(
      appBar: AppBar(title: const Text('登入')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            // Google Sign-In button
            ElevatedButton.icon(
              onPressed: () => _signInWithGoogle(context, ref),
              icon: const Icon(Icons.login),
              label: const Text('使用 Google 登入'),
              style: ElevatedButton.styleFrom(
                padding: const EdgeInsets.symmetric(
                  horizontal: 24,
                  vertical: 12,
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }

  Future<void> _signInWithGoogle(BuildContext context, WidgetRef ref) async {
    final authRepository = ref.read(authRepositoryProvider);

    try {
      final user = await authRepository.signInWithGoogle();
      
      if (context.mounted && user != null) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(
            content: Text('歡迎,${user.displayName}!'),
            backgroundColor: Colors.green,
          ),
        );
      }
    } catch (e) {
      if (context.mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(
            content: Text(authRepository.getAuthErrorMessage(e)),
            backgroundColor: Colors.red,
          ),
        );
      }
    }
  }
}

步驟 7:測試與驗證

7.1 執行測試

# 執行單元測試
flutter test

# 執行應用程式
flutter run

7.2 常見問題排除(FAQ)

問題 1:Sign in failed 錯誤

解法:

  • 檢查 SHA 指紋是否正確添加到 Firebase Console
  • 確認 package name 與 bundle ID 一致
  • 重新下載並替換配置檔案

問題 2:iOS 無法開啟 Google 登入頁面

解法:

  • 檢查 Info.plist 中的 URL Scheme 是否正確
  • 確認 GoogleService-Info.plist 包含 REVERSED_CLIENT_ID

問題 3:Google Sign-In API 版本差異

解法(v7.1.1 變更重點):

  • 使用 authenticate() 取代 signIn()
  • 使用 attemptLightweightAuthentication() 取代 signInSilently()
  • 僅使用 idToken,不使用 accessToken

7.3 最佳實踐

  1. 錯誤處理:為各種錯誤提供清楚使用者提示
  2. 狀態管理:使用 Riverpod 維護認證狀態
  3. 測試:撰寫單元與整合測試
  4. 安全性:避免在程式碼中硬編碼敏感資訊

完整專案結構範例

lib/
├── features/
│   └── auth/
│       ├── data/
│       │   └── repositories/
│       │       └── auth_repository.dart
│       └── presentation/
│           └── pages/
│               └── auth_page.dart
└── main.dart

android/
└── app/
    └── google-services.json

ios/
└── Runner/
    ├── GoogleService-Info.plist
    └── Info.plist

結論

透過本文的詳細指南,你應該能夠在 Flutter 應用中成功配置 Google Sign-In 功能。關鍵要點:

  1. 正確配置 Firebase Console(加入正確的 SHA 指紋與平台設定)
  2. 平台特定設定(Android/iOS 皆需對應配置檔)
  3. 使用最新 API(Google Sign-In v7.1.1 的 API 與舊版不同)
  4. 完整錯誤處理(提供良好的使用者體驗)

提醒:Google Sign-In 與 Firebase 的設定可能隨版本調整而變動,建議定期查閱官方文件以取得最新資訊。

參考與延伸閱讀(官方技術文件)

Firebase 與 Flutter

套件(pub.dev)

Android 設定

iOS 設定

本文基於實際開發專案的實作經驗,適用於 Flutter 3.9+ 版本。

FlutterFirebase AuthGoogle Sign-InAndroidiOSRiverpodOAuth登入身份驗證