---
# System prepended metadata

title: Flutter - google_sign_in - google 第三方登入
tags: [flutter]

---

# Flutter - google_sign_in - google 第三方登入

## 簡介
google_sign_in 是一個 Flutter 套件，用於實現 Google 登錄功能。它讓用戶可以通過 Google 帳戶進行身份驗證，以下是常用的套件功能：
* Google 登錄：讓用戶可以使用 Google 帳戶輕鬆登入應用程式。
* 獲取用戶基本資訊：如姓名、電子郵件地址、頭像等。
* 多平台支持：適用於 Android、iOS 和 Web 平台。
* 登出功能：支援用戶退出其 Google 帳戶。
* 安全性：基於 OAuth 2.0 協議，確保用戶數據的安全性。

```
flutter pub add google_sign_in
```

[官網](https://pub.dev/packages/google_sign_in)

## 實作
### 前置
1. 在 Firebase Console 新增 Android 專案，並添加 SHA-1 及 SHA-256
2.  到 GCP 建立 OAuth 2.0 用戶端 ID  ，若沒有 SHA-1 簽署憑證指紋，可以參考[官網或下方流程](https://developer.android.com/studio/publish/app-signing?hl=zh-tw)，有用過相關服務會自己建立可以檢查 client id 是否對的上就好
    * 如果才在 debug 階段，可以下以下指令取得：
        ```
        keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android
        ```
    * 如果部署上 firebase，可在 Firebase > App Distribution 取得：![image](https://hackmd.io/_uploads/HJwXh4m5Jl.png)
    * 如果部署上 play 商店可從，測試及發布 > 設定 > 應用程式簽署 取得
2. 填寫 OAuth 同意畫面需要的所有資訊就算非必填也要填寫
    ![截圖 2024-12-03 下午5.10.58](https://hackmd.io/_uploads/ryAFiShmke.png)
3. 到 GCP [啟用 Google People API](https://console.cloud.google.com/apis/library/people.googleapis.com)
4. 到 firebase 建立專案並下載 google-services.json 放到專案資料夾 android > app 底下

### 實作
```dart
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:google_sign_in/google_sign_in.dart';

// 需要請求的權限
GoogleSignIn _googleSignIn = GoogleSignIn(
  scopes: [
      'email',
    ]
);

class SignInDemo extends StatefulWidget {
  const SignInDemo({super.key});

  @override
  State createState() => _SignInDemoState();
}

class _SignInDemoState extends State<SignInDemo> {
  GoogleSignInAccount? _currentUser;
  String idToken = '';
  String accessToken = '';
  String email = '';
  String displayName = '';
  String id = '';
  String photoUrl = '';
  String serverAuthCode = '';

  @override
  void initState() {
    super.initState();
    
    // 登入人員有進行異動
    _googleSignIn.onCurrentUserChanged
        .listen((GoogleSignInAccount? account) async {
      GoogleSignInAuthentication googleSignInAuthentication;
      if (account != null) {
        googleSignInAuthentication = await account.authentication;
        idToken = googleSignInAuthentication.idToken ?? '';
        accessToken = googleSignInAuthentication.accessToken ?? '';
        serverAuthCode = account.serverAuthCode ?? '';
      }
      setState(() {
        _currentUser = account;
        email = account?.email ?? '';
        displayName = account?.displayName ?? '';
        id = account?.id ?? '';
        photoUrl = account?.photoUrl ?? '';
      });
    });

    _googleSignIn.signInSilently();
  }

  Future<void> _handleSignIn() async {
    try {
      await _googleSignIn.signIn();
    } catch (error) {
      if (!mounted) return; // 避免在 widget 被卸載後繼續使用 context
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('登入失敗：$error')),
      );
      print(error);
    }
  }
  // #enddocregion SignIn

  Future<void> _handleSignOut() => _googleSignIn.disconnect();

  Widget _buildBody() {
    final GoogleSignInAccount? user = _currentUser;
    if (user != null) {
      // The user is Authenticated
      return SingleChildScrollView(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.spaceAround,
          children: <Widget>[
            ListTile(
              leading: GoogleUserCircleAvatar(
                identity: user,
              ),
              title: Text('${user.displayName}'),
              subtitle: Text(user.email),
            ),
            const Divider(),
            copyListTile(title: 'email', subtitle: email),
            copyListTile(title: 'displayName', subtitle: displayName),
            copyListTile(title: 'id', subtitle: id),
            copyListTile(title: 'photoUrl', subtitle: photoUrl),
            const Divider(),
            copyListTile(title: 'serverAuthCode', subtitle: serverAuthCode),
            copyListTile(title: 'idToken', subtitle: idToken),
            copyListTile(title: 'accessToken', subtitle: accessToken),
            const Divider(),
            const Text('Signed in successfully.'),
            const Divider(),
            ElevatedButton(
              onPressed: _handleSignOut,
              child: const Text('SIGN OUT'),
            ),
          ],
        ),
      );
    } else {
      // The user is NOT Authenticated
      return Column(
        mainAxisAlignment: MainAxisAlignment.spaceAround,
        children: <Widget>[
          const Text('You are not currently signed in.'),
          ElevatedButton(
            onPressed: _handleSignIn,
            child: const Text('SIGN IN'),
          )
        ],
      );
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: const Text('Google Sign In'),
        ),
        body: ConstrainedBox(
          constraints: const BoxConstraints.expand(),
          child: _buildBody(),
        ));
  }

  ListTile copyListTile({required String title, required String subtitle}) {
    return ListTile(
      title: Text(title),
      subtitle: Text(subtitle),
      onLongPress: () async {
        await Clipboard.setData(ClipboardData(text: subtitle)).then(
          (_) {
            ScaffoldMessenger.of(context).showSnackBar(
              SnackBar(
                content: Text('已複製 $title 到剪貼簿'),
              ),
            );
          },
        );
      },
    );
  }
}

```

## 坑
1. 
    ```
    com.google.android.gms.common.api.ApiException: 10
    ```
    * 填寫 OAuth 同意畫面需要的所有資訊就算非必填也要 https://github.com/flutter/flutter/issues/33393#issuecomment-510395178
    * 確認 SHA-1 及 SHA-256 正確


###### tags: `flutter`