哈囉鐵人們!現在此App是一個非常出色的單機版應用~它功能豐富、體驗流暢,甚至還能離線使用。但它有一個天生的「地域限制」——那就是所有的使用者資料,如那些特別收藏的靈感,都被鎖在一台裝置的Hive資料庫裡。
如果使用者換了新手機怎麼辦?如果他想在手機和iPad 看到同樣的收藏列表怎麼辦?目前唯一的答案是:辦不到!
今天將引入Firebase的兩大王牌功能,將使用者的核心資料遷移到雲端,打造真正的多端同步:
第一步:安裝Firebase套件
之前已經安裝了 firebase_core,現在需要加入Auth和Firestore的套件:flutter pub add firebase_auth cloud_firestore
第二步:實作Firebase,匿名登入
要讓資料跟著「使用者」走,首先得知道「使用者」是誰。但強制使用者註冊帳號會增加使用門檻,最好的方式是「匿名登入」。Firebase Auth會在背景為使用者建立一個永久的、獨一無二的帳號,而使用者完全不需要進行任何操作。
Step1. 在Firebase控制台啟用匿名登入
Step2. 在App啟動時自動登入
確保使用者一打開App就擁有一個身份,修改lib/main.dart的main函式:
// 在 main.dart 頂部引入
import 'package:firebase_auth/firebase_auth.dart';
Future<void> main() async {
// ... 其他初始化 ...
await Firebase.initializeApp(...);
// 檢查當前是否有使用者登入,若無,則進行匿名登入
if (FirebaseAuth.instance.currentUser == null) {
try {
await FirebaseAuth.instance.signInAnonymously();
debugPrint("已成功匿名登入!");
} catch (e) {
debugPrint("匿名登入失敗: $e");
}
}
// ... Hive, Workmanager 初始化 ...
runApp(...);
}
現在,每個開App的使用者(無論新舊),都會被賦予一個獨一無二的uid (User ID),這個uid就是我們接下來在資料庫中區分不同使用者資料的關鍵鑰匙。
第三步:設定 Cloud Firestore 與資料結構
Step1. 在 Firebase 控制台建立資料庫
Step2. 規劃使用者獨立的資料空間
這是今天最核心的概念。為了不讓A使用者的收藏被B使用者看到,必須以uid來隔離資料。規劃結構如下:
users (集合 Collection)
└── Hq5k...xZ2 (文件 Document, 此處為使用者的 uid)
├── profile (欄位 Field): { displayName: "Guest", createdAt: ... }
│
└── favorites (子集合 Sub-collection)
│
└── abc...123 (文件 Document, 此處為靈感的 id)
├── summary: "靈感的摘要..."
├── tags: ["Flutter", "UI"]
├── sourceUrl: "https://..."
└── favoritedAt: Nov 20, 2025 at 10:00:00 AM UTC+8
第四步:將「收藏」邏輯從Hive遷移至 Firestore
現在,要對Day 14建立的FavoriteNotifier進行一次大手術,讓它從讀寫本地Hive轉為讀寫雲端Firestore。
打開lib/features/favorite/favorite_provider.dart:
import 'package:firebase_auth/firebase_auth.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
// ... 其他 import
class FavoriteNotifier extends StateNotifier<List<Insight>> {
FavoriteNotifier() : super([]) {
// 當使用者登入狀態改變時,自動加載他們的收藏
FirebaseAuth.instance.authStateChanges().listen((user) {
if (user != null) {
_loadFavorites(user.uid);
} else {
// 如果使用者登出,清空收藏列表
state = [];
}
});
}
// 從 Firestore 載入收藏
Future<void> _loadFavorites(String userId) async {
try {
final snapshot = await FirebaseFirestore.instance
.collection('users')
.doc(userId)
.collection('favorites')
.get();
final favorites = snapshot.docs.map((doc) => Insight.fromMap(doc.data())).toList();
state = favorites;
} catch (e) {
debugPrint("載入 Firestore 收藏失敗: $e");
}
}
// 新增收藏
Future<void> addFavorite(Insight insight) async {
final userId = FirebaseAuth.instance.currentUser?.uid;
if (userId == null) return; // 如果未登入,則不執行任何操作
if (!state.any((item) => item.id == insight.id)) {
// 寫入 Firestore
await FirebaseFirestore.instance
.collection('users')
.doc(userId)
.collection('favorites')
.doc(insight.id)
.set(insight.toMap()); // 將 Insight 物件轉為 Map
// 更新本地狀態
state = [...state, insight];
}
}
// 移除收藏
Future<void> removeFavorite(String insightId) async {
final userId = FirebaseAuth.instance.currentUser?.uid;
if (userId == null) return;
// 從 Firestore 刪除
await FirebaseFirestore.instance
.collection('users')
.doc(userId)
.collection('favorites')
.doc(insightId)
.delete();
// 更新本地狀態
state = state.where((insight) => insight.id != insightId).toList();
}
}
// ... Provider 維持不變
小提示:你需要在Insight模型中加入toMap()和fromMap()方法,以便在Dart物件和Firestore的Map格式之間轉換。
使用者的資料終於安全地存放在了雲端。但目前的體驗還有一點延遲:如果你在一台手機上收藏了某個靈感,需要重啟另一台手機上的App才能看到同步。如何讓這個同步過程變成即時的呢?
明天,將繼續探索Firestore方便的另一特性:即時更新 (Realtime Updates),將學會如何監聽雲端資料的變化,並讓App的UI瞬間、自動地做出反應!
【哈囉你好:)感謝你的閱讀!其他我會常出沒的地方:Threads】