Database
 sql >> Base de données >  >> RDS >> Database

Premiers pas avec Cloud Firestore pour iOS

Les codeurs mobiles tirent parti de la plate-forme Firebase Realtime Database de Google Mobile Backend as a Service (MBaaS) depuis de nombreuses années, ce qui les aide à se concentrer sur la création de fonctionnalités pour leurs applications sans avoir à se soucier de l'infrastructure back-end et de la base de données. En facilitant le stockage et la conservation des données dans le cloud et en prenant soin de l'authentification et de la sécurité, Firebase permet aux codeurs de se concentrer sur le côté client.

L'année dernière, Google a annoncé une autre solution de base de données back-end, Cloud Firestore, conçue à partir de zéro avec la promesse d'une plus grande évolutivité et d'une plus grande intuitivité. Cependant, cela a introduit une certaine confusion quant à sa place par rapport au produit phare déjà existant de Google, Firebase Realtime Database. Ce didacticiel décrira les différences entre les deux plates-formes et les avantages distincts de chacune. Vous apprendrez à utiliser les références de documents Firestore, ainsi qu'à lire, écrire, mettre à jour et supprimer des données en temps réel, en créant une application de rappels simple.

Objectifs de ce didacticiel

Ce tutoriel vous présentera Cloud Firestore. Vous apprendrez à tirer parti de la plate-forme pour la persistance et la synchronisation des bases de données en temps réel. Nous aborderons les sujets suivants :

  • Qu'est-ce que Cloud Firestore ?
  • le modèle de données Firestore
  • configurer Cloud Firestore
  • créer et utiliser des références Cloud Firestore
  • lire les données en temps réel depuis Cloud Firestore
  • créer, mettre à jour et supprimer des données
  • filtrage et requêtes composées

Connaissances supposées

Ce didacticiel suppose que vous avez eu une certaine exposition à Firebase et une expérience de développement avec Swift et Xcode.

Qu'est-ce que Cloud Firestore ?

Comme Firebase Realtime Database, Firestore fournit aux développeurs mobiles et Web une solution cloud multiplateforme pour conserver les données en temps réel, indépendamment de la latence du réseau ou de la connectivité Internet, ainsi qu'une intégration transparente avec la suite de produits Google Cloud Platform. Parallèlement à ces similitudes, il existe des avantages et des inconvénients distincts qui différencient les uns des autres.

Modèle de données

À un niveau fondamental, Realtime Database stocke les données sous la forme d'une grande arborescence JSON monolithique et hiérarchique, tandis que Firestore organise les données dans des documents et des collections, ainsi que des sous-collections. Cela nécessite moins de dénormalisation. Le stockage des données dans une arborescence JSON présente les avantages de la simplicité lorsqu'il s'agit de travailler avec des exigences de données simples ; Cependant, cela devient plus fastidieux à grande échelle lorsque vous travaillez avec des données hiérarchiques plus complexes.

Assistance hors ligne

Les deux produits offrent une prise en charge hors ligne, mettant activement en cache les données dans les files d'attente en cas de connectivité réseau latente ou inexistante, synchronisant les modifications locales vers le back-end lorsque cela est possible. Firestore prend en charge la synchronisation hors ligne pour les applications Web en plus des applications mobiles, tandis que la base de données en temps réel permet uniquement la synchronisation mobile.

Requêtes et transactions 

La base de données en temps réel ne prend en charge que des fonctionnalités de tri et de filtrage limitées. Vous ne pouvez trier ou filtrer qu'au niveau d'une propriété, mais pas les deux, dans une seule requête. Les requêtes sont également approfondies, ce qui signifie qu'elles renvoient un grand sous-arbre de résultats. Le produit ne prend en charge que les opérations d'écriture et de transaction simples qui nécessitent un rappel d'achèvement.

Firestore, d'autre part, introduit des requêtes d'index avec un tri et un filtrage composés, vous permettant de combiner des actions pour créer des filtres et un tri en chaîne. Vous pouvez également exécuter des requêtes superficielles renvoyant des sous-collections au lieu de la collection entière que vous obtiendriez avec la base de données en temps réel. Les transactions sont de nature atomique, que vous envoyiez une opération par lots ou une seule, les transactions se répétant automatiquement jusqu'à leur conclusion. De plus, la base de données en temps réel ne prend en charge que les transactions d'écriture individuelles, tandis que Firestore permet des opérations par lots de manière atomique.

Performances et évolutivité

La base de données en temps réel, comme vous vous en doutez, est assez robuste et a une faible latence. Cependant, les bases de données sont limitées à des régions uniques, sous réserve de disponibilité zonale. Firestore, d'autre part, héberge les données horizontalement dans plusieurs zones et régions pour garantir une véritable disponibilité, évolutivité et fiabilité à l'échelle mondiale. En fait, Google a promis que Firestore serait plus fiable que Realtime Database.

Une autre lacune de la base de données en temps réel est la limitation à 100 000 utilisateurs simultanés (100 000 connexions simultanées et 1 000 écritures/seconde dans une seule base de données) après quoi vous devrez fragmenter votre base de données (diviser votre base de données en plusieurs bases de données) afin de prendre en charge plus d'utilisateurs . Firestore évolue automatiquement sur plusieurs instances sans que vous ayez à intervenir.

Conçu dès le départ dans un souci d'évolutivité, Firestore dispose d'une nouvelle architecture schématique qui réplique les données sur plusieurs régions, prend en charge l'authentification et gère d'autres questions liées à la sécurité dans son SDK côté client. Son nouveau modèle de données est plus intuitif que celui de Firebase, ressemblant davantage à d'autres solutions de base de données NoSQL comparables comme MongoDB, tout en fournissant un moteur de requête plus robuste.

Sécurité 

Enfin, Realtime Database, comme vous le savez grâce à nos tutoriels précédents, gère la sécurité via des règles en cascade avec des déclencheurs de validation séparés. Cela fonctionne avec les règles de base de données Firebase, validant vos données séparément. Firestore, en revanche, fournit un modèle de sécurité plus simple mais plus puissant qui tire parti des règles de sécurité Cloud Firestore et de la gestion des identités et des accès (IAM), la validation des données étant automatiquement exclue.

  • Développement mobileFirebase Security RulesChike Mgbemena

Le modèle de données Firestore

Firestore est une base de données basée sur des documents NoSQL, composée de collections de documents, chacun contenant des données. Comme il s'agit d'une base de données NoSQL, vous n'obtiendrez pas de tables, de lignes et d'autres éléments que vous trouveriez dans une base de données relationnelle, mais plutôt des ensembles de paires clé/valeur que vous trouveriez dans des documents.

Vous créez implicitement des documents et des collections en affectant des données à un document, et si le document ou la collection n'existe pas, il sera automatiquement créé pour vous, car la collection doit toujours être le nœud racine (premier). Voici un simple exemple de schéma Tasks du projet sur lequel vous allez travailler prochainement, composé de la collection Tasks, ainsi que de nombreux documents contenant deux champs, le nom (chaîne) et un indicateur indiquant si la tâche est terminée (booléen) .

Décomposons chacun des éléments pour mieux les comprendre.

Collections

Synonymes de tables de base de données dans le monde SQL, les collections contiennent un ou plusieurs documents. Les collections doivent être les éléments racine de votre schéma et ne peuvent contenir que des documents, pas d'autres collections. Cependant, vous pouvez faire référence à un document qui à son tour fait référence à des collections (sous-collections).

Dans le diagramme ci-dessus, une tâche se compose de deux champs primitifs (name et done) ainsi qu'une sous-collection (sous-tâche) qui se compose de deux champs primitifs qui lui sont propres.

Documents

Les documents sont constitués de paires clé/valeur, les valeurs ayant l'un des types suivants : 

  • champs primitifs (tels que chaînes, nombres, booléens)
  • objets imbriqués complexes (listes ou tableaux de primitives)
  • sous-collections

Les objets imbriqués sont également appelés cartes et peuvent être représentés comme suit, dans le document. Voici un exemple d'objet et de tableau imbriqués, respectivement :

ID: 2422892 //primitive
name: “Remember to buy milk” 
detail: //nested object
    notes: "This is a task to buy milk from the store"
	created: 2017-04-09
	due: 2017-04-10
done: false
notify: ["2F22-89R2", "L092-G623", "H00V-T4S1"]
...

Pour plus d'informations sur les types de données pris en charge, consultez la documentation sur les types de données de Google. Ensuite, vous allez configurer un projet pour travailler avec Cloud Firestore.

Configuration du projet

Si vous avez déjà travaillé avec Firebase, cela devrait vous être familier. Sinon, vous devrez créer un compte dans Firebase et suivre les instructions de la section "Configurer le projet" de notre didacticiel précédent, Premiers pas avec l'authentification Firebase pour iOS .

Pour suivre ce didacticiel, clonez le dépôt du projet de didacticiel. Ensuite, incluez la bibliothèque Firestore par ajouter les éléments suivants à votre  Podfile :

pod 'Firebase/Core' 
pod 'Firebase/Firestore'

Saisissez ce qui suit dans votre terminal pour créer votre bibliothèque :

pod install

Ensuite, passez à Xcode et ouvrez le  .xcworkspace dossier. Accédez à AppDelegate.swift  fichier et saisissez ce qui suit dans application:didFinishLaunchingWithOptions: méthode :

FirebaseApp.configure()

Dans votre navigateur, accédez à la console Firebase et sélectionnez la base de données onglet à gauche.

Assurez-vous de sélectionner l'option Démarrer en mode test afin que vous n'ayez aucun problème de sécurité pendant que nous expérimentons, et tenez compte de l'avis de sécurité lorsque vous déplacez votre application en production. Vous êtes maintenant prêt à créer une collection et quelques exemples de documents.

Ajouter une collection et un exemple de document

Pour commencer, créez une collection initiale, Tasks , en sélectionnant Ajouter une collection bouton et nommez la collection, comme illustré ci-dessous :

Pour le premier document, vous allez laisser l'ID de document vide, ce qui générera automatiquement un ID pour vous. Le document sera simplement composé de deux champs : name et done .

Enregistrez le document et vous devriez être en mesure de confirmer la collection et le document avec l'ID généré automatiquement :

Avec la base de données configurée avec un exemple de document dans le cloud, vous êtes prêt à commencer à implémenter le SDK Firestore dans Xcode.

Créer et travailler avec des références de base de données

Ouvrez le MasterViewController.swift fichier dans Xcode et ajoutez les lignes suivantes pour importer la bibliothèque :

import Firebase

class MasterViewController: UITableViewController {
    @IBOutlet weak var addButton: UIBarButtonItem!
    
    private var documents: [DocumentSnapshot] = []
    public var tasks: [Task] = []
    private var listener : ListenerRegistration!
   ...

Ici, vous créez simplement une variable d'écoute qui vous permettra de déclencher une connexion à la base de données en temps réel lors d'un changement. Vous créez également un DocumentSnapshot référence qui contiendra l'instantané de données temporaire.

Avant de continuer avec le contrôleur de vue, créez un autre fichier Swift, Task.swift , qui représentera votre modèle de données :

import Foundation

struct Task{
    var name:String
    var done: Bool
    var id: String
    
    var dictionary: [String: Any] {
        return [
            "name": name,
            "done": done
        ]
    }
}

extension Task{
    init?(dictionary: [String : Any], id: String) {
        guard   let name = dictionary["name"] as? String,
            let done = dictionary["done"] as? Bool
            else { return nil }
        
        self.init(name: name, done: done, id: id)
    }
}

L'extrait de code ci-dessus inclut une propriété pratique (dictionnaire) et une méthode (init) qui faciliteront le remplissage de l'objet modèle. Revenez au contrôleur de vue et déclarez une variable setter globale qui limitera la requête de base aux 50 premières entrées de la liste des tâches. Vous supprimerez également l'écouteur une fois que vous aurez défini la variable de requête, comme indiqué dans le didSet propriété ci-dessous :

fileprivate func baseQuery() -> Query {
        return Firestore.firestore().collection("Tasks").limit(to: 50)
    }
    
    fileprivate var query: Query? {
        didSet {
            if let listener = listener {
                listener.remove()
            }
        }
    }

override func viewDidLoad() {
        super.viewDidLoad()
        self.query = baseQuery()
    }

 override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        self.listener.remove()
    }

Lire des données en temps réel depuis Cloud Firestore

Avec la référence du document en place, dans viewWillAppear(_animated: Bool) , associez l'écouteur que vous avez créé précédemment aux résultats de l'instantané de la requête et récupérez une liste de documents. Cela se fait en appelant la méthode Firestore query?.addSnapshotListener :

self.listener =  query?.addSnapshotListener { (documents, error) in
            guard let snapshot = documents else {
                print("Error fetching documents results: \(error!)")
                return
            }
            
            let results = snapshot.documents.map { (document) -> Task in
                if let task = Task(dictionary: document.data(), id: document.documentID) {
                    return task
                } else {
                    fatalError("Unable to initialize type \(Task.self) with dictionary \(document.data())")
                }
            }
            
            self.tasks = results
            self.documents = snapshot.documents
            self.tableView.reloadData()
            
        }

La fermeture ci-dessus attribue le snapshot.documents en mappant le tableau de manière itérative et en l'enveloppant dans une nouvelle Task objet d'instance de modèle pour chaque élément de données dans l'instantané. Ainsi, en quelques lignes seulement, vous avez lu avec succès toutes les tâches du cloud et les avez affectées aux tasks globales   déployer.

Pour afficher les résultats, remplissez les champs suivants TableView méthodes déléguées :

override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
    
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return tasks.count
    }
    
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
        
        let item = tasks[indexPath.row]
        
        cell.textLabel!.text = item.name
        cell.textLabel!.textColor = item.done == false ? UIColor.black : UIColor.lightGray
        
        return cell
    }

À ce stade, créez et exécutez le projet et dans le simulateur, vous devriez pouvoir observer les données apparaissant en temps réel. Ajoutez des données via la console Firebase et vous devriez les voir apparaître instantanément dans le simulateur d'application.

Créer, mettre à jour et supprimer des données

Après avoir lu avec succès le contenu du back-end, vous allez ensuite créer, mettre à jour et supprimer des données. L'exemple suivant illustrera comment mettre à jour les données, en utilisant un exemple artificiel où l'application vous permettra uniquement de marquer un élément comme fait en appuyant sur la cellule. Notez le collection.document( item.id ).updateData(["done": !item.done]) propriété de fermeture, qui fait simplement référence à un ID de document spécifique, mettant à jour chacun des champs du dictionnaire :

override func tableView(_ tableView: UITableView,
                            didSelectRowAt indexPath: IndexPath) {

        let item = tasks[indexPath.row]
        let collection = Firestore.firestore().collection("Tasks")

        collection.document(item.id).updateData([
            "done": !item.done,
            ]) { err in
                if let err = err {
                    print("Error updating document: \(err)")
                } else {
                    print("Document successfully updated")
                }
        }

        tableView.reloadRows(at: [indexPath], with: .automatic)
        
    }

Pour supprimer un élément, appelez le document( item.id ).delete() méthode:

override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
        return true
    }
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {

        if (editingStyle == .delete){
            let item = tasks[indexPath.row]
            _ = Firestore.firestore().collection("Tasks").document(item.id).delete()
        }

    }

La création d'une nouvelle tâche impliquera l'ajout d'un nouveau bouton dans votre Storyboard et la connexion de son IBAction au contrôleur de vue, en créant une addTask(_ sender:) méthode. Lorsqu'un utilisateur appuie sur le bouton, une feuille d'alerte s'affiche dans laquelle l'utilisateur peut ajouter un nouveau nom de tâche :

collection("Tasks").addDocument
    (data: ["name": textFieldReminder.text ?? 
        "empty task", "done": false])

Complétez la dernière partie de l'application en saisissant ce qui suit :

@IBAction func addTask(_ sender: Any) {
        
        let alertVC : UIAlertController = UIAlertController(title: "New Task", message: "What do you want to remember?", preferredStyle: .alert)
        
        alertVC.addTextField { (UITextField) in
            
        }
        
        let cancelAction = UIAlertAction.init(title: "Cancel", style: .destructive, handler: nil)
        
        alertVC.addAction(cancelAction)
        
        //Alert action closure
        let addAction = UIAlertAction.init(title: "Add", style: .default) { (UIAlertAction) -> Void in
            
            let textFieldReminder = (alertVC.textFields?.first)! as UITextField
            
            let db = Firestore.firestore()
            var docRef: DocumentReference? = nil
            docRef = db.collection("Tasks").addDocument(data: [
                "name": textFieldReminder.text ?? "empty task",
                "done": false
            ]) { err in
                if let err = err {
                    print("Error adding document: \(err)")
                } else {
                    print("Document added with ID: \(docRef!.documentID)")
                }
            }
            
        }
    
        alertVC.addAction(addAction)
        present(alertVC, animated: true, completion: nil)
        
    }

Créez et exécutez à nouveau l'application et, lorsque le simulateur apparaît, essayez d'ajouter quelques tâches, ainsi que d'en marquer quelques-unes comme terminées, et enfin testez la fonction de suppression en supprimant certaines tâches. Vous pouvez confirmer que les données stockées ont été mises à jour en temps réel en basculant sur votre console de base de données Firebase et en observant la collection et les documents.

Filtrage et requêtes composées

Jusqu'à présent, vous n'avez travaillé qu'avec une requête simple, sans aucune capacité de filtrage spécifique. Pour créer des requêtes légèrement plus robustes, vous pouvez filtrer par des valeurs spécifiques en utilisant un whereField clause :

docRef.whereField(“name”, isEqualTo: searchString)

Vous pouvez trier et limiter vos données de requête en utilisant le order(by: ) et limit(to: ) méthodes comme suit :

docRef.order(by: "name").limit(5)

Dans l'application FirebaseDo, vous avez déjà utilisé limit avec la requête de base. Dans l'extrait ci-dessus, vous avez également utilisé une autre fonctionnalité, les requêtes composées, où l'ordre et la limite sont enchaînés. Vous pouvez enchaîner autant de requêtes que vous le souhaitez, comme dans l'exemple suivant :

docRef
    .whereField(“name”, isEqualTo: searchString)
	.whereField(“done”, isEqualTo: false)
	.order(by: "name")
	.limit(5)