Firebase 集成
Auth、Firestore 与推送。
Firebase 集成
这一课你将能做到
- 用 FlutterFire CLI 把 Firebase 项目接入 Flutter(Android + iOS 双端)
- 用 Firebase Auth 实现邮箱注册/登录、Google 登录,并管理登录态
- 用 Cloud Firestore 做实时数据读写,并接入 Firebase Cloud Messaging 接收推送
为什么要学
Firebase 给 Flutter 提供「开箱即用的后端」:认证、数据库、推送、崩溃分析、AB 测试。不会 Firebase,做个人项目就只能手撸后端。即使是公司项目,理解 Firebase 也有助于理解 BaaS 模式。
人话:Firebase 就像「云端全家桶」,认证、数据库、推送、分析一锅端,让你专注写 UI 和业务。
先建立图像
Flutter App
├── FirebaseAuth ← 登录 / 注册 / 登出
├── Cloud Firestore ← NoSQL 实时数据库
├── Firebase Cloud Messaging← 推送通知
├── Firebase Crashlytics ← 崩溃上报
└── Firebase Analytics ← 埋点统计
跟着做
步骤 1:创建 Firebase 项目并初始化 FlutterFire
- 打开 <https://console.firebase.google.com>,创建一个项目(比如叫
flutter-demo)。
- 安装 FlutterFire CLI:
dart pub global activate flutterfire_cli
- 在 Flutter 项目根目录运行:
flutterfire configure
按提示选 Firebase 项目、勾选 Android 和 iOS。CLI 会自动:
- 生成
firebase_options.dart(包含各平台的 API Key)
- 在 Android 项目里下载
google-services.json
- 在 iOS 项目里下载
GoogleService-Info.plist
pubspec.yaml 加依赖:
dependencies:
firebase_core: ^2.24.0
firebase_auth: ^4.16.0
cloud_firestore: ^4.15.0
firebase_messaging: ^14.7.0
firebase_crashlytics: ^3.4.0
步骤 2:初始化 Firebase
import 'package:firebase_core/firebase_core.dart';
import 'firebase_options.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
runApp(const MyApp());
}
关键:WidgetsFlutterBinding.ensureInitialized() 必须最先调用,否则原生插件可能没准备好。
步骤 3:邮箱注册 / 登录
import 'package:firebase_auth/firebase_auth.dart';
class AuthService {
final _auth = FirebaseAuth.instance;
Stream<User?> get authState => _auth.authStateChanges();
Future<UserCredential> signUp(String email, String password) {
return _auth.createUserWithEmailAndPassword(email: email, password: password);
}
Future<UserCredential> signIn(String email, String password) {
return _auth.signInWithEmailAndPassword(email: email, password: password);
}
Future<void> signOut() => _auth.signOut();
User? get currentUser => _auth.currentUser;
}
class AuthGate extends StatelessWidget {
const AuthGate({super.key});
@override
Widget build(BuildContext context) {
return StreamBuilder<User?>(
stream: AuthService().authState,
builder: (ctx, snap) {
if (snap.connectionState == ConnectionState.waiting) {
return const Scaffold(body: Center(child: CircularProgressIndicator()));
}
if (snap.hasData) return const HomePage();
return const LoginPage();
},
);
}
}
步骤 4:Google 登录
dependencies:
google_sign_in: ^6.2.0
import 'package:google_sign_in/google_sign_in.dart';
Future<UserCredential> signInWithGoogle() async {
final googleUser = await GoogleSignIn().signIn();
if (googleUser == null) throw Exception('用户取消');
final googleAuth = await googleUser.authentication;
final credential = GoogleAuthProvider.credential(
accessToken: googleAuth.accessToken,
idToken: googleAuth.idToken,
);
return FirebaseAuth.instance.signInWithCredential(credential);
}
Android 需要在 Firebase Console 下载 SHA-1 并填到项目设置;iOS 需要在 Info.plist 里加 REVERSED_CLIENT_ID。
步骤 5:Cloud Firestore 读写
import 'package:cloud_firestore/cloud_firestore.dart';
class TodoService {
final _db = FirebaseFirestore.instance;
final _col = 'todos';
Stream<List<Todo>> watchTodos(String uid) {
return _db.collection(_col)
.where('uid', isEqualTo: uid)
.orderBy('createdAt', descending: true)
.snapshots()
.map((snap) => snap.docs.map((d) => Todo.fromJson(d.data())).toList());
}
Future<void> addTodo(String uid, String title) {
return _db.collection(_col).add({
'uid': uid,
'title': title,
'done': false,
'createdAt': FieldValue.serverTimestamp(),
});
}
Future<void> toggleDone(String docId, bool done) {
return _db.collection(_col).doc(docId).update({'done': done});
}
Future<void> deleteTodo(String docId) {
return _db.collection(_col).doc(docId).delete();
}
}
- Collection:表,如
todos
- Document:行,有自动生成的 ID
- Query:条件查询,
where + orderBy 需要建索引
snapshots():返回 Stream,数据变化时自动推送,实时同步
步骤 6:Firebase Cloud Messaging 推送
import 'package:firebase_messaging/firebase_messaging.dart';
Future<void> setupFCM() async {
final messaging = FirebaseMessaging.instance;
// 请求权限
final settings = await messaging.requestPermission();
if (settings.authorizationStatus != AuthorizationStatus.authorized) return;
// 获取 token(用于后端定向推送)
final token = await messaging.getToken();
debugPrint('FCM token: $token');
// 前台消息
FirebaseMessaging.onMessage.listen((msg) {
debugPrint('前台收到: ${msg.notification?.title}');
});
// 点击通知打开 App
FirebaseMessaging.onMessageOpenedApp.listen((msg) {
debugPrint('用户点击了通知: ${msg.data}');
});
}
Android 需在 AndroidManifest.xml 声明 INTERNET 权限;iOS 需开启 Push Notifications capability,并上传 APNs key 到 Firebase Console。
步骤 7:Crashlytics 崩溃上报
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
// 捕获 Flutter 框架异常
FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterFatalError;
// 捕获异步异常
PlatformDispatcher.instance.onError = (error, stack) {
FirebaseCrashlytics.instance.recordError(error, stack, fatal: true);
return true;
};
runApp(const MyApp());
}
讲清楚要点
- FlutterFire CLI 是官方推荐方式:自动处理 Android/iOS 双端配置,避免手动下载 json/plist 放错位置。
- Firebase Auth 的 User 对象:
uid 是全局唯一 ID,所有业务数据都应该以 uid 作为外键。
- Firestore 是 NoSQL:没有 JOIN,靠「冗余存储」和「聚合查询」设计 schema;实时同步靠
snapshots() Stream。
- FCM 三种消息:通知消息(系统托盘展示)、数据消息(App 自己处理)、前台消息(onMessage 回调)。
- Crashlytics 必须在 runApp 前初始化:否则启动崩溃抓不到。
你可能会懵的地方
-
Firebase 免费吗?
Spark 计划免费:Auth 无限、Firestore 1GB 存储/5 万读/2 万写、FCM 无限。个人项目足够;商业项目看用量。
-
Firestore 和 Realtime Database 选哪个?
Firestore 是新一代,查询更强、扩展更好;Realtime Database 延迟更低但功能少。新项目一律 Firestore。
-
为什么 Google 登录在 Android 上报错?
缺少 SHA-1 指纹。运行 cd android && ./gradlew signingReport,把 debug SHA-1 填到 Firebase Console → Project Settings → Android App。
-
FCM token 会变吗?
会。App 卸载重装、清除数据、系统刷新 token 时都会变。App 启动时应该重新获取并上报到后端。
-
Firestore 的 where + orderBy 报错?
需要复合索引。错误信息里会带一个链接,点进去一键创建索引。
错法对照
| 错法 | 问题 | 正解 |
|---|
main() 里不 ensureInitialized | Firebase 初始化失败 | 第一行就调 WidgetsFlutterBinding.ensureInitialized() |
把 google-services.json 提交到公开仓库 | API Key 泄露 | 加 .gitignore,或确保仓库私有 |
Firestore 查询用 == 判断字符串 | 大小写敏感,查不到 | 存 lowercase 字段,或全量拉下来本地过滤 |
在 build() 里直接调 FirebaseAuth.instance.currentUser | 登录态变化时不会自动 rebuild | 用 StreamBuilder 监听 authStateChanges() |
| FCM 前台消息不弹通知 | 前台时系统不自动弹 | 监听 onMessage,用 flutter_local_notifications 手动弹 |
卡点急救
flutterfire configure 报错:先 firebase login;Windows 上确保 dart pub global 的 bin 目录在 PATH。
- iOS 编译报
GoogleService-Info.plist 找不到:确认文件在 ios/Runner/ 目录下,且已加入 Xcode 项目。
- Auth 报
email-already-in-use:捕获 FirebaseAuthException,按 code 字段给用户提示。
- FCM 收不到推送:Android 检查
google-services.json 是否最新;iOS 检查 APNs key 是否上传、设备是否真机(模拟器收不到 APNs)。
- Firestore 写入成功但 UI 不更新:确认用的是
snapshots() 而不是 get();get() 是一次性读取,不监听变化。
面试常问
-
Firebase Auth 的 token 机制?
登录成功后返回 idToken(JWT),有效期 1 小时;SDK 自动用 refreshToken 续期。后端可以用 Admin SDK 验证 idToken 的合法性。
-
Firestore 和 SQL 的区别?
Firestore 是 NoSQL 文档数据库,schema-less,靠「反规范化」设计;没有 JOIN,查询条件有限;优势是实时同步、自动扩展、离线缓存。
-
FCM 的消息类型?
通知消息(Notification Message):系统托盘展示,App 在后台时点击打开;数据消息(Data Message):完全由 App 处理,可自定义行为。
-
Firebase 的离线机制?
Firestore 默认开启离线持久化:读过的数据缓存到本地,离线时可读;写操作在离线时排队,恢复网络后自动同步。
自测三问
flutterfire configure 做了哪三件事?
- 写一段代码:监听 Firebase Auth 登录态,已登录显示首页,未登录显示登录页。
- Firestore 的
snapshots() 和 get() 有什么区别?
小结一句话
Firebase = 认证 + Firestore + FCM + Crashlytics 的组合拳;FlutterFire CLI 一键接入,Auth 管登录态,Firestore 管实时数据,FCM 管推送,Crashlytics 管崩溃。
延伸