# Firebase Auth & Google Sign-In for Flutter When integrating Firebase Authentication and Google Sign-In into Flutter apps targeting cross-platform environments (like Mobile + Web), you must navigate several breaking changes introduced in `google_sign_in` 7.x+ and some platform-specific quirks. ## 1. `google_sign_in` 7.2.0 API Changes - **Method Renamed**: The `signIn()` method is deprecated/removed and has been replaced with `authenticate()`. - **Token Separation**: The `GoogleSignInAuthentication` object no longer packages both identity and authorization tokens together. Initial authentication now only provides the `idToken`. If an `accessToken` is required for Google APIs, you must explicitly request server authorization separately. ## 2. Initialization & Web Hang/Crash Pitfalls - **Initialization Requirement**: In 7.x, you must call `await GoogleSignIn.instance.initialize();` globally before using the plugin. - **Web Client ID Constraint**: On Flutter Web, if you call `initialize()` without passing a `clientId` argument OR specifying the `` tag in `web/index.html`, the Dart Web Debug Service (DWDS) and the app will throw an assertion error and **hang infinitely**, resulting in a blank screen. - **Common Workaround**: If you intend to use Firebase Auth's `signInWithPopup(GoogleAuthProvider())` for the web, you can conditionally skip the local `GoogleSignIn` package initialization entirely: ```dart import 'package:flutter/foundation.dart' show kIsWeb; if (!kIsWeb) { await GoogleSignIn.instance.initialize(); } ``` ## 3. Web Logout Crashes - If you bypassed `GoogleSignIn` initialization on the web (as demonstrated above), you cannot call its `signOut()` method later. Attempting to execute `await GoogleSignIn.instance.signOut();` during the user's logout flow on the Web platform evaluates against an uninitialized context or unsupported environment, crashing the app. - **Solution**: Conditionally separate the logout logic for Web to rely entirely on `FirebaseAuth`: ```dart if (!kIsWeb) { await GoogleSignIn.instance.signOut(); } await FirebaseAuth.instance.signOut(); ``` ## 4. Prototyping Workaround: Bypassing Firestore Composite Indices *Note: This is a Firestore consideration frequently encountered while fetching user-specific auth data.* When querying data via `FirebaseFirestore.instance`, using `.where('userId', isEqualTo: uid)` combined with a sort on a different field like `.orderBy('createdAt', descending: true)` mandates a custom composite index. - **Quick Alternative**: During local development, you can avoid defining indexes by pulling the data using only `.where()` and applying the `.sort()` operation client-side on the resulting `List` in Dart. ## 5. Robust `AuthService` Boilerplate Here is a comprehensive `AuthService` implementation that properly handles the initialization and platform differences between Flutter Web and Mobile: ```dart import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter/foundation.dart'; import 'package:google_sign_in/google_sign_in.dart'; class AuthService { final FirebaseAuth _auth = FirebaseAuth.instance; AuthService() { if (!kIsWeb) { GoogleSignIn.instance.initialize(); } } // Stream to listen to auth state changes Stream get authStateChanges => _auth.authStateChanges(); // Get current user User? get currentUser => _auth.currentUser; // Google Sign-In Future signInWithGoogle() async { try { if (kIsWeb) { // Web uses popup to avoid DWDS hangs and manual client ID config GoogleAuthProvider authProvider = GoogleAuthProvider(); return await _auth.signInWithPopup(authProvider); } else { // Mobile uses standard flow final GoogleSignInAccount? googleUser = await GoogleSignIn.instance.authenticate(); if (googleUser == null) return null; // Cancelled final GoogleSignInAuthentication googleAuth = await googleUser.authentication; final AuthCredential credential = GoogleAuthProvider.credential( idToken: googleAuth.idToken, ); return await _auth.signInWithCredential(credential); } } catch (e) { print("Error during Google Sign-In: \$e"); return null; } } // Sign out Future signOut() async { try { if (!kIsWeb) { await GoogleSignIn.instance.signOut(); } await _auth.signOut(); } catch (e) { print("Error signing out: \$e"); } } } ``` ## 6. Troubleshooting `auth/unauthorized-domain` on Flutter Web When running Flutter Web locally and using `signInWithPopup`, you might encounter a situation where the Google Sign-In popup opens and immediately closes. - **Symptom**: The console shows `Sign-in failed: [firebase_auth/unauthorized-domain] This domain is not authorized for OAuth operation for your Firebase project.` - **Cause**: The domain (usually `localhost` during local testing) is not listed in the Authorized Domains in the Firebase Console. - **Solution**: Add `localhost` to the Authorized Domains list in the Firebase Console (Authentication > Settings > Authorized domains) or in your `firebase.json` auth config. - **CRITICAL**: Do NOT include the protocol or port number when adding the domain (e.g., use `localhost`, NOT `http://localhost:9090`). Flutter Web often runs on random ports or specific ports, but Firebase Auth only cares about the domain.