在 Flutter 修復 Android 的 Google 登入(google_sign_in v7.1.1)
Last updated on

在 Flutter 修復 Android 的 Google 登入(google_sign_in v7.1.1)

software-development

在 Flutter 修復 Android 的 Google 登入(google_sign_in v7.1.1)

問題描述

在 Flutter 專案中,iOS 的 Google 登入運作正常,但 Android 端卻出現 GoogleSignInException.clientConfigurationError,即便 Firebase 設定與 SHA 憑證都已正確配置。

環境:

  • Flutter SDK: 3.9.0+
  • google_sign_in: 7.1.1
  • firebase_auth: 5.7.0
  • 平台:Android

釐清的根因

1. 缺少 Android 相依套件

Android 專案的 build.gradle 少了 Google Play Services 的驗證函式庫,導致登入流程無法正確執行。

2. 錯誤的 GoogleSignIn API 使用方式

google_sign_in 套件在 v7.1.1 與舊版有相容性變更:

  • 採用 GoogleSignIn.instance 的單例模式
  • 初始化方式改為以 initialize() 並需要 serverClientId
  • 驗證流程方法名稱與行為改變
  • GoogleSignInAuthentication 權杖結構調整

3. 未設定 Web Client ID

Android 必須顯式設定 OAuth 2.0 的 Web Client ID(client_type=3),有別於某些 iOS 場景能在未設定下仍可運作。

解決步驟

步驟一:加入必要的 Android 相依套件

android/app/build.gradle.kts

dependencies {
    implementation("com.google.android.gms:play-services-auth:21.2.0")
}

這個相依套件是 Android 端 Google 登入必不可少的基礎。

步驟二:更新 GoogleSignIn 的實作

錯誤範例(v7.1.1 不適用):

// 錯誤的初始化方式
final authRepositoryProvider = Provider<AuthRepository>((ref) {
  return AuthRepository(
    FirebaseAuth.instance,
    googleSignIn: GoogleSignIn.instance, // 缺少必要的設定
  );
});

// 錯誤的登入流程
Future<User?> signInWithGoogle() async {
  // 使用不存在的方法
  await googleSignIn!.initialize();
  if (!googleSignIn!.supportsAuthenticate()) {
    throw AuthException('Not supported');
  }
  final googleUser = await googleSignIn!.authenticate();
  // ...
}

正確作法(v7.1.1):

// 正確:以單例並搭配初始化設定
final _googleSignIn = GoogleSignIn.instance;
bool _googleSignInInitialized = false;

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

// 正確的登入流程
Future<User?> signInWithGoogle() async {
  if (googleSignIn == null) {
    throw AuthException('Google Sign-In not configured');
  }

  try {
    // 僅初始化一次,並帶入 Web Client ID
    if (!_googleSignInInitialized) {
      await googleSignIn!.initialize(
        serverClientId: 'YOUR_WEB_CLIENT_ID.apps.googleusercontent.com',
      );
      _googleSignInInitialized = true;
    }
    
    // v7.1.1 使用 authenticate()
    final GoogleSignInAccount googleUser = await googleSignIn!.authenticate();

    // 取得權杖
    final GoogleSignInAuthentication googleAuth = googleUser.authentication;

    // 建立 Firebase 憑證(注意:v7.1.1 僅有 idToken,沒有 accessToken)
    final credential = GoogleAuthProvider.credential(
      idToken: googleAuth.idToken,
    );

    // 登入 Firebase
    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}');
  }
}

步驟三:修正權杖使用方式

錯誤作法:

// v7.1.1 沒有 accessToken
final credential = GoogleAuthProvider.credential(
  accessToken: googleAuth.accessToken, // 這個屬性不存在
  idToken: googleAuth.idToken,
);

正確作法:

// v7.1.1 僅使用 idToken
final credential = GoogleAuthProvider.credential(
  idToken: googleAuth.idToken,
);

步驟四:更新靜默登入(silent sign-in)

Future<User?> signInWithGoogleSilently() async {
  if (googleSignIn == null) {
    return null;
  }

  try {
    // 需要時再初始化
    if (!_googleSignInInitialized) {
      await googleSignIn!.initialize(
        serverClientId: 'YOUR_WEB_CLIENT_ID.apps.googleusercontent.com',
      );
      _googleSignInInitialized = true;
    }
    
    // v7.1.1 使用 attemptLightweightAuthentication 進行靜默登入
    final googleUser = await googleSignIn!.attemptLightweightAuthentication();
    
    if (googleUser == null) {
      return null;
    }

    final GoogleSignInAuthentication googleAuth = googleUser.authentication;
    
    final credential = GoogleAuthProvider.credential(
      idToken: googleAuth.idToken,
    );

    final userCredential = await _firebaseAuth.signInWithCredential(credential);
    return userCredential.user;
  } catch (e) {
    return null; // 靜默登入失敗可靈活忽略
  }
}

設定檢查清單

1. Firebase 主控台設定

  • 在 Authentication 啟用 Google 登入
  • 新增 Android App 並填入正確 Package Name
  • 下載並放置 google-services.jsonandroid/app/

2. SHA 憑證

  • 在 Firebase Console 新增 Debug SHA-1
  • 新增 Release SHA-1(正式版必填)

取得 Debug SHA-1:

keytool -list -v -keystore ~/.android/debug.keystore \
  -alias androiddebugkey -storepass android -keypass android

3. OAuth 2.0 用戶端 ID

  • 在 Firebase Console 或 Google Cloud Console 找到 Web Client ID(client_type=3)
  • initialize()serverClientId 帶入此 ID

google-services.json 範例:

{
  "oauth_client": [
    {
      "client_id": "xxx.apps.googleusercontent.com",
      "client_type": 3  // Android 端需要的 Web Client ID
    }
  ]
}

google_sign_in v7.1.1 的差異重點

面向舊版v7.1.1
初始化直接建立實例GoogleSignIn.instance 單例
設定方式以建構子參數initialize(serverClientId)
登入方法signIn()authenticate()
靜默登入signInSilently()attemptLightweightAuthentication()
權杖accessToken + idTokenidToken
平台檢查supportsAuthenticate()不需要

常見陷阱

  1. 沒加入 play-services-auth 相依 → Android 端無法運作
  2. 使用錯的 Client ID → Android 必須使用 Web Client ID(type 3)
  3. 忘記初始化 → 認證前需以 serverClientId 呼叫 initialize()
  4. 使用已棄用/變更的方法 → 升版請檢查 API 變更
  5. 少填 SHA 憑證 → Debug 與 Release 都要填

測試建議

  1. 變更後請重新建置:

    flutter clean && flutter pub get
  2. 優先用實機測試(模擬器可能有 Google Play 服務問題)

  3. 觀察詳細日誌:

    flutter run --verbose
  4. 驗證 Firebase 設定:

    flutterfire configure

結論

升級到 google_sign_in v7.1.1 需要特別留意初始化方式、登入流程方法與權杖處理。Android 端的關鍵在於補齊 Play Services 相依,並在初始化時使用 Web Client ID。

參考資料

FlutterAndroidGoogle 登入google_sign_infirebase_authFirebaseOAuth 2.0Google Play 服務Dart疑難排解