Fonctionnalités hors ligne sur les plates-formes Apple

Les applications Firebase fonctionnent même si votre application perd temporairement sa connexion réseau. De plus, Firebase fournit des outils pour conserver les données localement, gérer la présence et gérer la latence.

Persistance du disque

Les applications Firebase gèrent automatiquement les interruptions temporaires du réseau. Les données mises en cache sont disponibles hors ligne et Firebase renvoie toutes les écritures lorsque la connectivité réseau est restaurée.

Lorsque vous activez la persistance du disque, votre application écrit les données localement sur l'appareil afin que votre application puisse conserver son état hors connexion, même si l'utilisateur ou le système d'exploitation redémarre l'application.

Vous pouvez activer la persistance du disque avec une seule ligne de code.

Rapide

Remarque : Ce produit Firebase n'est pas disponible sur la cible App Clip.
Database.database().isPersistenceEnabled = true

Objectif c

Remarque : Ce produit Firebase n'est pas disponible sur la cible App Clip.
[FIRDatabase database].persistenceEnabled = YES;

Comportement de persistance

En activant la persistance, toutes les données que le client Firebase Realtime Database synchroniserait en ligne persistent sur le disque et sont disponibles hors ligne, même lorsque l'utilisateur ou le système d'exploitation redémarre l'application. Cela signifie que votre application fonctionne comme si elle était en ligne en utilisant les données locales stockées dans le cache. Les rappels d'écoute continueront à se déclencher pour les mises à jour locales.

Le client Firebase Realtime Database conserve automatiquement une file d'attente de toutes les opérations d'écriture effectuées lorsque votre application est hors ligne. Lorsque la persistance est activée, cette file d'attente est également conservée sur le disque afin que toutes vos écritures soient disponibles lorsque l'utilisateur ou le système d'exploitation redémarre l'application. Lorsque l'application retrouve la connectivité, toutes les opérations sont envoyées au serveur Firebase Realtime Database.

Si votre application utilise Firebase Authentication , le client Firebase Realtime Database conserve le jeton d'authentification de l'utilisateur lors des redémarrages de l'application. Si le jeton d'authentification expire alors que votre application est hors ligne, le client suspend les opérations d'écriture jusqu'à ce que votre application authentifie à nouveau l'utilisateur, sinon les opérations d'écriture risquent d'échouer en raison des règles de sécurité.

Garder les données à jour

La base de données en temps réel Firebase synchronise et stocke une copie locale des données pour les auditeurs actifs. De plus, vous pouvez synchroniser des emplacements spécifiques.

Rapide

Remarque : Ce produit Firebase n'est pas disponible sur la cible App Clip.
let scoresRef = Database.database().reference(withPath: "scores")
scoresRef.keepSynced(true)

Objectif c

Remarque : Ce produit Firebase n'est pas disponible sur la cible App Clip.
FIRDatabaseReference *scoresRef = [[FIRDatabase database] referenceWithPath:@"scores"];
[scoresRef keepSynced:YES];

Le client Firebase Realtime Database télécharge automatiquement les données à ces emplacements et les maintient synchronisées même si la référence n'a pas d'écouteurs actifs. Vous pouvez désactiver la synchronisation avec la ligne de code suivante.

Rapide

Remarque : Ce produit Firebase n'est pas disponible sur la cible App Clip.
scoresRef.keepSynced(false)

Objectif c

Remarque : Ce produit Firebase n'est pas disponible sur la cible App Clip.
[scoresRef keepSynced:NO];

Par défaut, 10 Mo de données précédemment synchronisées sont mises en cache. Cela devrait suffire pour la plupart des applications. Si le cache dépasse sa taille configurée, la base de données en temps réel Firebase purge les données qui ont été utilisées le moins récemment. Les données maintenues synchronisées ne sont pas purgées du cache.

Interroger des données hors ligne

La base de données en temps réel Firebase stocke les données renvoyées par une requête pour une utilisation hors connexion. Pour les requêtes construites hors connexion, la base de données en temps réel Firebase continue de fonctionner pour les données précédemment chargées. Si les données demandées n'ont pas été chargées, la base de données en temps réel Firebase charge les données à partir du cache local. Lorsque la connectivité réseau est à nouveau disponible, les données se chargent et reflètent la requête.

Par exemple, ce code interroge les quatre derniers éléments d'une base de données en temps réel Firebase de scores

Rapide

Remarque : Ce produit Firebase n'est pas disponible sur la cible App Clip.
let scoresRef = Database.database().reference(withPath: "scores")
scoresRef.queryOrderedByValue().queryLimited(toLast: 4).observe(.childAdded) { snapshot in
  print("The \(snapshot.key) dinosaur's score is \(snapshot.value ?? "null")")
}

Objectif c

Remarque : Ce produit Firebase n'est pas disponible sur la cible App Clip.
FIRDatabaseReference *scoresRef = [[FIRDatabase database] referenceWithPath:@"scores"];
[[[scoresRef queryOrderedByValue] queryLimitedToLast:4]
    observeEventType:FIRDataEventTypeChildAdded withBlock:^(FIRDataSnapshot *snapshot) {
      NSLog(@"The %@ dinosaur's score is %@", snapshot.key, snapshot.value);
    }];

Supposons que l'utilisateur perd la connexion, se déconnecte et redémarre l'application. Alors qu'elle est toujours hors ligne, l'application recherche les deux derniers éléments à partir du même emplacement. Cette requête renverra avec succès les deux derniers éléments, car l'application a chargé les quatre éléments dans la requête ci-dessus.

Rapide

Remarque : Ce produit Firebase n'est pas disponible sur la cible App Clip.
scoresRef.queryOrderedByValue().queryLimited(toLast: 2).observe(.childAdded) { snapshot in
  print("The \(snapshot.key) dinosaur's score is \(snapshot.value ?? "null")")
}

Objectif c

Remarque : Ce produit Firebase n'est pas disponible sur la cible App Clip.
[[[scoresRef queryOrderedByValue] queryLimitedToLast:2]
    observeEventType:FIRDataEventTypeChildAdded withBlock:^(FIRDataSnapshot *snapshot) {
      NSLog(@"The %@ dinosaur's score is %@", snapshot.key, snapshot.value);
    }];

Dans l'exemple précédent, le client Firebase Realtime Database déclenche des événements "enfant ajouté" pour les deux dinosaures les plus performants, en utilisant le cache persistant. Mais cela ne déclenchera pas d'événement 'value', car l'application n'a jamais exécuté cette requête en ligne.

Si l'application demandait les six derniers éléments hors ligne, elle obtiendrait immédiatement des événements "enfants ajoutés" pour les quatre éléments mis en cache. Lorsque l'appareil revient en ligne, le client Firebase Realtime Database se synchronise avec le serveur et obtient les deux derniers événements "enfant ajouté" et "valeur" pour l'application.

Gestion des transactions hors ligne

Toutes les transactions effectuées lorsque l'application est hors ligne sont mises en file d'attente. Une fois que l'application retrouve la connectivité réseau, les transactions sont envoyées au serveur de base de données en temps réel.

Gérer la présence

Dans les applications en temps réel, il est souvent utile de détecter quand les clients se connectent et se déconnectent. Par exemple, vous pouvez marquer un utilisateur comme "hors ligne" lorsque son client se déconnecte.

Les clients Firebase Database fournissent des primitives simples que vous pouvez utiliser pour écrire dans la base de données lorsqu'un client se déconnecte des serveurs Firebase Database. Ces mises à jour se produisent que le client se déconnecte proprement ou non, vous pouvez donc compter sur elles pour nettoyer les données même si une connexion est interrompue ou si un client tombe en panne. Toutes les opérations d'écriture, y compris la définition, la mise à jour et la suppression, peuvent être effectuées lors d'une déconnexion.

Voici un exemple simple d'écriture de données lors de la déconnexion à l'aide de la primitive onDisconnect :

Rapide

Remarque : Ce produit Firebase n'est pas disponible sur la cible App Clip.
let presenceRef = Database.database().reference(withPath: "disconnectmessage");
// Write a string when this client loses connection
presenceRef.onDisconnectSetValue("I disconnected!")

Objectif c

Remarque : Ce produit Firebase n'est pas disponible sur la cible App Clip.
FIRDatabaseReference *presenceRef = [[FIRDatabase database] referenceWithPath:@"disconnectmessage"];
// Write a string when this client loses connection
[presenceRef onDisconnectSetValue:@"I disconnected!"];

Comment fonctionne la déconnexion

Lorsque vous établissez une opération onDisconnect() , l'opération réside sur le serveur Firebase Realtime Database. Le serveur vérifie la sécurité pour s'assurer que l'utilisateur peut effectuer l'événement d'écriture demandé et informe votre application s'il n'est pas valide. Le serveur surveille alors la connexion. Si, à un moment quelconque, la connexion expire ou est activement fermée par le client de la base de données en temps réel, le serveur vérifie la sécurité une deuxième fois (pour s'assurer que l'opération est toujours valide), puis invoque l'événement.

Votre application peut utiliser le rappel sur l'opération d'écriture pour s'assurer que l' onDisconnect a été correctement attaché :

Rapide

Remarque : Ce produit Firebase n'est pas disponible sur la cible App Clip.
presenceRef.onDisconnectRemoveValue { error, reference in
  if let error = error {
    print("Could not establish onDisconnect event: \(error)")
  }
}

Objectif c

Remarque : Ce produit Firebase n'est pas disponible sur la cible App Clip.
[presenceRef onDisconnectRemoveValueWithCompletionBlock:^(NSError *error, FIRDatabaseReference *reference) {
  if (error != nil) {
    NSLog(@"Could not establish onDisconnect event: %@", error);
  }
}];

Un événement onDisconnect peut également être annulé en appelant .cancel() :

Rapide

Remarque : Ce produit Firebase n'est pas disponible sur la cible App Clip.
presenceRef.onDisconnectSetValue("I disconnected")
// some time later when we change our minds
presenceRef.cancelDisconnectOperations()

Objectif c

Remarque : Ce produit Firebase n'est pas disponible sur la cible App Clip.
[presenceRef onDisconnectSetValue:@"I disconnected"];
// some time later when we change our minds
[presenceRef cancelDisconnectOperations];

Détection de l'état de la connexion

Pour de nombreuses fonctionnalités liées à la présence, il est utile que votre application sache quand elle est en ligne ou hors ligne. Firebase Realtime Database fournit un emplacement spécial sur /.info/connected qui est mis à jour chaque fois que l'état de connexion du client Firebase Realtime Database change. Voici un exemple:

Rapide

Remarque : Ce produit Firebase n'est pas disponible sur la cible App Clip.
let connectedRef = Database.database().reference(withPath: ".info/connected")
connectedRef.observe(.value, with: { snapshot in
  if snapshot.value as? Bool ?? false {
    print("Connected")
  } else {
    print("Not connected")
  }
})

Objectif c

Remarque : Ce produit Firebase n'est pas disponible sur la cible App Clip.
FIRDatabaseReference *connectedRef = [[FIRDatabase database] referenceWithPath:@".info/connected"];
[connectedRef observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
  if([snapshot.value boolValue]) {
    NSLog(@"connected");
  } else {
    NSLog(@"not connected");
  }
}];

/.info/connected est une valeur booléenne qui n'est pas synchronisée entre les clients Realtime Database car la valeur dépend de l'état du client. En d'autres termes, si un client lit /.info/connected comme faux, cela ne garantit pas qu'un autre client lira également faux.

Gestion de la latence

Horodatages du serveur

Les serveurs Firebase Realtime Database fournissent un mécanisme pour insérer les horodatages générés sur le serveur en tant que données. Cette fonctionnalité, associée à onDisconnect , fournit un moyen simple de noter de manière fiable l'heure à laquelle un client Realtime Database s'est déconnecté :

Rapide

Remarque : Ce produit Firebase n'est pas disponible sur la cible App Clip.
let userLastOnlineRef = Database.database().reference(withPath: "users/morgan/lastOnline")
userLastOnlineRef.onDisconnectSetValue(ServerValue.timestamp())

Objectif c

Remarque : Ce produit Firebase n'est pas disponible sur la cible App Clip.
FIRDatabaseReference *userLastOnlineRef = [[FIRDatabase database] referenceWithPath:@"users/morgan/lastOnline"];
[userLastOnlineRef onDisconnectSetValue:[FIRServerValue timestamp]];

Décalage de l'horloge

Bien que firebase.database.ServerValue.TIMESTAMP soit beaucoup plus précis et préférable pour la plupart des opérations de lecture/écriture, il peut parfois être utile d'estimer le décalage d'horloge du client par rapport aux serveurs de la base de données Firebase Realtime. Vous pouvez joindre un rappel à l'emplacement /.info/serverTimeOffset pour obtenir la valeur, en millisecondes, que les clients Firebase Realtime Database ajoutent à l'heure locale signalée (heure d'époque en millisecondes) pour estimer l'heure du serveur. Notez que la précision de ce décalage peut être affectée par la latence du réseau, et est donc principalement utile pour découvrir des écarts importants (> 1 seconde) dans l'heure de l'horloge.

Rapide

Remarque : Ce produit Firebase n'est pas disponible sur la cible App Clip.
let offsetRef = Database.database().reference(withPath: ".info/serverTimeOffset")
offsetRef.observe(.value, with: { snapshot in
  if let offset = snapshot.value as? TimeInterval {
    print("Estimated server time in milliseconds: \(Date().timeIntervalSince1970 * 1000 + offset)")
  }
})

Objectif c

Remarque : Ce produit Firebase n'est pas disponible sur la cible App Clip.
FIRDatabaseReference *offsetRef = [[FIRDatabase database] referenceWithPath:@".info/serverTimeOffset"];
[offsetRef observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
  NSTimeInterval offset = [(NSNumber *)snapshot.value doubleValue];
  NSTimeInterval estimatedServerTimeMs = [[NSDate date] timeIntervalSince1970] * 1000.0 + offset;
  NSLog(@"Estimated server time: %0.3f", estimatedServerTimeMs);
}];

Exemple d'application de présence

En combinant les opérations de déconnexion avec la surveillance de l'état de la connexion et les horodatages du serveur, vous pouvez créer un système de présence des utilisateurs. Dans ce système, chaque utilisateur stocke des données à un emplacement de base de données pour indiquer si un client de base de données en temps réel est en ligne ou non. Les clients définissent cet emplacement sur true lorsqu'ils se connectent et un horodatage lorsqu'ils se déconnectent. Cet horodatage indique la dernière fois que l'utilisateur donné était en ligne.

Notez que votre application doit mettre en file d'attente les opérations de déconnexion avant qu'un utilisateur ne soit marqué en ligne, afin d'éviter toute condition de concurrence dans le cas où la connexion réseau du client est perdue avant que les deux commandes puissent être envoyées au serveur.

Voici un système de présence utilisateur simple :

Rapide

Remarque : Ce produit Firebase n'est pas disponible sur la cible App Clip.
// since I can connect from multiple devices, we store each connection instance separately
// any time that connectionsRef's value is null (i.e. has no children) I am offline
let myConnectionsRef = Database.database().reference(withPath: "users/morgan/connections")

// stores the timestamp of my last disconnect (the last time I was seen online)
let lastOnlineRef = Database.database().reference(withPath: "users/morgan/lastOnline")

let connectedRef = Database.database().reference(withPath: ".info/connected")

connectedRef.observe(.value, with: { snapshot in
  // only handle connection established (or I've reconnected after a loss of connection)
  guard let connected = snapshot.value as? Bool, connected else { return }

  // add this device to my connections list
  let con = myConnectionsRef.childByAutoId()

  // when this device disconnects, remove it.
  con.onDisconnectRemoveValue()

  // The onDisconnect() call is before the call to set() itself. This is to avoid a race condition
  // where you set the user's presence to true and the client disconnects before the
  // onDisconnect() operation takes effect, leaving a ghost user.

  // this value could contain info about the device or a timestamp instead of just true
  con.setValue(true)

  // when I disconnect, update the last time I was seen online
  lastOnlineRef.onDisconnectSetValue(ServerValue.timestamp())
})

Objectif c

Remarque : Ce produit Firebase n'est pas disponible sur la cible App Clip.
// since I can connect from multiple devices, we store each connection instance separately
// any time that connectionsRef's value is null (i.e. has no children) I am offline
FIRDatabaseReference *myConnectionsRef = [[FIRDatabase database] referenceWithPath:@"users/morgan/connections"];

// stores the timestamp of my last disconnect (the last time I was seen online)
FIRDatabaseReference *lastOnlineRef = [[FIRDatabase database] referenceWithPath:@"users/morgan/lastOnline"];

FIRDatabaseReference *connectedRef = [[FIRDatabase database] referenceWithPath:@".info/connected"];
[connectedRef observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
  if([snapshot.value boolValue]) {
    // connection established (or I've reconnected after a loss of connection)

    // add this device to my connections list
    FIRDatabaseReference *con = [myConnectionsRef childByAutoId];

    // when this device disconnects, remove it
    [con onDisconnectRemoveValue];

    // The onDisconnect() call is before the call to set() itself. This is to avoid a race condition
    // where you set the user's presence to true and the client disconnects before the
    // onDisconnect() operation takes effect, leaving a ghost user.

    // this value could contain info about the device or a timestamp instead of just true
    [con setValue:@YES];


    // when I disconnect, update the last time I was seen online
    [lastOnlineRef onDisconnectSetValue:[FIRServerValue timestamp]];
  }
}];