Truy cập dữ liệu khi không có mạng

Cloud Firestore hỗ trợ tính năng lưu trữ cố định dữ liệu khi không có mạng. Tính năng này sẽ lưu một bản sao vào bộ nhớ đệm dữ liệu Cloud Firestore mà ứng dụng của bạn đang sử dụng, vì vậy ứng dụng của bạn có thể truy cập vào dữ liệu khi thiết bị không có kết nối mạng. Bạn có thể viết, đọc, nghe, và truy vấn dữ liệu đã lưu vào bộ nhớ đệm. Khi thiết bị có kết nối mạng trở lại, Cloud Firestore đồng bộ hoá mọi thay đổi cục bộ do ứng dụng của bạn thực hiện với Phần phụ trợ của Cloud Firestore.

Để sử dụng tính năng lưu trữ cố định ngoại tuyến, bạn không cần thay đổi mã mà bạn dùng để truy cập vào dữ liệu trên Cloud Firestore. Khi bật tính năng lưu trữ cố định ngoại tuyến, thư viện ứng dụng Cloud Firestore tự động quản lý trực tuyến và truy cập dữ liệu ngoại tuyến và đồng bộ hoá dữ liệu cục bộ khi thiết bị có kết nối mạng trở lại.

Định cấu hình dữ liệu cố định ngoại tuyến

Khi khởi chạy Cloud Firestore, bạn có thể bật hoặc tắt ổn định ngoại tuyến:

  • Đối với các nền tảng Android và Apple, tính năng lưu trữ cố định khi không có mạng được bật theo mặc định. Để vô hiệu hoá cố định, hãy đặt tuỳ chọn PersistenceEnabled thành false.
  • Đối với web, chế độ lưu trữ cố định khi không có mạng theo mặc định sẽ bị tắt. Để bật cố định, hãy gọi phương thức enablePersistence. Bộ nhớ đệm của Cloud Firestore sẽ không tự động bị xoá giữa các phiên. Do đó, nếu ứng dụng web của bạn xử lý thông tin nhạy cảm, hãy nhớ hỏi xem người dùng có đang sử dụng thiết bị trước khi bật dữ liệu cố định.

Web

// 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()})
  });
  

Web

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
Lưu ý: Sản phẩm này không hoạt động trên các mục tiêu watchOS và App Clip.
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
Lưu ý: Sản phẩm này không hoạt động trên các mục tiêu watchOS và App Clip.
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+KTX

val settings = firestoreSettings {
    // Use memory cache
    setLocalCacheSettings(memoryCacheSettings {})
    // Use persistent disk cache (default)
    setLocalCacheSettings(persistentCacheSettings {})
}
db.firestoreSettings = settings

Java

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));

Định cấu hình kích thước bộ nhớ đệm

Khi bật tính năng lưu trữ cố định, Cloud Firestore sẽ lưu vào bộ nhớ đệm mọi tài liệu nhận được từ phần phụ trợ để truy cập ngoại tuyến. Cloud Firestore thiết lập ngưỡng mặc định cho kích thước bộ nhớ đệm. Sau khi vượt quá giá trị mặc định, Cloud Firestore định kỳ cố gắng dọn dẹp các dữ liệu cũ hơn, không dùng đến tài liệu. Bạn có thể định cấu hình một ngưỡng kích thước bộ nhớ đệm khác hoặc tắt hoàn toàn quá trình dọn dẹp:

Web

import { initializeFirestore, CACHE_SIZE_UNLIMITED } from "firebase/firestore";

const firestoreDb = initializeFirestore(app, {
  cacheSizeBytes: CACHE_SIZE_UNLIMITED
});

Web

firebase.firestore().settings({
    cacheSizeBytes: firebase.firestore.CACHE_SIZE_UNLIMITED
});
Swift
Lưu ý: Sản phẩm này không hoạt động trên các mục tiêu watchOS và App Clip.
// 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
Lưu ý: Sản phẩm này không hoạt động trên các mục tiêu watchOS và App Clip.
// 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+KTX


// 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


// 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,
);

Nghe dữ liệu ngoại tuyến

Khi thiết bị không kết nối mạng, nếu bạn đã bật tính năng lưu trữ cố định ngoại tuyến, trình nghe sẽ nhận được các sự kiện nghe khi dữ liệu được lưu vào bộ nhớ đệm cục bộ thay đổi. Bạn có thể nghe tài liệu, bộ sưu tập và truy vấn.

Để kiểm tra xem bạn đang nhận dữ liệu từ máy chủ hay bộ nhớ đệm, hãy sử dụng Thuộc tính fromCache trên SnapshotMetadata trong sự kiện tổng quan nhanh. Nếu fromCachetrue, dữ liệu đến từ bộ nhớ đệm và có thể đã lỗi thời hoặc chưa hoàn tất. Nếu fromCachefalse, dữ liệu đã đầy đủ và hiện tại với các bản cập nhật mới nhất trên máy chủ.

Theo mặc định, không sự kiện nào được kích hoạt nếu chỉ SnapshotMetadata thay đổi. Nếu bạn dựa vào các giá trị fromCache, hãy chỉ định includeMetadataChanges nghe khi bạn đính kèm trình xử lý nghe.

Web

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);
    });
});

Web

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
Lưu ý: Sản phẩm này không hoạt động trên các mục tiêu watchOS và App Clip.
// 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
Lưu ý: Sản phẩm này không hoạt động trên các mục tiêu watchOS và App Clip.
// 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+KTX

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

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}");
    }
  }
});

Nhận dữ liệu ngoại tuyến

Nếu bạn nhận được tài liệu khi thiết bị không kết nối mạng, Cloud Firestore sẽ trả về dữ liệu từ bộ nhớ đệm.

Khi truy vấn một tập hợp, một kết quả trống sẽ được trả về nếu không có tập hợp nào được lưu vào bộ nhớ đệm tài liệu. Khi tìm nạp một tài liệu cụ thể, hệ thống sẽ trả về lỗi.

Truy vấn dữ liệu ngoại tuyến

Tính năng truy vấn hoạt động với tính ổn định ngoại tuyến. Bạn có thể truy xuất kết quả của truy vấn bằng cách nhận trực tiếp hoặc nghe, như được mô tả trong phần trước . Bạn cũng có thể tạo truy vấn mới trên dữ liệu được lưu trữ cục bộ trong khi thiết bị của bạn đang ngoại tuyến, nhưng ban đầu các truy vấn sẽ chỉ chạy dựa trên tài liệu.

Định cấu hình chỉ mục truy vấn ngoại tuyến

Theo mặc định, SDK Firestore quét tất cả tài liệu trong một tập hợp trong bộ nhớ đệm cục bộ khi thực thi các truy vấn ngoại tuyến. Với chế độ mặc định này, chế độ ngoại tuyến hiệu suất truy vấn của họ có thể bị ảnh hưởng khi người dùng không có kết nối Internet trong thời gian dài.

Khi bật bộ nhớ đệm liên tục, bạn có thể cải thiện hiệu suất của chế độ ngoại tuyến bằng cách cho phép SDK tự động tạo chỉ mục truy vấn cục bộ.

Tính năng tự động lập chỉ mục bị tắt theo mặc định. Ứng dụng của bạn phải bật tự động lập chỉ mục mỗi khi khởi động. Kiểm soát việc có tự động lập chỉ mục hay không như minh hoạ dưới đây.

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+KTX

// return type: PersistentCacheManager?

Firebase.firestore.persistentCacheIndexManager?.apply {
      // Indexing is disabled by default
      enableIndexAutoCreation()
    } ?: println("indexManager is null")
    

Java

// 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();
    

Sau khi bạn bật tính năng tự động lập chỉ mục, SDK sẽ đánh giá xem tập hợp nào có một số lượng lớn tài liệu được lưu vào bộ nhớ đệm và tối ưu hoá hiệu suất của các truy vấn cục bộ.

SDK cung cấp một phương thức để xoá chỉ mục truy vấn.

Tắt và bật quyền truy cập mạng

Bạn có thể sử dụng phương pháp dưới đây để vô hiệu hoá quyền truy cập mạng cho ứng dụng Cloud Firestore. Khi quyền truy cập mạng đang tắt, mọi ảnh chụp nhanh trình nghe và các yêu cầu tài liệu truy xuất kết quả từ bộ nhớ đệm. Ghi các hoạt động được đưa vào hàng đợi cho đến khi quyền truy cập mạng được bật lại.

Web

import { disableNetwork } from "firebase/firestore"; 

await disableNetwork(db);
console.log("Network disabled!");
// Do offline actions
// ...

Web

firebase.firestore().disableNetwork()
    .then(() => {
        // Do offline actions
        // ...
    });
Swift
Lưu ý: Sản phẩm này không hoạt động trên các mục tiêu watchOS và App Clip.
Firestore.firestore().disableNetwork { (error) in
  // Do offline things
  // ...
}
Objective-C
Lưu ý: Sản phẩm này không hoạt động trên các mục tiêu watchOS và App Clip.
[[FIRFirestore firestore] disableNetworkWithCompletion:^(NSError *_Nullable error) {
  // Do offline actions
  // ...
}];

Kotlin+KTX

db.disableNetwork().addOnCompleteListener {
    // Do offline things
    // ...
}

Java

db.disableNetwork()
        .addOnCompleteListener(new OnCompleteListener<Void>() {
            @Override
            public void onComplete(@NonNull Task<Void> task) {
                // Do offline things
                // ...
            }
        });

Dart

db.disableNetwork().then((_) {
  // Do offline things
});

Hãy sử dụng phương pháp sau để bật lại quyền truy cập mạng:

Web

import { enableNetwork } from "firebase/firestore"; 

await enableNetwork(db);
// Do online actions
// ...

Web

firebase.firestore().enableNetwork()
    .then(() => {
        // Do online actions
        // ...
    });
Swift
Lưu ý: Sản phẩm này không hoạt động trên các mục tiêu watchOS và App Clip.
Firestore.firestore().enableNetwork { (error) in
  // Do online things
  // ...
}
Objective-C
Lưu ý: Sản phẩm này không hoạt động trên các mục tiêu watchOS và App Clip.
[[FIRFirestore firestore] enableNetworkWithCompletion:^(NSError *_Nullable error) {
  // Do online actions
  // ...
}];

Kotlin+KTX

db.enableNetwork().addOnCompleteListener {
    // Do online things
    // ...
}

Java

db.enableNetwork()
        .addOnCompleteListener(new OnCompleteListener<Void>() {
            @Override
            public void onComplete(@NonNull Task<Void> task) {
                // Do online things
                // ...
            }
        });

Dart

db.enableNetwork().then((_) {
  // Back online
});