Les applications Firebase fonctionnent même si votre application perd temporairement sa connexion réseau. De plus, Firebase fournit des outils permettant de conserver des données localement, de gérer la présence et de gérer la latence.
Persistance de disque
Les applications Firebase gèrent automatiquement les interruptions réseau temporaires. Les données mises en cache sont disponibles hors connexion, et Firebase renvoie toutes les écritures lorsque la connectivité réseau est rétablie.
Lorsque vous activez la persistance sur disque, votre application écrit les données localement sur l'appareil afin de pouvoir maintenir son état hors connexion, même si l'utilisateur ou le système d'exploitation redémarre l'application.
Vous pouvez activer la persistance de disque en une seule ligne de code.
Swift
Database.database().isPersistenceEnabled = true
Objective-C
[FIRDatabase database].persistenceEnabled = YES;
Comportement de persistance
En activant la persistance, toutes les données que le client Firebase Realtime Database synchroniserait en ligne sont conservées sur le disque et sont disponibles hors connexion, même lorsque l'utilisateur ou le système d'exploitation redémarre l'application. Cela signifie que votre application fonctionne comme elle le ferait en ligne en utilisant les données locales stockées dans le cache. Les rappels d'écouteur continueront de 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 connexion. 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 connexion, le client met en pause les opérations d'écriture jusqu'à ce que votre application réauthentifie l'utilisateur. Sinon, les opérations d'écriture risquent d'échouer en raison de règles de sécurité.
Actualiser les données
Firebase Realtime Database synchronise et stocke une copie locale des données pour les écouteurs actifs. De plus, vous pouvez synchroniser des établissements spécifiques.
Swift
let scoresRef = Database.database().reference(withPath: "scores") scoresRef.keepSynced(true)
Objective-C
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 synchronise, même si la référence ne comporte pas d'écouteurs actifs. Vous pouvez désactiver à nouveau la synchronisation à l'aide de la ligne de code suivante.
Swift
scoresRef.keepSynced(false)
Objective-C
[scoresRef keepSynced:NO];
Par défaut, 10 Mo de données précédemment synchronisées sont mises en cache. Cela devrait être suffisant pour la plupart des applications. Si le cache dépasse sa taille configurée, Firebase Realtime Database purge les données qui ont été utilisées le moins récemment. Les données synchronisées ne sont pas supprimées du cache.
Interroger des données hors connexion
Firebase Realtime Database stocke les données renvoyées par une requête pour les utiliser hors connexion. Pour les requêtes créées hors connexion, Firebase Realtime Database continue de fonctionner pour les données précédemment chargées. Si les données demandées n'ont pas été chargées, Firebase Realtime Database charge les données à partir du cache local. Lorsque la connectivité réseau est à nouveau disponible, les données sont chargées et reflètent la requête.
Par exemple, ce code interroge les quatre derniers éléments d'un Firebase Realtime Database de scores.
Swift
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")") }
Objective-C
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 perde la connexion, passe en mode hors connexion et redémarre l'application. Lorsqu'elle est toujours hors connexion, l'application interroge les deux derniers éléments de la même zone géographique. Cette requête renverra les deux derniers éléments, car l'application a chargé les quatre éléments de la requête ci-dessus.
Swift
scoresRef.queryOrderedByValue().queryLimited(toLast: 2).observe(.childAdded) { snapshot in print("The \(snapshot.key) dinosaur's score is \(snapshot.value ?? "null")") }
Objective-C
[[[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 génère des événements "enfant ajouté" pour les deux dinosaures les plus performants, à l'aide du cache persistant. Toutefois, il 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 en mode hors connexion, elle recevrait immédiatement des événements "enfant ajouté" 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 "child added" et "value" pour l'application.
Gérer les transactions hors connexion
Toutes les transactions effectuées lorsque l'application est hors connexion sont mises en file d'attente. Une fois que l'application a rétabli la connectivité réseau, les transactions sont envoyées au serveur Realtime Database.
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 connexion" 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 de manière propre ou non. Vous pouvez donc vous y fier pour nettoyer les données même si une connexion est interrompue ou si un client plante. Toutes les opérations d'écriture, y compris la configuration, la mise à jour et la suppression, peuvent être effectuées en cas de déconnexion.
Voici un exemple simple d'écriture de données lors de la déconnexion à l'aide de la primitive onDisconnect
:
Swift
let presenceRef = Database.database().reference(withPath: "disconnectmessage"); // Write a string when this client loses connection presenceRef.onDisconnectSetValue("I disconnected!")
Objective-C
FIRDatabaseReference *presenceRef = [[FIRDatabase database] referenceWithPath:@"disconnectmessage"]; // Write a string when this client loses connection [presenceRef onDisconnectSetValue:@"I disconnected!"];
Fonctionnement de onDisconnect
Lorsque vous établissez une opération onDisconnect()
, elle 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 si elle n'est pas valide. Le serveur surveille ensuite la connexion. Si la connexion expire à un moment donné ou est fermée activement par le client Realtime Database, le serveur vérifie la sécurité une seconde fois (pour s'assurer que l'opération est toujours valide), puis appelle l'événement.
Votre application peut utiliser le rappel de l'opération d'écriture pour s'assurer que onDisconnect
a été correctement associé:
Swift
presenceRef.onDisconnectRemoveValue { error, reference in if let error = error { print("Could not establish onDisconnect event: \(error)") } }
Objective-C
[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()
:
Swift
presenceRef.onDisconnectSetValue("I disconnected") // some time later when we change our minds presenceRef.cancelDisconnectOperations()
Objective-C
[presenceRef onDisconnectSetValue:@"I disconnected"]; // some time later when we change our minds [presenceRef cancelDisconnectOperations];
Détecter 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 connexion. Firebase Realtime Database fournit un emplacement spécial à /.info/connected
, qui est mis à jour chaque fois que l'état de connexion du client Firebase Realtime Database change. Voici un exemple :
Swift
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") } })
Objective-C
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 elle dépend de l'état du client. En d'autres termes, si un client lit /.info/connected
comme "false", il n'est pas garanti qu'un autre client lise également "false".
Gérer la latence
Code temporel du serveur
Les serveurs Firebase Realtime Database fournissent un mécanisme permettant d'insérer des codes temporels générés sur le serveur en tant que données. Cette fonctionnalité, combinée à onDisconnect
, permet de noter facilement et de manière fiable l'heure à laquelle un client Realtime Database s'est déconnecté:
Swift
let userLastOnlineRef = Database.database().reference(withPath: "users/morgan/lastOnline") userLastOnlineRef.onDisconnectSetValue(ServerValue.timestamp())
Objective-C
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 de l'horloge du client par rapport aux serveurs de Firebase Realtime Database. Vous pouvez associer 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'epoch en millisecondes) afin d'estimer l'heure du serveur. Notez que la précision de ce décalage peut être affectée par la latence réseau. Il est donc principalement utile pour détecter les écarts importants (> 1 seconde) dans l'heure de l'horloge.
Swift
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)") } })
Objective-C
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); }];
Application exemple de présence
En combinant les opérations de déconnexion à la surveillance de l'état de la connexion et aux codes temporels 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 Realtime Database est en ligne ou non. Les clients définissent cette position sur "True" lorsqu'ils se connectent et sur un code temporel lorsqu'ils se déconnectent. Ce code temporel 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 course si la connexion réseau du client est perdue avant que les deux commandes ne puissent être envoyées au serveur.
Voici un système de présence utilisateur simple:
Swift
// 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 snapshot.value as? Bool ?? false 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()) })
Objective-C
// 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]]; } }];