Habilitar datos sin conexión

Firestore admite la conservación de datos sin conexión. Esta función almacena en caché una copia de los datos de Firestore que tu aplicación está usando activamente, de modo que tu aplicación pueda acceder a los datos cuando el dispositivo no tenga conexión. Puedes escribir, leer, escuchar y consultar los datos almacenados en caché. Cuando el dispositivo vuelva a conectarse, Firestore sincronizará los cambios locales que haya hecho tu aplicación con el backend de Firestore.

Para usar la persistencia sin conexión, no es necesario que hagas ningún cambio en el código que usas para acceder a los datos de Firestore. Si la persistencia sin conexión está habilitada, la biblioteca de cliente de Firestore gestiona automáticamente el acceso a los datos online y sin conexión, y sincroniza los datos locales cuando el dispositivo vuelve a conectarse.

Configurar la persistencia sin conexión

Cuando inicializas Firestore, puedes habilitar o inhabilitar la persistencia sin conexión:

  • En las plataformas Android y Apple, la persistencia sin conexión está habilitada de forma predeterminada. Para inhabilitar la persistencia, asigna el valor false a la opción PersistenceEnabled.
  • En la Web, la persistencia sin conexión está inhabilitada de forma predeterminada. Para habilitar la persistencia, llama al método enablePersistence. La caché de Firestore no se borra automáticamente entre sesiones. Por lo tanto, si tu aplicación web gestiona información sensible, asegúrate de preguntar al usuario si está usando un dispositivo de confianza antes de habilitar la persistencia.

Versión web 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()})
  });
  

Versión web 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
Nota: Este producto no está disponible en los destinos de watchOS y 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
Nota: Este producto no está disponible en los destinos de watchOS y 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
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));

Configurar el tamaño de la caché

Cuando la persistencia está habilitada, Firestore almacena en caché todos los documentos recibidos del backend para acceder a ellos sin conexión. Firestore define un umbral predeterminado para el tamaño de la caché. Una vez superado el valor predeterminado, Firestore intenta periódicamente limpiar los documentos antiguos que no se utilizan. Puedes configurar un umbral de tamaño de caché diferente o inhabilitar el proceso de limpieza por completo:

Versión web 9

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

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

Versión web 8

firebase.firestore().settings({
    cacheSizeBytes: firebase.firestore.CACHE_SIZE_UNLIMITED
});
Swift
Nota: Este producto no está disponible en los destinos de watchOS y 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
Nota: Este producto no está disponible en los destinos de watchOS y 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
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,
);

Escuchar datos sin conexión

Mientras el dispositivo esté sin conexión, si has habilitado la persistencia sin conexión, tus oyentes recibirán eventos de escucha cuando cambien los datos almacenados en caché localmente. Puedes escuchar documentos, colecciones y consultas.

Para comprobar si recibe datos del servidor o de la caché, utilice la propiedad fromCache en SnapshotMetadata de su evento de instantánea. Si fromCache es true, los datos proceden de la caché y pueden estar obsoletos o incompletos. Si fromCache es false, los datos están completos y actualizados con las últimas actualizaciones del servidor.

De forma predeterminada, no se genera ningún evento si solo cambia el SnapshotMetadata. Si dependes de los valores de fromCache, especifica la opción includeMetadataChanges listen cuando adjuntes tu controlador de escucha.

Versión web 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);
    });
});

Versión web 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
Nota: Este producto no está disponible en los destinos de watchOS y 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
Nota: Este producto no está disponible en los destinos de watchOS y 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
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}");
    }
  }
});

Obtener datos offline

Si obtienes un documento mientras el dispositivo está sin conexión, Firestore devuelve los datos de la caché.

Al consultar una colección, se devuelve un resultado vacío si no hay documentos en caché. Cuando se obtiene un documento específico, se devuelve un error.

Consultar datos offline

Las consultas funcionan con la persistencia sin conexión. Puedes obtener los resultados de las consultas mediante una solicitud GET directa o escuchando, como se describe en las secciones anteriores. También puedes crear consultas sobre datos persistentes a nivel local mientras el dispositivo está sin conexión, pero las consultas se ejecutarán inicialmente solo en los documentos almacenados en caché.

Configurar índices de consultas offline

De forma predeterminada, el SDK de Firestore analiza todos los documentos de una colección en su caché local al ejecutar consultas sin conexión. Con este comportamiento predeterminado, el rendimiento de las consultas sin conexión puede verse afectado si los usuarios no tienen conexión durante largos periodos.

Si la caché persistente está habilitada, puedes mejorar el rendimiento de las consultas sin conexión permitiendo que el SDK cree automáticamente índices de consultas locales.

La indexación automática está inhabilitada de forma predeterminada. Tu aplicación debe habilitar la indexación automática cada vez que se inicie. Controla si la indexación automática está habilitada, como se muestra a continuación.

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

Una vez que se habilita la indexación automática, el SDK evalúa qué colecciones tienen un gran número de documentos almacenados en caché y optimiza el rendimiento de las consultas locales.

El SDK proporciona un método para eliminar índices de consultas.

Inhabilitar y habilitar el acceso a la red

Puedes usar el método que se indica a continuación para inhabilitar el acceso a la red de tu cliente de Firestore. Mientras el acceso a la red esté inhabilitado, todos los listeners de instantáneas y las solicitudes de documentos obtendrán resultados de la caché. Las operaciones de escritura se ponen en cola hasta que se vuelve a habilitar el acceso a la red.

Versión web 9

import { disableNetwork } from "firebase/firestore"; 

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

Versión web 8

firebase.firestore().disableNetwork()
    .then(() => {
        // Do offline actions
        // ...
    });
Swift
Nota: Este producto no está disponible en los destinos de watchOS y App Clip.
Firestore.firestore().disableNetwork { (error) in
  // Do offline things
  // ...
}
Objective‑C
Nota: Este producto no está disponible en los destinos de watchOS y App Clip.
[[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
});

Sigue este método para volver a habilitar el acceso a la red:

Versión web 9

import { enableNetwork } from "firebase/firestore"; 

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

Versión web 8

firebase.firestore().enableNetwork()
    .then(() => {
        // Do online actions
        // ...
    });
Swift
Nota: Este producto no está disponible en los destinos de watchOS y App Clip.
Firestore.firestore().enableNetwork { (error) in
  // Do online things
  // ...
}
Objective‑C
Nota: Este producto no está disponible en los destinos de watchOS y App Clip.
[[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
});