# 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 取得:
* 如果部署上 play 商店可從,測試及發布 > 設定 > 應用程式簽署 取得
2. 填寫 OAuth 同意畫面需要的所有資訊就算非必填也要填寫

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`