Accede a datos sin conexión

Cloud Firestore permite conservar datos sin conexión. Esta función almacena en caché una copia de los datos de Cloud Firestore que usa la app de forma activa, de modo que esta pueda acceder a los datos cuando el dispositivo no tenga conexión. Puedes escribir, leer, escuchar y consultar los datos en caché. Cuando el dispositivo vuelve a estar en línea, Cloud Firestore sincroniza con el backend de Cloud Firestore los cambios locales que haya hecho tu app.

La función de persistencia sin conexión no requiere modificaciones en el código que se emplea para acceder a los datos de Cloud Firestore. Cuando está activada la persistencia de datos sin conexión, la biblioteca cliente de Cloud Firestore administra el acceso a los datos en línea y sin conexión de forma automática, y sincroniza los datos locales cuando vuelve a conectarse el dispositivo.

Configura la persistencia sin conexión

Cuando inicialices Cloud Firestore, podrás habilitar o inhabilitar la persistencia sin conexión:

  • En las plataformas de Android y Apple, la persistencia sin conexión está habilitada de forma predeterminada. Para inhabilitar la persistencia, configura la opción PersistenceEnabled en false.
  • En la Web, la persistencia sin conexión está inhabilitada de forma predeterminada. Para habilitar la persistencia, llama al método enablePersistence. Entre sesiones, no se borra automáticamente la caché de Cloud Firestore. Por lo tanto, si la aplicación web administra información sensible, asegúrate de preguntarle al usuario si se encuentra en un dispositivo de confianza antes de habilitar la persistencia.

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

API con espacio de nombres 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
Nota: Este producto no se encuentra disponible en los destinos de watchOS ni de App Clips.
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 se encuentra disponible en los destinos de watchOS ni de 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));

Configura el tamaño de la caché

Cuando la persistencia está habilitada, Cloud Firestore almacena en caché todos los documentos recibidos del backend para el acceso sin conexión. Cloud Firestore establece un umbral predeterminado para el tamaño de la caché. Cuando se supera el umbral, Cloud Firestore intenta periódicamente borrar los documentos antiguos que no se usan. Puedes configurar un umbral de tamaño distinto para la caché o inhabilitar completamente el proceso de limpieza:

API modular web

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

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

API con espacio de nombres web

firebase.firestore().settings({
    cacheSizeBytes: firebase.firestore.CACHE_SIZE_UNLIMITED
});
Swift
Nota: Este producto no se encuentra disponible en los destinos de watchOS ni de App Clips.
// 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 se encuentra disponible en los destinos de watchOS ni de 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,
);

Escucha datos sin conexión

Si no tienes conexión en el dispositivo pero está habilitada la persistencia sin conexión, los objetos de escucha recibirán eventos cuando cambien los datos almacenados en la caché local. Los eventos de escucha pueden relacionarse con documentos, colecciones y consultas.

Para comprobar si estás recibiendo datos del servidor o de la caché, usa la propiedad fromCache en los SnapshotMetadata del evento de instantánea. Si fromCache es true, los datos provienen de la caché y es posible que sean obsoletos o estén incompletos. Si fromCache es false, los datos están completos y las últimas actualizaciones provienen del servidor.

Según la configuración predeterminada, no se genera ningún evento si solo cambian los SnapshotMetadata. Si utilizas los valores de fromCache, especifica la opción de escucha includeMetadataChanges cuando conectes el controlador de escucha.

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

API con espacio de nombres 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
Nota: Este producto no se encuentra disponible en los destinos de watchOS ni de App Clips.
// 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 se encuentra disponible en los destinos de watchOS ni de 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}");
    }
  }
});

Obtén datos sin conexión

Si intentas obtener un documento mientras el dispositivo no tiene conexión, Cloud Firestore muestra datos de la caché.

Cuando se consulta una colección, se muestra un resultado vacío si no hay documentos en la caché. Cuando se intenta recuperar un documento específico, se muestra un error.

Consulta datos sin conexión

La consulta funciona con la persistencia sin conexión. Puedes recuperar los resultados de las consultas con una operación get directa o mediante escucha, como se describe en las secciones anteriores. También puedes crear nuevas consultas de datos que se conservan en la caché local mientras el dispositivo está sin conexión. Sin embargo, inicialmente, solo se ejecutarán las consultas con los documentos almacenados en caché.

Configura los índices de consulta sin conexión

De forma predeterminada, el SDK de Firestore analiza todos los documentos de una colección en su caché local cuando ejecuta consultas sin conexión. Con este comportamiento predeterminado, el rendimiento de las consultas sin conexión puede verse afectado cuando los usuarios se encuentran sin conexión durante períodos prolongados.

Con la caché persistente habilitada, puedes mejorar el rendimiento de las consultas sin conexión si permites que el SDK cree índices de consulta locales de forma automática.

La indexación automática está inhabilitada de forma predeterminada. Tu app debe habilitar la indexación automática cada vez que se inicia. 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+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();
    

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

El SDK proporciona un método para borrar índices de consulta.

Inhabilita y vuelve a habilitar el acceso a la red

Puedes usar el siguiente método para inhabilitar el acceso de tu cliente de Cloud Firestore a la red. Mientras el acceso a la red está inhabilitado, todos los objetos de escucha de instantáneas y las solicitudes de documentos recuperan resultados de la memoria caché. Las operaciones de escritura se ponen en cola hasta que se vuelve a habilitar el acceso a la red.

API modular web

import { disableNetwork } from "firebase/firestore"; 

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

API con espacio de nombres web

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

Usa el siguiente método para volver a habilitar el acceso a la red:

API modular web

import { enableNetwork } from "firebase/firestore"; 

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

API con espacio de nombres web

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