(Facultatif) Prototyper et tester avec Firebase Local Emulator Suite
Avant d'aborder la façon dont votre application lit et écrit dans Realtime Database, Découvrons un ensemble d'outils que vous pouvez utiliser pour prototyper et tester Realtime Database fonctionnalité: Firebase Local Emulator Suite. Si vous testez différentes données en optimisant vos règles de sécurité, ou en tentant d'identifier moyen rentable d'interagir avec le backend, en étant capable de travailler localement sans déployer de services en temps réel peut être une excellente idée.
Un émulateur Realtime Database fait partie de Local Emulator Suite, qui permet à votre application d'interagir avec le contenu et la configuration de votre base de données émulée, et éventuellement les ressources de projet émulées (fonctions, autres bases de données, et règles de sécurité).
L'utilisation de l'émulateur Realtime Database ne nécessite que quelques étapes:
- Ajoutez une ligne de code à la configuration de test de votre application pour vous connecter à l'émulateur.
- À partir de la racine du répertoire local de votre projet, exécutez
firebase emulators:start
. - Effectuer des appels à partir du code du prototype de votre application à l'aide d'une plate-forme Realtime Database SDK comme d'habitude, ou à l'aide de l'API REST Realtime Database.
Un tutoriel détaillé impliquant Realtime Database et Cloud Functions est disponible. Vous pouvez également consulter l'introduction de Local Emulator Suite.
Obtenir une référence de base de données FIR
Pour lire ou écrire des données depuis la base de données, vous avez besoin d'une instance de
FIRDatabaseReference
:
Swift
var ref: DatabaseReference! ref = Database.database().reference()
Objective-C
@property (strong, nonatomic) FIRDatabaseReference *ref; self.ref = [[FIRDatabase database] reference];
Écrire des données
Ce document présente les principes de base de la lecture et de l'écriture de données Firebase.
Les données Firebase sont écrites dans une référence Database
et récupérées en attachant un écouteur asynchrone à la référence. L'écouteur est déclenché
une fois pour l'état initial des données et à nouveau
chaque fois que les données changent.
Opérations d'écriture de base
Pour les opérations d'écriture de base, vous pouvez utiliser setValue
pour enregistrer des données dans un
en remplaçant toutes les données existantes de ce chemin. Vous pouvez utiliser cette méthode pour:
- Types de cartes correspondant aux types JSON disponibles, comme suit:
NSString
NSNumber
NSDictionary
NSArray
Par exemple, vous pouvez ajouter un utilisateur avec setValue
comme suit:
Swift
self.ref.child("users").child(user.uid).setValue(["username": username])
Objective-C
[[[self.ref child:@"users"] child:authResult.user.uid] setValue:@{@"username": username}];
L'utilisation de setValue
de cette manière écrase les données à l'emplacement spécifié, y compris les nœuds enfants. Toutefois, vous pouvez toujours mettre à jour un élément enfant
à réécrire l'objet entier. Si vous souhaitez autoriser les utilisateurs à mettre à jour leur profil, vous pouvez modifier le nom d'utilisateur comme suit :
Swift
self.ref.child("users/\(user.uid)/username").setValue(username)
Objective-C
[[[[_ref child:@"users"] child:user.uid] child:@"username"] setValue:username];
Lire des données
Lire les données en écoutant les événements de valeur
Pour lire les données d'un chemin et écouter les modifications, utilisez la fonction
observeEventType:withBlock
sur FIRDatabaseReference
à observer
FIRDataEventTypeValue
événements.
Type d'événement | Utilisation habituelle |
---|---|
FIRDataEventTypeValue |
Lire et écouter les modifications apportées à l'intégralité du contenu d'un chemin d'accès |
Vous pouvez utiliser l'événement FIRDataEventTypeValue
pour lire les données à un chemin donné.
telles qu'elles existaient au moment de l'événement. Cette méthode est déclenchée une fois
"Listener" est attaché et à nouveau chaque fois que les données, y compris les enfants,
des modifications. Le rappel d'événement reçoit un snapshot
contenant toutes les données à cette
la localisation, y compris les données des enfants. En l'absence de données, l'instantané renvoie
false
lorsque vous appelez exists()
et nil
lorsque vous lisez sa propriété value
.
L'exemple suivant illustre une application de blog sur les réseaux sociaux qui récupère les détails d'un article à partir de la base de données:
Swift
refHandle = postRef.observe(DataEventType.value, with: { snapshot in // ... })
Objective-C
_refHandle = [_postRef observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot * _Nonnull snapshot) { NSDictionary *postDict = snapshot.value; // ... }];
L'écouteur reçoit un FIRDataSnapshot
contenant les données au niveau
emplacement dans la base de données au moment de l'événement dans sa propriété value
. Toi
peut attribuer les valeurs au type natif approprié, tel que NSDictionary
.
Si aucune donnée n'existe à l'emplacement, value
est nil
.
Lire les données une fois
Lire une fois avec getData()
Le SDK est conçu pour gérer les interactions avec les serveurs de base de données, que votre l'application est en ligne ou hors connexion.
En règle générale, vous devez utiliser les techniques d'événements de valeur décrites ci-dessus pour lire les données et être averti des mises à jour des données à partir du backend. Ces techniques de réduire votre utilisation et votre facturation, et sont optimisés pour offrir à vos utilisateurs tant en ligne que hors connexion.
Si vous n'avez besoin des données qu'une seule fois, vous pouvez utiliser getData()
pour obtenir un instantané de
de la base de données. Si, pour une raison quelconque, getData()
ne parvient pas à renvoyer le
"server", le client va vérifier le cache du stockage local et renvoyer une erreur
si la valeur est toujours introuvable.
L'exemple suivant illustre la récupération du nom d'utilisateur public d'un utilisateur une seule fois depuis la base de données:
Swift
do { let snapshot = try await ref.child("users/\(uid)/username").getData() let userName = snapshot.value as? String ?? "Unknown" } catch { print(error) }
Objective-C
NSString *userPath = [NSString stringWithFormat:@"users/%@/username", uid]; [[ref child:userPath] getDataWithCompletionBlock:^(NSError * _Nullable error, FIRDataSnapshot * _Nonnull snapshot) { if (error) { NSLog(@"Received an error %@", error); return; } NSString *userName = snapshot.value; }];
Une utilisation inutile de getData()
peut augmenter l'utilisation de la bande passante et entraîner une perte de données
qui peuvent être évitées en utilisant un écouteur en temps réel, comme illustré
ci-dessus.
Lire les données une fois avec un observateur
Dans certains cas, vous souhaiterez peut-être que la valeur du cache local soit renvoyée
immédiatement, au lieu de rechercher une valeur
mise à jour sur le serveur. Dans ces
vous pouvez utiliser observeSingleEventOfType
pour obtenir les données
le cache du disque local.
Cela est utile pour les données qui ne doivent être chargées qu'une seule fois et qui ne devraient pas changer fréquemment ni nécessiter une écoute active. Par exemple, l'application de blog utilise cette méthode pour charger le profil d'un utilisateur commencez à rédiger un nouveau message:
Swift
let userID = Auth.auth().currentUser?.uid ref.child("users").child(userID!).observeSingleEvent(of: .value, with: { snapshot in // Get user value let value = snapshot.value as? NSDictionary let username = value?["username"] as? String ?? "" let user = User(username: username) // ... }) { error in print(error.localizedDescription) }
Objective-C
NSString *userID = [FIRAuth auth].currentUser.uid; [[[_ref child:@"users"] child:userID] observeSingleEventOfType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot * _Nonnull snapshot) { // Get user value User *user = [[User alloc] initWithUsername:snapshot.value[@"username"]]; // ... } withCancelBlock:^(NSError * _Nonnull error) { NSLog(@"%@", error.localizedDescription); }];
Mettre à jour ou supprimer des données
Mettre à jour des champs spécifiques
Pour écrire simultanément sur des enfants spécifiques d'un nœud sans écraser d'autres
les nœuds enfants, utilisez la méthode updateChildValues
.
Lorsque vous appelez updateChildValues
, vous pouvez mettre à jour les valeurs enfants de niveau inférieur en
spécifiant un chemin d'accès pour la clé. Si les données sont stockées dans plusieurs emplacements
vous pouvez mettre à jour toutes les instances de ces données
distribution ramifiée des données. Par exemple, un
application de blog social peut vouloir créer un article et le mettre à jour simultanément pour
le flux d'activité récente et
le flux d'activité de l'utilisateur ayant publié la publication. Pour ce faire,
de blog utilise un code de ce type:
Swift
guard let key = ref.child("posts").childByAutoId().key else { return } let post = ["uid": userID, "author": username, "title": title, "body": body] let childUpdates = ["/posts/\(key)": post, "/user-posts/\(userID)/\(key)/": post] ref.updateChildValues(childUpdates)
Objective-C
NSString *key = [[_ref child:@"posts"] childByAutoId].key; NSDictionary *post = @{@"uid": userID, @"author": username, @"title": title, @"body": body}; NSDictionary *childUpdates = @{[@"/posts/" stringByAppendingString:key]: post, [NSString stringWithFormat:@"/user-posts/%@/%@/", userID, key]: post}; [_ref updateChildValues:childUpdates];
Cet exemple utilise childByAutoId
pour créer un post dans le nœud contenant des posts pour
tous les utilisateurs de /posts/$postid
et récupèrent simultanément la clé avec
getKey()
La clé peut ensuite être utilisée pour créer une deuxième entrée dans le
posts à /user-posts/$userid/$postid
.
Ces chemins d'accès vous permettent d'effectuer des mises à jour simultanées de plusieurs emplacements dans
l'arborescence JSON avec un seul appel à updateChildValues
, comme dans cet exemple
crée le post aux deux emplacements. Les mises à jour simultanées effectuées de cette manière sont atomiques : toutes les mises à jour réussissent ou toutes échouent.
Ajouter un bloc de fin de lecture
Si vous souhaitez savoir quand vos données ont été validées, vous pouvez ajouter un bloc de finalisation. setValue
et updateChildValues
sont tous deux
d'achèvement qui est appelé lorsque l'écriture a été validée dans le
base de données. Cet écouteur peut être utile pour suivre les données qui ont été enregistrées et celles qui sont encore en cours de synchronisation. Si l'appel a échoué,
L'écouteur reçoit un objet d'erreur indiquant la raison de l'échec.
Swift
do { try await ref.child("users").child(user.uid).setValue(["username": username]) print("Data saved successfully!") } catch { print("Data could not be saved: \(error).") }
Objective-C
[[[_ref child:@"users"] child:user.uid] setValue:@{@"username": username} withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) { if (error) { NSLog(@"Data could not be saved: %@", error); } else { NSLog(@"Data saved successfully."); } }];
Supprimer des données
Le moyen le plus simple de supprimer des données consiste à appeler removeValue
au niveau d'une référence à
l'emplacement de ces données.
Vous pouvez également supprimer en spécifiant nil
comme valeur pour une autre opération d'écriture, telle que setValue
ou updateChildValues
. Vous pouvez utiliser cette technique
avec updateChildValues
pour supprimer plusieurs enfants dans un seul appel d'API.
Dissocier les écouteurs
Les observateurs n'arrêtent pas automatiquement la synchronisation des données lorsque vous quittez un
ViewController
Si un observateur n'est pas correctement supprimé, il continue de se synchroniser
en mémoire locale. Lorsqu'un observateur n'est plus nécessaire, supprimez-le en transmettant
le FIRDatabaseHandle
associé à la méthode removeObserverWithHandle
.
Lorsque vous ajoutez un bloc de rappel à une référence, un FIRDatabaseHandle
est renvoyé.
Ces identifiants peuvent être utilisés pour supprimer le bloc de rappel.
Si plusieurs écouteurs ont été ajoutés à une référence de base de données, chaque écouteur est
appelé lorsqu'un événement est déclenché. Pour arrêter la synchronisation des données à cet emplacement, vous devez supprimer tous les observateurs à un emplacement en appelant la méthode removeAllObservers
.
Appeler removeObserverWithHandle
ou removeAllObservers
sur un écouteur
ne pas supprimer automatiquement les écouteurs enregistrés sur ses nœuds enfants ; vous devez également
gardez une trace de ces références ou identifiants
pour les supprimer.
Enregistrer les données en tant que transactions
lorsque vous travaillez avec des données susceptibles d'être corrompues par des modifications, telles que des compteurs incrémentiels, vous pouvez utiliser un opération de transaction. Vous donnez à cette opération deux arguments: une fonction de mise à jour et un argument facultatif d'achèvement. La fonction de mise à jour prend l'état actuel des données un argument et renvoie le nouvel état souhaité que vous souhaitez écrire.
Dans l'exemple d'application de blog sur les réseaux sociaux, vous pourriez autoriser les utilisateurs activer ou désactiver le suivi de posts et suivre le nombre d'étoiles qu'ils ont reçues ; comme suit:
Swift
ref.runTransactionBlock({ (currentData: MutableData) -> TransactionResult in if var post = currentData.value as? [String: AnyObject], let uid = Auth.auth().currentUser?.uid { var stars: [String: Bool] stars = post["stars"] as? [String: Bool] ?? [:] var starCount = post["starCount"] as? Int ?? 0 if let _ = stars[uid] { // Unstar the post and remove self from stars starCount -= 1 stars.removeValue(forKey: uid) } else { // Star the post and add self to stars starCount += 1 stars[uid] = true } post["starCount"] = starCount as AnyObject? post["stars"] = stars as AnyObject? // Set value and report transaction success currentData.value = post return TransactionResult.success(withValue: currentData) } return TransactionResult.success(withValue: currentData) }) { error, committed, snapshot in if let error = error { print(error.localizedDescription) } }
Objective-C
[ref runTransactionBlock:^FIRTransactionResult * _Nonnull(FIRMutableData * _Nonnull currentData) { NSMutableDictionary *post = currentData.value; if (!post || [post isEqual:[NSNull null]]) { return [FIRTransactionResult successWithValue:currentData]; } NSMutableDictionary *stars = post[@"stars"]; if (!stars) { stars = [[NSMutableDictionary alloc] initWithCapacity:1]; } NSString *uid = [FIRAuth auth].currentUser.uid; int starCount = [post[@"starCount"] intValue]; if (stars[uid]) { // Unstar the post and remove self from stars starCount--; [stars removeObjectForKey:uid]; } else { // Star the post and add self to stars starCount++; stars[uid] = @YES; } post[@"stars"] = stars; post[@"starCount"] = @(starCount); // Set value and report transaction success currentData.value = post; return [FIRTransactionResult successWithValue:currentData]; } andCompletionBlock:^(NSError * _Nullable error, BOOL committed, FIRDataSnapshot * _Nullable snapshot) { // Transaction completed if (error) { NSLog(@"%@", error.localizedDescription); } }];
L'utilisation d'une transaction permet d'éviter que le nombre d'étoiles soit incorrect si plusieurs
les utilisateurs ont ajouté le même message
à leurs favoris en même temps ou le client avait des données obsolètes. La valeur contenue dans la classe FIRMutableData
est initialement la dernière valeur connue du client pour le chemin d'accès, ou nil
en l'absence de valeur. Le serveur compare la valeur initiale à sa valeur actuelle, et accepte la transaction si les valeurs correspondent, ou la rejette. Si la transaction est refusée, le serveur renvoie
la valeur actuelle au client, qui exécute à nouveau la transaction avec le paramètre
la valeur mise à jour. Ce processus se répète jusqu'à ce que la transaction soit acceptée ou qu'un trop grand nombre de transactions soient acceptées.
ont été effectuées.
Incréments atomiques côté serveur
Dans le cas d'utilisation ci-dessus, nous écrivons deux valeurs dans la base de données: l'identifiant l'utilisateur qui a activé ou désactivé le suivi du post, ainsi que le nombre d'étoiles incrémenté. Si nous savons déjà que l'utilisateur ajoute le post à ses favoris, nous pouvons utiliser une opération d'incrément atomique au lieu d'une transaction.
Swift
let updates = [ "posts/\(postID)/stars/\(userID)": true, "posts/\(postID)/starCount": ServerValue.increment(1), "user-posts/\(postID)/stars/\(userID)": true, "user-posts/\(postID)/starCount": ServerValue.increment(1) ] as [String : Any] Database.database().reference().updateChildValues(updates)
Objective-C
NSDictionary *updates = @{[NSString stringWithFormat: @"posts/%@/stars/%@", postID, userID]: @TRUE, [NSString stringWithFormat: @"posts/%@/starCount", postID]: [FIRServerValue increment:@1], [NSString stringWithFormat: @"user-posts/%@/stars/%@", postID, userID]: @TRUE, [NSString stringWithFormat: @"user-posts/%@/starCount", postID]: [FIRServerValue increment:@1]}; [[[FIRDatabase database] reference] updateChildValues:updates];
Ce code n'utilise pas d'opération de transaction. Par conséquent, il n'obtient pas automatiquement réexécuté en cas de mise à jour incompatible. Toutefois, comme l'opération d'incrémentation se produit directement sur le serveur de base de données, il n'y a aucun risque de conflit.
Si vous souhaitez détecter et rejeter les conflits propres à une application, tels qu'un conflit activer le suivi d'un post qu'il a déjà suivi, vous devez écrire des règles de sécurité pour ce cas d'utilisation.
Utiliser les données hors connexion
Si un client perd sa connexion réseau, votre application continuera de fonctionner correctement.
Chaque client connecté à une base de données Firebase gère sa propre version interne de toutes les données actives. Lorsque les données sont écrites, elles sont écrites dans cette version locale en premier. Le client Firebase synchronise ensuite ces données avec la base de données distante. serveurs et avec d'autres clients, de la manière à la base.
Par conséquent, toutes les écritures dans la base de données déclenchent immédiatement des événements locaux, avant toutes les données sont écrites sur le serveur. Cela signifie que votre application quelle que soit la latence ou la connectivité du réseau.
Une fois la connectivité rétablie, votre application reçoit l'ensemble d'événements approprié pour que le client se synchronise avec l'état actuel du serveur, sans avoir à écrire de code personnalisé.
Nous reviendrons sur le comportement hors connexion dans la section En savoir plus sur les fonctionnalités en ligne et hors connexion.