Flutter: Comment faire du CRUD avec Firebase RTDB

introduction

Dans le post précédent de Flutter: Comment créer une connexion utilisateur avec Firebase, nous avons expliqué comment implémenter la connexion utilisateur ou créer un écran d’enregistrement avec l’authentification Firebase. En utilisant le même projet, nous allons présenter CRUD ou créer, lire, mettre à jour et supprimer des opérations avec Firebase RTDB ou base de données en temps réel dans cet article.

Commencer

Pour ce projet, vous devez enregistrer votre projet auprès de Firebase et inclure le fichier de configuration téléchargé dans votre projet. Vous pouvez obtenir les étapes nécessaires dans le post précédent mentionné ci-dessus. Cette étape n'est nécessaire que si vous préférez configurer votre propre base de données Firebase, sinon vous êtes libre d'utiliser le mien avec le fichier de configuration que j'ai également inclus dans le projet github. Trouvez le lien vers le projet au bas de cet article.

Etape 1: Créer une classe de modèle

Nous sommes donc parvenus à afficher le message de bienvenue une fois que l'utilisateur s'est connecté à son compte. Ceci est affiché dans home_page.dart. Pour que notre application de tâches à faire reste simple, nous allons simplement stocker le nom de tâches à faire et permettre à l’utilisateur de marquer comme achevé ou non. Pour stocker des informations sur chaque tâche, vous devez avoir une classe de modèle. Une classe de modèle pour une tâche à faire ressemblerait à ceci:

/models/todo.dart

classe Todo {
  Clé de chaîne;
  Sujet de la chaîne;
  bool terminé;
  String userId;

  Todo (this.subject, this.userId, this.completed);

  Todo.fromSnapshot (instantané DataSnapshot):
    key = snapshot.key,
    userId = snapshot.value ["userId"],
    subject = snapshot.value ["subject"],
    complete = snapshot.value ["complete"];

  toJson () {
    revenir {
      "userId": userId,
      "sujet": sujet,
      "terminé": terminé,
    };
  }
}

Chaque tâche est unique et possède sa propre clé. Chaque élément a un nom ou un sujet, un drapeau pour suivre son achèvement ou son achèvement et l'utilisateur qui a créé cet élément. Pour créer une nouvelle tâche, tous les paramètres sauf la clé doivent obligatoirement être passés au constructeur Todo (). La clé est générée automatiquement par la RTDB et stockée lorsque de nouvelles tâches sont ajoutées.

Lorsque les données sont extraites de Firebase RTDB, elles sont au format json. Par conséquent, nous avons Todo.fromSnapshot (instantané DataSnapshot) qui nous permet de mapper des données du format JSON au format Todo. ToJson () fait l'inverse, qui consiste à mapper les données au format JSON avant de les télécharger dans Firebase RTDB.

Etape 2: Initialiser la requête

De retour dans notre home_page.dart, nous avons créé une liste de tâches en utilisant List _todoList = new List (). Lorsqu'une liste de tâches est extraite de Firebase, nous la stockons dans des variables de liste locales.

Nous utilisons final FirebaseDatabase _database = FirebaseDatabase.instance; pour accéder à l'instance Firebase. Nous construisons ensuite une requête à partir de cette instance en utilisant:

Requête _todoQuery = _database
    .référence()
    .child ("todo")
    .orderByChild ("userId")
    .equalTo (widget.userId);

Dans cette requête, à l'aide de l'instance FirebaseDatabase, nous récupérons une référence à toutes les données sous path / todo. Si vous avez un autre niveau dans todo, votre requête sera alors _database.reference (). Child ("todo"). Child ("un autre niveau"). J'utilise habituellement .orderByChild ("xx xx") et .equalTo ("xx xx") pour indiquer à Firebase que je veux une liste de tâches où chacun des ID utilisateur est celui que je vous ai donné. Logique?

Voici à quoi cela ressemble dans la RTDB:

Etape 3: Configuration des écouteurs

En utilisant la requête que nous venons de créer ci-dessus, nous allons y attacher 2 types d’abonnements de flux. L'un est onChildAdded et un autre onChildChanged. Ce que fait OnChildAdded.listen (), c’est qu’il écoute tout nouvel élément de tâche ajouté à Firebase, reçoit un événement et le transmet à la fonction de rappel qui, dans ce cas, est _onEntryAdded. Il en va de même pour onChildChanged.listen (), qui écoute tout changement de données dans Firebase tel que mark todo item.

_onTodoAddedSubscription = _todoQuery.onChildAdded.listen (_onEntryAdded);
_onTodoChangedSubscription = _todoQuery.onChildChanged.listen (_onEntryChanged);

Alors, quelle est la fonction de _onEntryAdded? Il capture l'instantané de l'événement et convertit du format json au format de modèle todo et ajoute à la liste des tâches.

_onEntryAdded (événement événement) {
  setState (() {
    _todoList.add (Todo.fromSnapshot (event.snapshot));
  });
}

Pour la fonction _onEntryChanged, il extrait la clé de l'instantané de l'événement et extrait l'index de la liste des tâches. Ensuite, à partir de l'index de liste, il met à jour cette tâche particulière avec celle de l'instantané d'événement.

_onEntryChanged (événement événement) {
  var oldEntry = _todoList.singleWhere ((entry) {
    return entry.key == event.snapshot.key;
  });

  setState (() {
    _todoList [_todoList.indexOf (oldEntry)] = Todo.fromSnapshot (event.snapshot);
  });
}

Pour vous désabonner correctement de StreamSubscription, nous utilisons simplement .cancel () dans la méthode dispose ()

@passer outre
vide dispose () {
  _onTodoAddedSubscription.cancel ();
  _onTodoChangedSubscription.cancel ();
  super.dispose ();
}

Etape 4: Construire cette liste

J'aime utiliser ListView lorsque nécessaire pour parcourir une liste d'éléments dont la taille change de façon dynamique et les afficher dans une liste. Donc, dans ce cas, nous allons parcourir chaque élément dans la liste _todoList. ListView prend itemCount qui est simplement la taille de la liste de tâches, c'est-à-dire _todoList.count. ListView prend également itemBuilder, qui est la partie qui construira la mosaïque unique pour afficher un seul élément de tâche. Nous allons utiliser le widget ListTile pour afficher un seul élément de tâche. ListTile accepte certains paramètres, tels que la fin pour placer une icône ou un autre widget à droite de ListTile, un titre et un sous-titre pour un texte d'affichage de 2 tailles et contextes différents.

Sur chaque ListTile, nous allons afficher une coche grise si la tâche n'est pas terminée et une coche verte si la tâche est terminée. Pour cela, nous pouvons utiliser l'opérateur ternaire qui est? , semblable à une déclaration if-else.

Pour l'utiliser, nous fournissons une vérification de la valeur booléenne à certaines conditions (dans ce cas, vérifie l'indicateur terminé pour un élément dans Firebase) et le terminons avec?

(_todoList [index] .completed)? [Faites quelque chose si vous avez terminé]: [Faites quelque chose si vous n’avez pas terminé]

Par conséquent, notre ListTile ressemble à ceci:

child: ListTile (
  titre: Texte (
    matière,
    style: TextStyle (fontSize: 20.0),
  ),
  fin: IconButton (
      icône: (_todoList [index] .completed)
          ? Icône(
        Icons.done_outline,
        couleur: Colors.green,
        taille: 20.0,
      )
          : Icône (Icons.done, couleur: Colors.grey, taille: 20.0),
      onPressed: () {
        _updateTodo (_todoList [index]);
      }),
)

Et dans l'ensemble ListView:

Widget _showTodoList () {
  si (_todoList.length> 0) {
    retourne ListView.builder (
        shrinkWrap: true,
        itemCount: _todoList.length,
        itemBuilder: (contexte BuildContext, index int) {
          Chaîne todoId = _todoList [index] .key;
          Sujet de chaîne = _todoList [index] .subject;
          bool completed = _todoList [index] .completed;
          String userId = _todoList [index] .userId;
          return Rejetable (
            clé: clé (todoId),
            fond: Conteneur (couleur: Colors.red),
            onDismissed: (direction) async {
              _deleteTodo (todoId, index);
            },
            child: ListTile (
              titre: Texte (
                matière,
                style: TextStyle (fontSize: 20.0),
              ),
              fin: IconButton (
                  icône: (terminé)
                      ? Icône(
                    Icons.done_outline,
                    couleur: Colors.green,
                    taille: 20.0,
                  )
                      : Icône (Icons.done, couleur: Colors.grey, taille: 20.0),
                  onPressed: () {
                    _updateTodo (_todoList [index]);
                  }),
            ),
          )
        });
  } autre {
    return Center (child: Text ("Bienvenue. Votre liste est vide",
      textAlign: TextAlign.center,
      style: TextStyle (fontSize: 30.0),));
  }
}

Notez que le ListTile est encapsulé avec un autre appel de widget Dismissible. Il s'agit d'un widget qui permet à l'utilisateur de balayer tout le ListTile pour imiter le glissement d'action à supprimer.

5Etape 5: Fab fabuleux

Toujours à la home_page.dart, dans la méthode de génération qui retourne un échafaudage, en dessous du corps, nous allons créer un bouton d’action FAB ou flottant. Ce bouton a pour but de permettre à l’utilisateur d’ajouter de nouvelles tâches à la liste. Le FAB affichera une boîte de dialogue d’alerte contenant un champ de texte dans lequel l’utilisateur pourra entrer le nom du nouveau travail.

floatingActionButton: FloatingActionButton (
  onPressed: () {
    _showDialog (contexte);
  },
  info-bulle: 'Incrément',
  child: Icône (Icons.add),
)

Pour que la boîte de dialogue d'alerte apparaisse, vous ne pouvez pas simplement renvoyer un AlertDialog et vous attendre à ce qu'il s'affiche. Au lieu de cela, nous devons utiliser wait showDialog () et renvoyer AlertDialog à l'intérieur de ce générateur. AlertDialog hébergera un champ de texte dont la valeur sera conservée par un textEditingController avec 2 boutons plats de sauvegarde et d'annulation. Le bouton de sauvegarde obtiendra évidemment le nouveau nom de l’item de tâche et créera une nouvelle instance de tâche avant de télécharger dans Firebase.

_showDialog (contexte BuildContext) async {
  _textEditingController.clear ();
  attendez showDialog  (
      contexte: contexte,
      constructeur: (contexte BuildContext) {
        retourne AlertDialog (
          content: new Row (
            enfants:  [
              nouveau Expanded (
                  child: new TextField (
                contrôleur: _textEditingController,
                autofocus: vrai,
                décoration: nouvelle InputDecoration (
                  labelText: 'Ajouter une nouvelle tâche',
                ),
              ))
            ],
          ),
          actions:  [
            new FlatButton (
                child: const Text ('Annuler'),
                onPressed: () {
                  Navigator.pop (contexte);
                }),
            new FlatButton (
                child: const Text ('Save'),
                onPressed: () {
                  _addNewTodo (_textEditingController.text.toString ());
                  Navigator.pop (contexte);
                })
          ],
        )
      });
}

Etape 5: Passons au CRUD

Créer

Pour créer un nouvel élément de tâche, nous prendrons le nom saisi par l'utilisateur dans le champ de texte situé dans AlertDialog lorsqu'ils ont tapé sur FloatingActionButton. Nous instancions un nouvel objet todo avec le nom saisi. Enfin, nous téléchargeons sur Firebase en utilisant _database.reference (). Child (“todo”). Push (). Set (todo.toJson ())

_addNewTodo (String todoItem) {
  if (todoItem.length> 0) {
    Todo todo = new Todo (todoItem.toString (), widget.userId, false);
    _database.reference (). child ("todo"). push (). set (todo.toJson ());
  }
}

Lire

Pour lire, il a été mentionné ci-dessus que vous aurez besoin de construire une requête qui est:

_todoQuery = _database
    .référence()
    .child ("todo")
    .orderByChild ("userId")
    .equalTo (widget.userId);

A partir de la requête, nous allons attacher 2 écouteurs qui sont onChildAdded et onChildChanged qui déclencheront chacune des méthodes de rappel avec des instantanés d'événements. À partir de l'instantané de l'événement, nous les convertissons simplement en classe todo et ajoutons à la liste

_onEntryAdded (événement événement) {
  setState (() {
    _todoList.add (Todo.fromSnapshot (event.snapshot));
  });
}
_onEntryChanged (événement événement) {
  var oldEntry = _todoList.singleWhere ((entry) {
    return entry.key == event.snapshot.key;
  });

  setState (() {
    _todoList [_todoList.indexOf (oldEntry)] =
        Todo.fromSnapshot (event.snapshot);
  });
}

Pour améliorer les requêtes basées sur userId, il est recommandé de définir une règle dans les règles RTDB de Firebase. Ceci est l'indexation des appels et aide Firebase à optimiser votre arrangement de données afin d'améliorer le temps de réponse. Pour plus d'informations, voir Indexer vos données par Firebase.

{
  / * Consultez la page https://firebase.google.com/docs/database/security pour en savoir plus sur les règles de sécurité. * /
  "règles": {
    "faire": {
      ".indexOn": "userId",
    },
    ".read": vrai,
    ".write": true
  }
}

Update

L'utilisateur peut marquer chaque tâche comme terminée ou annuler cette étape. Ils peuvent simplement appuyer sur l’icône de coche à droite de chaque todoListTile. Pour la mise à jour, nous devons obtenir todo.key car nous devons accéder à path / todo / todo-unique-key pour pouvoir mettre à jour le contenu de ce chemin. La méthode est similaire à Create dans le sens où elle utilise .set (), mais la différence réside dans l’ajout de .child (todo.key) dans le chemin.

_updateTodo (Todo todo) {
  // Basculement terminé
  todo.completed =! todo.completed;
  if (todo! = null) {
    _database.reference (). child ("todo"). child (todo.key) .set (todo.toJson ());
  }
}

El Supprimer

Supprimer un élément de Firebase est simple. Semblable à Update, nous devons obtenir le bon todo.key mais nous utiliserons la méthode .remove ().

Notez qu’il n’existe pas d’écouteur pour la suppression d’éléments contrairement à l’écouteur d’élément ajouté ou modifié. Par conséquent, il est impossible que ces deux méthodes d'écoute soient déclenchées et obtiennent le dernier instantané de la base de données. Pour cela, nous devons supprimer manuellement l'élément de notre variable locale _todoList uniquement lorsque la suppression de Firebase est réussie.
_deleteTodo (String todoId, int index) {
  _database.reference (). child ("todo"). child (todoId) .remove (). then ((_) {
    print ("Supprimer $ todoId réussie");
    setState (() {
      _todoList.removeAt (index);
    });
  });
}

Démo

Voici à quoi ressemble l'application

Démo de l'application finale

Github

Code source disponible:

https://github.com/tattwei46/flutter_login_demo

Appréciation

Merci d'avoir pris le temps de lire ce post. J'espère que cela vous aidera dans votre merveilleux voyage avec Flutter. Si vous trouvez cela utile, veuillez m'encourager à écrire plus d'articles comme celui-ci