啟用離線資料
Firestore 支援永久保存離線資料。這項功能會快取應用程式常用的 Firestore 資料副本,因此即使裝置離線,應用程式也能存取資料。您可以寫入、讀取、聆聽及查詢快取資料。裝置恢復連線時,Firestore 會將應用程式在本機進行的任何變更同步至 Firestore 後端。
如要使用離線持續性功能,您不必變更用來存取 Firestore 資料的程式碼。啟用離線持久性後,Firestore 用戶端程式庫就會自動管理線上和離線資料存取,並在裝置重新連線時同步處理本機資料。
設定離線保存功能
初始化 Firestore 時,您可以啟用或停用離線持續性:
- 在 Android 和 Apple 平台上,離線持續性預設為啟用。如要停用持續性,請將
PersistenceEnabled
選項設為false
。 - 在網頁上,離線持續性功能預設為停用。如要啟用持續性,請呼叫
enablePersistence
方法。Firestore 的快取不會在工作階段之間自動清除。因此,如果您的網路應用程式會處理私密資訊,請務必先詢問使用者是否使用信任的裝置,再啟用持續性。
網頁版 9
// Memory cache is the default if no config is specified.
initializeFirestore(app);
// This is the default behavior if no persistence is specified.
initializeFirestore(app, {localCache: memoryLocalCache()});
// Defaults to single-tab persistence if no tab manager is specified.
initializeFirestore(app, {localCache: persistentLocalCache(/*settings*/{})});
// Same as `initializeFirestore(app, {localCache: persistentLocalCache(/*settings*/{})})`,
// but more explicit about tab management.
initializeFirestore(app,
{localCache:
persistentLocalCache(/*settings*/{tabManager: persistentSingleTabManager()})
});
// Use multi-tab IndexedDb persistence.
initializeFirestore(app,
{localCache:
persistentLocalCache(/*settings*/{tabManager: persistentMultipleTabManager()})
});
網頁版 8
firebase.firestore().enablePersistence() .catch((err) => { if (err.code == 'failed-precondition') { // Multiple tabs open, persistence can only be enabled // in one tab at a a time. // ... } else if (err.code == 'unimplemented') { // The current browser does not support all of the // features required to enable persistence // ... } }); // Subsequent queries will use persistence, if it was enabled successfully
Swift
let settings = FirestoreSettings() // Use memory-only cache settings.cacheSettings = MemoryCacheSettings(garbageCollectorSettings: MemoryLRUGCSettings()) // Use persistent disk cache, with 100 MB cache size settings.cacheSettings = PersistentCacheSettings(sizeBytes: 100 * 1024 * 1024 as NSNumber) // Any additional options // ... // Enable offline data persistence let db = Firestore.firestore() db.settings = settings
Objective-C
FIRFirestoreSettings *settings = [[FIRFirestoreSettings alloc] init]; // Use memory-only cache settings.cacheSettings = [[FIRMemoryCacheSettings alloc] initWithGarbageCollectorSettings:[[FIRMemoryLRUGCSettings alloc] init]]; // Use persistent disk cache (default behavior) // This example uses 100 MB. settings.cacheSettings = [[FIRPersistentCacheSettings alloc] initWithSizeBytes:@(100 * 1024 * 1024)]; // Any additional options // ... // Enable offline data persistence FIRFirestore *db = [FIRFirestore firestore]; db.settings = settings;
Kotlin
Android
val settings = firestoreSettings { // Use memory cache setLocalCacheSettings(memoryCacheSettings {}) // Use persistent disk cache (default) setLocalCacheSettings(persistentCacheSettings {}) } db.firestoreSettings = settings
Java
Android
FirebaseFirestoreSettings settings = new FirebaseFirestoreSettings.Builder(db.getFirestoreSettings()) // Use memory-only cache .setLocalCacheSettings(MemoryCacheSettings.newBuilder().build()) // Use persistent disk cache (default) .setLocalCacheSettings(PersistentCacheSettings.newBuilder() .build()) .build(); db.setFirestoreSettings(settings);
Dart
// Apple and Android db.settings = const Settings(persistenceEnabled: true); // Web await db .enablePersistence(const PersistenceSettings(synchronizeTabs: true));
設定快取大小
啟用持續性後,Firestore 會快取從後端收到的所有文件,供離線存取。Firestore 會設定快取大小的預設門檻。超過預設值後,Firestore 會定期嘗試清除較舊的未使用文件。您可以設定不同的快取大小門檻,或完全停用清除程序:
網頁版 9
import { initializeFirestore, CACHE_SIZE_UNLIMITED } from "firebase/firestore"; const firestoreDb = initializeFirestore(app, { cacheSizeBytes: CACHE_SIZE_UNLIMITED });
網頁版 8
firebase.firestore().settings({ cacheSizeBytes: firebase.firestore.CACHE_SIZE_UNLIMITED });
Swift
// The default cache size threshold is 100 MB. Configure "cacheSizeBytes" // for a different threshold (minimum 1 MB) or set to "FirestoreCacheSizeUnlimited" // to disable clean-up. let settings = Firestore.firestore().settings // Set cache size to 100 MB settings.cacheSettings = PersistentCacheSettings(sizeBytes: 100 * 1024 * 1024 as NSNumber) Firestore.firestore().settings = settings
Objective-C
// The default cache size threshold is 100 MB. Configure "cacheSizeBytes" // for a different threshold (minimum 1 MB) or set to "kFIRFirestoreCacheSizeUnlimited" // to disable clean-up. FIRFirestoreSettings *settings = [FIRFirestore firestore].settings; // Set cache size to 100 MB settings.cacheSettings = [[FIRPersistentCacheSettings alloc] initWithSizeBytes:@(100 * 1024 * 1024)]; [FIRFirestore firestore].settings = settings;
Kotlin
Android
// The default cache size threshold is 100 MB. Configure "setCacheSizeBytes" // for a different threshold (minimum 1 MB) or set to "CACHE_SIZE_UNLIMITED" // to disable clean-up. val settings = FirebaseFirestoreSettings.Builder() .setCacheSizeBytes(FirebaseFirestoreSettings.CACHE_SIZE_UNLIMITED) .build() db.firestoreSettings = settings
Java
Android
// The default cache size threshold is 100 MB. Configure "setCacheSizeBytes" // for a different threshold (minimum 1 MB) or set to "CACHE_SIZE_UNLIMITED" // to disable clean-up. FirebaseFirestoreSettings settings = new FirebaseFirestoreSettings.Builder() .setCacheSizeBytes(FirebaseFirestoreSettings.CACHE_SIZE_UNLIMITED) .build(); db.setFirestoreSettings(settings);
Dart
db.settings = const Settings( persistenceEnabled: true, cacheSizeBytes: Settings.CACHE_SIZE_UNLIMITED, );
聆聽離線資料
如果裝置處於離線狀態,且您已啟用離線持續性,當本機快取資料變更時,監聽器就會收到監聽事件。你可以聆聽文件、集合和查詢。
如要檢查是否收到伺服器或快取中的資料,請在快照事件的 SnapshotMetadata
中使用 fromCache
屬性。如果 fromCache
為 true
,表示資料來自快取,可能已過時或不完整。如果 fromCache
為 false
,表示資料完整且為最新狀態,與伺服器上的最新更新一致。
根據預設,如果只有 SnapshotMetadata
變更,系統不會引發任何事件。如果您依賴 fromCache
值,請在附加監聽處理常式時指定 includeMetadataChanges
監聽選項。
網頁版 9
import { collection, onSnapshot, where, query } from "firebase/firestore"; const q = query(collection(db, "cities"), where("state", "==", "CA")); onSnapshot(q, { includeMetadataChanges: true }, (snapshot) => { snapshot.docChanges().forEach((change) => { if (change.type === "added") { console.log("New city: ", change.doc.data()); } const source = snapshot.metadata.fromCache ? "local cache" : "server"; console.log("Data came from " + source); }); });
網頁版 8
db.collection("cities").where("state", "==", "CA") .onSnapshot({ includeMetadataChanges: true }, (snapshot) => { snapshot.docChanges().forEach((change) => { if (change.type === "added") { console.log("New city: ", change.doc.data()); } var source = snapshot.metadata.fromCache ? "local cache" : "server"; console.log("Data came from " + source); }); });
Swift
// Listen to metadata updates to receive a server snapshot even if // the data is the same as the cached data. db.collection("cities").whereField("state", isEqualTo: "CA") .addSnapshotListener(includeMetadataChanges: true) { querySnapshot, error in guard let snapshot = querySnapshot else { print("Error retreiving snapshot: \(error!)") return } for diff in snapshot.documentChanges { if diff.type == .added { print("New city: \(diff.document.data())") } } let source = snapshot.metadata.isFromCache ? "local cache" : "server" print("Metadata: Data fetched from \(source)") }
Objective-C
// Listen to metadata updates to receive a server snapshot even if // the data is the same as the cached data. [[[db collectionWithPath:@"cities"] queryWhereField:@"state" isEqualTo:@"CA"] addSnapshotListenerWithIncludeMetadataChanges:YES listener:^(FIRQuerySnapshot *snapshot, NSError *error) { if (snapshot == nil) { NSLog(@"Error retreiving snapshot: %@", error); return; } for (FIRDocumentChange *diff in snapshot.documentChanges) { if (diff.type == FIRDocumentChangeTypeAdded) { NSLog(@"New city: %@", diff.document.data); } } NSString *source = snapshot.metadata.isFromCache ? @"local cache" : @"server"; NSLog(@"Metadata: Data fetched from %@", source); }];
Kotlin
Android
db.collection("cities").whereEqualTo("state", "CA") .addSnapshotListener(MetadataChanges.INCLUDE) { querySnapshot, e -> if (e != null) { Log.w(TAG, "Listen error", e) return@addSnapshotListener } for (change in querySnapshot!!.documentChanges) { if (change.type == DocumentChange.Type.ADDED) { Log.d(TAG, "New city: ${change.document.data}") } val source = if (querySnapshot.metadata.isFromCache) { "local cache" } else { "server" } Log.d(TAG, "Data fetched from $source") } }
Java
Android
db.collection("cities").whereEqualTo("state", "CA") .addSnapshotListener(MetadataChanges.INCLUDE, new EventListener<QuerySnapshot>() { @Override public void onEvent(@Nullable QuerySnapshot querySnapshot, @Nullable FirebaseFirestoreException e) { if (e != null) { Log.w(TAG, "Listen error", e); return; } for (DocumentChange change : querySnapshot.getDocumentChanges()) { if (change.getType() == Type.ADDED) { Log.d(TAG, "New city:" + change.getDocument().getData()); } String source = querySnapshot.getMetadata().isFromCache() ? "local cache" : "server"; Log.d(TAG, "Data fetched from " + source); } } });
Dart
db .collection("cities") .where("state", isEqualTo: "CA") .snapshots(includeMetadataChanges: true) .listen((querySnapshot) { for (var change in querySnapshot.docChanges) { if (change.type == DocumentChangeType.added) { final source = (querySnapshot.metadata.isFromCache) ? "local cache" : "server"; print("Data fetched from $source}"); } } });
取得離線資料
如果裝置處於離線狀態時收到文件,Firestore 會從快取傳回資料。
查詢集合時,如果沒有快取文件,系統會傳回空白結果。擷取特定文件時,系統會改為傳回錯誤。
查詢離線資料
查詢作業可搭配離線持久性使用。如前幾節所述,您可以透過直接取得或監聽的方式,擷取查詢結果。裝置離線時,您也可以對本機保存的資料建立新查詢,但查詢一開始只會針對快取文件執行。
設定離線查詢索引
根據預設,執行離線查詢時,Firestore SDK 會掃描本機快取中的集合內所有文件。如果使用者長時間處於離線狀態,這種預設行為可能會導致離線查詢效能不佳。
啟用永久快取後,SDK 就能自動建立本機查詢索引,進而提升離線查詢的效能。
自動建立索引功能預設為停用。應用程式每次啟動時,都必須啟用自動建立索引功能。控管是否啟用自動建立索引,如下所示。
Swift
if let indexManager = Firestore.firestore().persistentCacheIndexManager { // Indexing is disabled by default indexManager.enableIndexAutoCreation() } else { print("indexManager is nil") }
Objective-C
PersistentCacheIndexManager *indexManager = [FIRFirestore firestore].persistentCacheIndexManager; if (indexManager) { // Indexing is disabled by default [indexManager enableIndexAutoCreation]; }
Kotlin
Android
// return type: PersistentCacheManager? Firebase.firestore.persistentCacheIndexManager?.apply { // Indexing is disabled by default enableIndexAutoCreation() } ?: println("indexManager is null")
Java
Android
// return type: @Nullable PersistentCacheIndexManager PersistentCacheIndexManager indexManager = FirebaseFirestore.getInstance().getPersistentCacheIndexManager(); if (indexManager != null) { // Indexing is disabled by default indexManager.enableIndexAutoCreation(); } // If not check indexManager != null, IDE shows warning: Method invocation 'enableIndexAutoCreation' may produce 'NullPointerException' FirebaseFirestore.getInstance().getPersistentCacheIndexManager().enableIndexAutoCreation();
啟用自動建立索引後,SDK 會評估哪些集合含有大量快取文件,並提升本機查詢的效能。
SDK 提供刪除查詢索引的方法。
停用及啟用網路存取權
您可以使用下列方法,為 Firestore 用戶端停用網路存取權。網路存取權停用後,所有快照監聽器和文件要求都會從快取擷取結果。寫入作業會排入佇列,直到重新啟用網路存取權為止。
網頁版 9
import { disableNetwork } from "firebase/firestore"; await disableNetwork(db); console.log("Network disabled!"); // Do offline actions // ...
網頁版 8
firebase.firestore().disableNetwork() .then(() => { // Do offline actions // ... });
Swift
Firestore.firestore().disableNetwork { (error) in // Do offline things // ... }
Objective-C
[[FIRFirestore firestore] disableNetworkWithCompletion:^(NSError *_Nullable error) { // Do offline actions // ... }];
Kotlin
Android
db.disableNetwork().addOnCompleteListener { // Do offline things // ... }
Java
Android
db.disableNetwork() .addOnCompleteListener(new OnCompleteListener<Void>() { @Override public void onComplete(@NonNull Task<Void> task) { // Do offline things // ... } });
Dart
db.disableNetwork().then((_) { // Do offline things });
請使用下列方法重新啟用網路存取權:
網頁版 9
import { enableNetwork } from "firebase/firestore"; await enableNetwork(db); // Do online actions // ...
網頁版 8
firebase.firestore().enableNetwork() .then(() => { // Do online actions // ... });
Swift
Firestore.firestore().enableNetwork { (error) in // Do online things // ... }
Objective-C
[[FIRFirestore firestore] enableNetworkWithCompletion:^(NSError *_Nullable error) { // Do online actions // ... }];
Kotlin
Android
db.enableNetwork().addOnCompleteListener { // Do online things // ... }
Java
Android
db.enableNetwork() .addOnCompleteListener(new OnCompleteListener<Void>() { @Override public void onComplete(@NonNull Task<Void> task) { // Do online things // ... } });
Dart
db.enableNetwork().then((_) { // Back online });