Cloud Firestore は、オフライン データの永続性をサポートします。この機能により、アプリが使用している Cloud Firestore データのコピーがキャッシュに保存されるため、デバイスがオフラインの場合でも、アプリはデータにアクセスできます。キャッシュされたデータに対しては書き込み、読み取り、リッスン、クエリを行うことができます。デバイスがオンラインに戻ると、アプリがローカルで行った変更と Cloud Firestore バックエンドが同期されます。
オフラインの永続性を利用する場合、Cloud Firestore データへのアクセスに使用するコードに変更を行う必要はありません。オフラインの永続性を有効にすると、Cloud Firestore クライアント ライブラリは、オンラインとオフラインのデータアクセスを自動的に管理し、デバイスがオンラインに戻ったときにローカルデータを同期します。
オフラインの永続性を構成する
Cloud Firestore を初期化するときに、オフラインの永続性を有効にするか無効にするかを選択できます。
- Android と Apple プラットフォームの場合、オフラインの永続性はデフォルトで有効になっています。永続性を無効にするには、
PersistenceEnabled
オプションをfalse
に設定します。 - ウェブの場合、オフラインの永続性はデフォルトで無効になっています。永続性を有効にするには、
enablePersistence
メソッドを呼び出します。Cloud Firestore のキャッシュは、セッション間で自動的にクリアされません。したがって、ウェブアプリで機密情報を処理する場合は、永続性を有効にする前に、ユーザーが信頼できるデバイスを使用しているかどうかをユーザーに尋ねて確認してください。
Web version 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()})
});
Web version 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() settings.isPersistenceEnabled = true // Any additional options // ... // Enable offline data persistence let db = Firestore.firestore() db.settings = settings
Objective-C
FIRFirestoreSettings *settings = [[FIRFirestoreSettings alloc] init]; settings.persistenceEnabled = YES; // Any additional options // ... // Enable offline data persistence FIRFirestore *db = [FIRFirestore firestore]; db.settings = settings;
Kotlin+KTX
val settings = firestoreSettings { isPersistenceEnabled = true } db.firestoreSettings = settings
Java
FirebaseFirestoreSettings settings = new FirebaseFirestoreSettings.Builder() .setPersistenceEnabled(true) .build(); db.setFirestoreSettings(settings);
Dart
// Apple and Android db.settings = const Settings(persistenceEnabled: true); // Web await db .enablePersistence(const PersistenceSettings(synchronizeTabs: true));
キャッシュ サイズを構成する
永続性が有効になっている場合、Cloud Firestore は、オフライン アクセスを行うために、バックエンドから受信したすべてのドキュメントをキャッシュします。Cloud Firestore ではキャッシュ サイズに対し、デフォルトのしきい値を設定しています。デフォルトを超えると、Cloud Firestore は、定期的に古い未使用のドキュメントをクリーンアップしようとします。キャッシュ サイズのしきい値を変更するか、クリーンアップ プロセスを完全に無効にすることもできます。
Web version 9
import { initializeFirestore, CACHE_SIZE_UNLIMITED } from "firebase/firestore"; const firestoreDb = initializeFirestore(app, { cacheSizeBytes: CACHE_SIZE_UNLIMITED });
Web version 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 settings.cacheSizeBytes = FirestoreCacheSizeUnlimited 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; settings.cacheSizeBytes = kFIRFirestoreCacheSizeUnlimited; [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, );
オフライン データをリッスンする
オフラインの永続性を有効にした場合、デバイスがオフラインの間にローカルのキャッシュ データが変更されると、リスナーがリッスン イベントを受信します。リッスンできる対象にはドキュメント、コレクション、クエリがあります。
データの取得元がサーバーかキャッシュかを確認するには、スナップショット イベントの SnapshotMetadata
にある fromCache
プロパティを使用します。fromCache
が true
の場合、データはキャッシュから取得されています。この場合、古いデータまたは不完全なデータを使用している可能性があります。fromCache
が false
の場合、データは完全で、サーバーの最新の更新内容が反映された状態です。
デフォルトで、SnapshotMetadata
だけが変更された場合はイベントが発生しません。fromCache
値を使用する場合には、リッスン ハンドラを接続するときに includeMetadataChanges
リッスン オプションを指定します。
Web version 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); }); });
Web version 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+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}"); } } });
オフライン データを取得する
デバイスがオフラインのときにドキュメントを取得すると、Cloud Firestore はキャッシュからデータを返します。
キャッシュにドキュメントが保存されていなければ、コレクションに対してクエリを実行すると空の結果が返されます。特定のドキュメントを取得しようとした場合は、代わりにエラーが返されます。
オフライン データにクエリを実行する
オフラインの永続性でもクエリを実行できます。ここまでに説明したように、クエリの結果を取得するには直接取得またはリッスンのいずれかの方法を使用します。デバイスがオフラインの間にローカルに保存されたデータに対して新しいクエリを作成することもできますが、最初はキャッシュ内のドキュメントに対してのみクエリが実行されます。
オフライン クエリのインデックスを構成する
デフォルトでは、Firestore SDK はオフライン クエリの実行時に、ローカル キャッシュのコレクション内のすべてのドキュメントをスキャンします。このデフォルトの動作では、ユーザーが長時間オフラインの場合、オフライン クエリのパフォーマンスが低下する可能性があります。
ローカルクエリのインデックスを構成すると、オフライン クエリのパフォーマンスを向上させることができます。
Swift
Apple プラットフォーム SDK には、JSON の構成を読み取る setIndexConfiguration
メソッドが用意されています。この JSON 構成は、サーバー上でインデックスを構成する際に使用するものと同じであり、インデックス定義の形式も同じです。
// You will normally read this from a file asset or cloud storage. let indexConfigJson = """ { indexes: [ ... ], fieldOverrides: [ ... ] } """ // Apply the configuration. Firestore.firestore().setIndexConfiguration(indexConfigJson)
Objective-C
Apple プラットフォーム SDK には、JSON の構成を読み取る setIndexConfiguration
- メソッドが用意されています。この JSON 構成は、サーバー上でインデックスを構成する際に使用するものと同じであり、インデックス定義の形式も同じです。
// You will normally read this from a file asset or cloud storage. NSString *indexConfigJson = @" { " " indexes: [ " " ... " " ], " " fieldOverrides: [ " " ... " " ] " " } "; // Apply the configuration. [[FIRFirestore firestore] setIndexConfigurationFromJSON:indexConfigJson completion:^(NSError * _Nullable error) { // ... }];
Java
Android SDK には、JSON の構成を読み取る setIndexConfiguration
メソッドが用意されています。この JSON 構成は、サーバー上でインデックスを構成する際に使用するものと同じであり、インデックス定義の形式も同じです。
// You will normally read this from a file asset or cloud storage. String indexConfigJson = " { " + " indexes: [ " + " ... " + " ], " + " fieldOverrides: [ " + " ... " + " ] " + " } "; // Apply the configuration. FirebaseFirestore.getInstance().setIndexConfiguration(indexConfigJson);
Kotlin+KTX
Android SDK には、JSON の構成を読み取る setIndexConfiguration
メソッドが用意されています。この JSON 構成は、サーバー上でインデックスを構成する際に使用するものと同じであり、インデックス定義の形式も同じです。
// You will normally read this from a file asset or cloud storage. val indexConfigJson = """ { indexes: [ ... ], fieldOverrides: [ ... ] } """ // Apply the configuration. FirebaseFirestore.getInstance().setIndexConfiguration(indexConfigJson)
Dart
Flutter SDK には、JSON の構成を読み取る setIndexConfigurationFromJSON
メソッドが用意されています。この JSON 構成は、サーバー上でインデックスを構成する際に使用するものと同じであり、インデックス定義の形式も同じです。
// You will normally read this from a file asset or cloud storage. var indexConfigJson = """ { indexes: [ ... ], fieldOverrides: [ ... ] } """; // Apply the configuration. await FirebaseFirestore.instance.setIndexConfigurationFromJSON(json: indexConfigJson);
または、setIndexConfiguration
メソッドを使用して、クラスベースの API でインデックスを構成することもできます。
var indexes = [ Index( collectionGroup: "posts", queryScope: QueryScope.collection, fields: [ IndexField(fieldPath: "author", arrayConfig: ArrayConfig.contains), IndexField(fieldPath: "timestamp", order: Order.descending) ], ), ]; await FirebaseFirestore.instance.setIndexConfiguration(indexes: indexes);
使用するオフライン インデックスの構成は、アプリがオフライン時に頻繁にアクセスするコレクションとドキュメント、および必要なオフライン パフォーマンスによって異なります。バックエンドのインデックス構成をエクスポートしてクライアントで使用することもできますが、アプリのオフライン アクセス パターンはオンライン アクセス パターンと大きく異なる可能性があるため、オンライン インデックスの構成はオフラインでの使用に適さない可能性があります。たとえば、オフライン時にどのコレクションやドキュメントに良好なパフォーマンスでアクセスしたいかによって構成は変わってきます。アプリの動作の分析が完了したら、インデックス ガイドのインデックス定義の原則に従ってください。
クライアント アプリでの読み込みでオフライン インデックス構成を使用できるようにするには:
- オフライン インデックス構成をコンパイルしてアプリと一緒に配布します。
- CDN からオフライン インデックス構成をダウンロードします。
- Cloud Storage for Firebase などのストレージ システムからオフライン インデックス構成を取得します。
ネットワーク アクセスの無効化と有効化
以下のメソッドを使うと、Cloud Firestore クライアントのネットワーク アクセスを無効にできます。ネットワーク アクセスが無効になっている間、スナップショット リスナーとドキュメント要求はいずれもキャッシュから結果を取得します。ネットワーク アクセスが再び有効になるまで、書き込み操作はキューに入れられます。
Web version 9
import { disableNetwork } from "firebase/firestore"; await disableNetwork(db); console.log("Network disabled!"); // Do offline actions // ...
Web version 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+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 });
ネットワーク アクセスを再度有効にするには、以下のメソッドを使用します。
Web version 9
import { enableNetwork } from "firebase/firestore"; await enableNetwork(db); // Do online actions // ...
Web version 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+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 });