大家好,歡迎來到第十四天!在 Day 13,我們完成了 Firebase 專案的設定。今天,我們要來實作 Crew Up! 的認證系統,讓使用者能夠安全地登入和使用我們的 App。
Firebase Authentication 是 Google 提供的完整認證解決方案,支援多種登入方式,包括 Google、Facebook、Email/Password 等。對於 Crew Up! 這樣的社交應用,我們選擇 Google 登入作為主要認證方式,因為:
在 Crew Up! 專案中,我們遵循 Clean Architecture 原則,將認證邏輯分層實作:
Data Layer - AuthRemoteDataSource
注意:以下是簡化版本,適合理解基本概念。實際生產環境會包含重試機制、詳細錯誤處理、Token 管理、網路感知等進階功能。
// lib/features/auth/data/datasources/auth_remote_datasource.dart
// (imports omitted)
class AuthRemoteDataSource implements AuthDataSource {
final FirebaseAuth _firebaseAuth;
AuthRemoteDataSource({
FirebaseAuth? firebaseAuth,
}) : _firebaseAuth = firebaseAuth ?? FirebaseAuth.instance;
@override
Future<UserModel> signInWithGoogle() async {
final GoogleSignIn googleSignIn = GoogleSignIn.instance;
final GoogleSignInAccount googleUser = await googleSignIn.authenticate();
final GoogleSignInAuthentication googleAuth = googleUser.authentication;
final credential = GoogleAuthProvider.credential(
accessToken: googleAuth.accessToken,
idToken: googleAuth.idToken,
);
final userCredential = await _firebaseAuth.signInWithCredential(credential);
final firebaseUser = userCredential.user;
if (firebaseUser == null) {
throw AppException.unknown('Google 登入失敗:無法獲取使用者資訊');
}
return UserModel.fromFirebaseUser(firebaseUser);
}
}
Repository Layer - AuthRepositoryImpl
注意:以下是簡化版本,實際專案會繼承 EnhancedBaseRepository 並使用統一的錯誤處理策略。
// lib/features/auth/data/repositories/auth_repository_impl.dart
// (imports omitted)
class AuthRepositoryImpl implements AuthRepository {
final AuthDataSource _remoteDataSource;
AuthRepositoryImpl({required AuthDataSource remoteDataSource})
: _remoteDataSource = remoteDataSource;
@override
Future<Result<User>> signInWithGoogle() async {
final userModel = await _remoteDataSource.signInWithGoogle();
return Success(userModel.toEntity());
}
@override
Stream<Result<User?>> authStateChanges() {
return _remoteDataSource.authStateChanges().map(
(userModel) => Success(userModel?.toEntity()),
);
}
}
Presentation Layer - AuthNotifier
AuthNotifier 現在完全依賴 Firebase SDK 的認證狀態管理,提供簡潔且可靠的認證流程。詳細實作請參考下方的「Firebase SDK 的自動認證狀態管理」章節。
現在讓我們看看如何在 UI 層面使用這些認證功能:
// lib/features/auth/presentation/screens/login_screen.dart
// (imports omitted)
class LoginScreen extends ConsumerWidget {
const LoginScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final authState = ref.watch(authNotifierProvider);
final localizations = S.of(context);
return Scaffold(
backgroundColor: AppColors.surfaceBackground2,
body: SafeArea(
child: Padding(
padding: AppSpacing.horizontalL,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Logo
SvgPicture.asset('assets/login/logo.svg', width: 137, height: 54),
const SizedBox(height: AppSpacing.xl),
// Google 登入按鈕
SizedBox(
width: double.infinity,
height: 48,
child: ElevatedButton(
onPressed: authState.isLoading ? null : () => _handleGoogleSignIn(ref),
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.surfaceWhite,
foregroundColor: AppColors.textTertiary,
shape: const RoundedRectangleBorder(
borderRadius: AppRadius.buttonMediumRadius,
side: BorderSide(color: AppColors.surfaceBorder),
),
),
child: authState.isLoading
? const CircularProgressIndicator(color: AppColors.textTertiary)
: Text(
localizations.googleLogin,
style: AppTypography.body1.copyWith(
color: AppColors.textTertiary,
fontWeight: FontWeight.bold,
),
),
),
),
],
),
),
),
);
}
void _handleGoogleSignIn(WidgetRef ref) async {
await ref.read(authNotifierProvider.notifier).signInWithGoogle();
}
}
Firebase Authentication SDK 提供了完整的認證狀態管理,包括自動 Token 刷新、跨 App 重啟的狀態持久化等。在 CrewUp 專案中,我們完全依賴 Firebase SDK 的內建機制:
// lib/features/auth/presentation/providers/auth_provider.dart
// (imports omitted)
// Repository Provider - 依賴注入 Repository 實作
@riverpod
AuthRepository authRepository(Ref ref) {
final remoteDataSource = ref.read(authRemoteDataSourceProvider);
return AuthRepositoryImpl(remoteDataSource);
}
// DataSource Provider - 提供 Firebase 認證資料來源
@riverpod
AuthDataSource authRemoteDataSource(Ref ref) => AuthRemoteDataSource();
// Use Case Provider - 提供 Google 登入業務邏輯
@riverpod
SignInWithGoogleUseCase signInWithGoogleUseCase(Ref ref) {
final repository = ref.read(authRepositoryProvider);
return SignInWithGoogleUseCase(repository);
}
// Auth State Provider - 完全依賴 Firebase SDK 的認證狀態管理
@riverpod
class AuthNotifier extends _$AuthNotifier {
StreamSubscription<Result<domain.User?>>? _authSubscription;
@override
Future<domain.User?> build() async {
final repository = ref.read(authRepositoryProvider);
// 監聽認證狀態變化 - Firebase SDK 是唯一的真相來源
_authSubscription = repository.authStateChanges().listen((result) {
result.fold(
(user) => state = AsyncValue.data(user),
(error) => state = AsyncValue.error(error, StackTrace.current),
);
});
// Provider 釋放時取消訂閱 - 避免記憶體洩漏
ref.onDispose(() {
_authSubscription?.cancel();
});
// 獲取當前用戶 - 支援 App 重啟後的狀態恢復
final currentUserResult = await repository.getCurrentUser();
return currentUserResult.fold(
(user) => user,
(error) => null,
);
}
/// Google 登入 - 使用 UseCase 處理業務邏輯
Future<void> signInWithGoogle() async {
try {
state = const AsyncValue.loading();
final signInUseCase = ref.read(signInWithGoogleUseCaseProvider);
final user = await signInUseCase();
state = AsyncValue.data(user);
} on Exception catch (e) {
state = AsyncValue.error(e, StackTrace.current);
}
}
}
Firebase SDK 使用雙 Token 系統來管理認證狀態:
自動刷新機制:
// Firebase SDK 會自動處理 Token 刷新
// 當 ID Token 即將過期時,SDK 會自動使用 Refresh Token 獲取新的 ID Token
// 開發者無需手動處理這個過程
Firebase SDK 會自動將認證狀態保存到本地儲存,當 App 重新啟動時,會自動恢復用戶的登入狀態:
// 在 App 啟動時,Firebase SDK 會自動檢查本地儲存的認證狀態
// 如果找到有效的 Refresh Token,會自動刷新 ID Token
// 這確保了用戶在 App 重啟後仍然保持登入狀態
在 Crew Up! 專案中,我們使用 Riverpod 來統一管理認證狀態:
// 在任何 Widget 中都可以監聽認證狀態
final authState = ref.watch(authNotifierProvider);
authState.when(
data: (user) {
if (user != null) {
// 用戶已登入
return HomeScreen();
} else {
// 用戶未登入
return LoginScreen();
}
},
loading: () => LoadingScreen(),
error: (error, stack) => ErrorScreen(error),
);
在認證流程中,我們需要處理各種可能的錯誤情況:
// 網路錯誤
if (error is SocketException) {
// 顯示網路連接錯誤
}
// 用戶取消登入
if (error is GoogleSignInException &&
error.code == GoogleSignInExceptionCode.canceled) {
// 用戶主動取消,不顯示錯誤
}
// 其他認證錯誤
else {
// 顯示一般錯誤訊息
}
今天我們完成了 Crew Up! 的 Firebase Authentication 整合:
核心成果
技術要點
下一步
明天,我們將深入探討 Cloud Firestore 的實作,學習如何在 Clean Architecture 中整合 NoSQL 資料庫,建立可擴展的資料架構,並實作活動管理、用戶資料等核心功能。
期待與您在 Day 15 相見!