Flutter: come eseguire CRUD con Firebase RTDB

introduzione

Nel precedente post di Flutter: come eseguire l'accesso degli utenti con Firebase, abbiamo parlato di come implementare l'accesso degli utenti o di registrare la schermata con l'autenticazione Firebase. Utilizzando lo stesso progetto, mostreremo CRUD o creeremo, leggeremo, aggiorneremo ed elimineremo le operazioni con Firebase RTDB o database in tempo reale in questo post.

Iniziare

Questo progetto richiede di registrare il progetto con Firebase e includere il file di configurazione scaricato nel progetto. Puoi ottenere i passaggi necessari nel precedente post menzionato sopra. Questo passaggio è necessario solo se si preferisce configurare il proprio database Firebase, altrimenti si è liberi di usare il mio con il file di configurazione che ho incluso anche nel progetto github. Trova il link al progetto in fondo a questo post.

Passo 1: creare la classe del modello

Quindi, tornando a dove abbiamo lasciato in precedenza, siamo riusciti a visualizzare il messaggio di benvenuto dopo che l'utente ha effettuato correttamente l'accesso al proprio account. Questo è mostrato all'interno di home_page.dart. Per semplificare la nostra applicazione da fare, memorizzeremo semplicemente il nome da fare e consentiremo all'utente di contrassegnare come completato o meno. Per memorizzare informazioni su ogni elemento da fare, è necessario disporre di una classe di modello. Una classe di modello per un elemento da fare dovrebbe apparire così:

/models/todo.dart

class Todo {
  Chiave stringa;
  Soggetto della stringa;
  bool completato;
  String userId;

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

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

  toJson () {
    ritorno {
      "userId": userId,
      "soggetto": soggetto,
      "completato": completato,
    };
  }
}

Ogni elemento da fare è unico e ha la sua chiave. Ogni elemento ha un nome o un soggetto, un flag per tenere traccia del suo completamento o completamento e ID utente di chi ha creato questo elemento. Per creare un nuovo todo, tutti i parametri tranne la chiave sono necessari per passare al costruttore Todo (). La chiave viene generata automaticamente da RTDB e memorizzata quando viene aggiunta una nuova operazione.

Quando i dati vengono recuperati da Firebase RTDB, sono in formato json. Quindi abbiamo Todo.fromSnapshot (DataSnapshot snapshot) che ci consente di mappare i dati dal formato json al formato Todo. ToJson () fa il contrario, ovvero mappare i dati in formato json prima di caricarli in Firebase RTDB.

Fase 2: inizializza la query

Nella nostra home_page.dart, abbiamo creato un elenco di todos utilizzando List _todoList = new List (). Quando un elenco di todos viene recuperato da Firebase, lo memorizzeremo nelle variabili dell'elenco locale.

Usiamo FirebaseDatabase finale _database = FirebaseDatabase.instance; per accedere all'istanza di Firebase. Quindi costruiamo una query da questa istanza usando:

Query _todoQuery = _database
    .riferimento()
    .child ( "todo")
    .orderByChild ( "UserID")
    .equalTo (widget.userId);

In questa query, utilizzando l'istanza FirebaseDatabase, recuperiamo un riferimento a tutti i dati in path / todo. Se hai un altro livello in todo, la tua query sarebbe _database.reference (). Child ("todo"). Child ("altro livello"). Sia .orderByChild ("xx xx") che .equalTo ("xx xx") sono ciò che uso per dire a Firebase che voglio un elenco di todos in cui ogni utente di todo è quello che ti do. Ha senso?

Ecco come appare nell'RTDB:

Fase 3: impostazione dei listener

Usando la query che abbiamo appena creato sopra, vi allegheremo 2 tipi di abbonamenti stream. Uno è onChildAdded e un altro è onChildChanged. Ciò che onChildAdded.listen () è in ascolto per ogni nuovo elemento todo aggiunto in Firebase e riceve un evento e passa alla funzione di callback che in questo caso è _onEntryAdded. Lo stesso vale per onChildChanged.listen (), che ascolta qualsiasi modifica dei dati nel Firebase come mark todo item come fatto.

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

Quindi qual è la funzione di _onEntryAdded? Cattura l'istantanea dell'evento e si converte da json a todo in formato modello e si aggiunge all'elenco di todos.

_onEntryAdded (evento evento) {
  setState (() {
    _todoList.add (Todo.fromSnapshot (event.snapshot));
  });
}

Per la funzione _onEntryChanged, recupera la chiave dall'istantanea dell'evento e ottiene l'indice dall'elenco di todo. Quindi dall'indice dell'elenco, aggiorna quella particolare operazione con quella dell'istantanea dell'evento.

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

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

Per annullare correttamente l'iscrizione a StreamSubscription, utilizziamo semplicemente .cancel () all'interno del metodo dispose ()

@oltrepassare
void dispose () {
  _onTodoAddedSubscription.cancel ();
  _onTodoChangedSubscription.cancel ();
  super.dispose ();
}

TeFase 4: costruisci ListView

Mi piace usare ListView quando necessario per scorrere su un elenco di elementi che cambia in modo dinamico nelle dimensioni e li mostra in un elenco. Quindi, in questo caso, eseguiremo l'iterazione su ciascun todo in _todoList. ListView accetta itemCount che è semplicemente la dimensione dell'elenco todo, ovvero _todoList.count. ListView include anche itemBuilder che è la parte che costruirà il singolo riquadro per visualizzare un singolo elemento todo. Utilizzeremo il widget ListTile per visualizzare un singolo oggetto todo. ListTile accetta alcuni parametri come il trailing per mettere l'icona o altri widget sul lato destro di ListTile, il titolo e il sottotitolo per visualizzare il testo di 2 contesti e dimensioni diversi, portando simili al trailing ma per il lato sinistro del ListTile e altri.

Su ogni ListTile, visualizzeremo un segno di spunta grigio se il todo non è completato e un segno di spunta verde se il todo è completato. Per questo, possiamo usare l'operatore ternario che è? , simile a un'istruzione if-else.

Per usarlo, forniamo un bool check su determinate condizioni (in questo caso sta controllando il flag completato per un articolo in Firebase) e lo finiamo con?

(_todoList [index] .completed)? [Fai qualcosa se completato]: [Fai qualcosa se non completato]

Quindi, il nostro ListTile è simile al seguente:

figlio: ListTile (
  titolo: testo (
    soggetto,
    stile: TextStyle (fontSize: 20.0),
  ),
  trailing: IconButton (
      icona: (_todoList [index] .completed)
          ? Icona(
        Icons.done_outline,
        colore: colori verde,
        dimensione: 20,0,
      )
          : Icona (Icons.done, colore: Colors.grey, dimensione: 20.0),
      onPressed: () {
        _updateTodo (_todoList [index]);
      }),
)

E ListView generale:

Widget _showTodoList () {
  if (_todoList.length> 0) {
    return ListView.builder (
        shrinkWrap: true,
        itemCount: _todoList.length,
        itemBuilder: (contesto BuildContext, indice int) {
          String todoId = _todoList [indice] .key;
          String subject = _todoList [index] .subject;
          bool completato = _todoList [index] .completed;
          Stringa userId = _todoList [indice] .userId;
          return Dismissible (
            chiave: chiave (todoId),
            sfondo: contenitore (color: Colors.red),
            onDismissed: (direction) async {
              _deleteTodo (todoId, index);
            },
            figlio: ListTile (
              titolo: testo (
                soggetto,
                stile: TextStyle (fontSize: 20.0),
              ),
              trailing: IconButton (
                  icona: (completato)
                      ? Icona(
                    Icons.done_outline,
                    colore: colori verde,
                    dimensione: 20,0,
                  )
                      : Icona (Icons.done, colore: Colors.grey, dimensione: 20.0),
                  onPressed: () {
                    _updateTodo (_todoList [index]);
                  }),
            ),
          );
        });
  } altro {
    centro di restituzione (figlio: testo ("Benvenuto. Il tuo elenco è vuoto",
      textAlign: TextAlign.center,
      stile: TextStyle (fontSize: 30.0),));
  }
}

Si noti che ListTile è racchiuso in un'altra chiamata del widget Non consentito. Questo è un widget che consente all'utente di scorrere l'intero ListTile per simulare il colpo di azione da eliminare.

TeFase 5: Fabulous FAB

Sempre su home_page.dart, nel metodo build che restituisce un ponteggio, sotto il corpo, creeremo un pulsante di azione mobile o FAB. Lo scopo di questo pulsante è consentire all'utente di aggiungere una nuova operazione nell'elenco. Il FAB mostrerà una finestra di avviso contenente un campo di testo in cui l'utente può inserire il nome del nuovo todo.

floatingActionButton: FloatingActionButton (
  onPressed: () {
    _showDialog (contesto);
  },
  descrizione comando: 'Incremento',
  child: Icon (Icons.add),
)

Per visualizzare la finestra di avviso, non puoi semplicemente restituire un AlertDialog e aspettarti che venga mostrato. Invece dobbiamo usare waitit showDialog () e restituire AlertDialog all'interno di questo builder. AlertDialog ospiterà un campo di testo il cui valore sarà mantenuto da un textEditingController con 2 FlatButtons di salvataggio e cancellazione. Il pulsante Salva otterrà ovviamente il nuovo nome dell'elemento todo e creerà una nuova istanza todo prima di caricarlo nel Firebase.

_showDialog (contesto BuildContext) asincrono {
  _textEditingController.clear ();
  attende showDialog  (
      contesto: contesto,
      builder: (contesto BuildContext) {
        return AlertDialog (
          contenuto: nuova riga (
            figli:  [
              nuovo espanso (
                  figlio: nuovo TextField (
                controller: _textEditingController,
                autofocus: vero,
                decorazione: nuovo InputDecoration (
                  labelText: 'Aggiungi nuovo todo',
                ),
              ))
            ],
          ),
          azioni:  [
            nuovo FlatButton (
                child: const Text ('Annulla'),
                onPressed: () {
                  Navigator.pop (contesto);
                }),
            nuovo FlatButton (
                figlio: const Text ('Salva'),
                onPressed: () {
                  _addNewTodo (_textEditingController.text.toString ());
                  Navigator.pop (contesto);
                })
          ],
        );
      });
}

TeFase 5: Let's CRUD

Create

Per creare un nuovo oggetto todo, prenderemo il nome inserito dall'utente in TextField all'interno di AlertDialog quando hanno toccato FloatingActionButton. Istanziamo un nuovo oggetto todo con l'input del nome. Finalmente cariciamo su Firebase usando _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 () figlio ( "todo") spingere () set (todo.toJson ());
  }
}

Read

Per leggere, è stato menzionato sopra che sarà necessario creare una query che sia:

_todoQuery = _database
    .riferimento()
    .child ( "todo")
    .orderByChild ( "UserID")
    .equalTo (widget.userId);

Dalla query, allegheremo 2 listener che sono onChildAdded e onChildChanged che attiveranno i rispettivi metodi di callback con istantanee di eventi. Dall'istantanea dell'evento, li convertiamo semplicemente in todo class e li aggiungiamo all'elenco

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

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

Per contribuire a migliorare le query basate su userId, si consiglia di impostare una regola nelle regole di Firebase RTDB. Si tratta di indicizzazione delle chiamate e aiuta Firebase a ottimizzare la disposizione dei dati migliorando i tempi di risposta. Per ulteriori informazioni, consultare Index Your Data by Firebase.

{
  / * Visita https://firebase.google.com/docs/database/security per ulteriori informazioni sulle regole di sicurezza. * /
  "regole": {
    "fare": {
      ".indexOn": "userId",
    },
    ".read": vero,
    ".write": vero
  }
}

Update

L'utente può contrassegnare ogni elemento todo come completato o annullare questo passaggio. Possono semplicemente toccare l'icona del segno di spunta sul lato destro di ogni todoListTile. Per l'aggiornamento, è necessario ottenere todo.key poiché è necessario accedere a path / todo / todo-unique-key per poter aggiornare ciò che si trova in quel percorso. Il metodo è simile a Crea nel senso che sta usando .set () ma la differenza è l'aggiunta di .child (todo.key) nel percorso.

_updateTodo (Todo todo) {
  // Attiva / disattiva completato
  todo.completed =! todo.completed;
  if (todo! = null) {
    .. _Database.reference () figlio ( "todo") figlio (todo.key) .set (todo.toJson ());
  }
}

Delete

L'eliminazione dell'elemento da Firebase è semplice. Simile all'aggiornamento, dobbiamo ottenere il todo.key corretto ma useremo il metodo .remove ().

Si noti che non esiste un listener per la rimozione di elementi a differenza del listener per gli articoli aggiunti o modificati. Quindi non è possibile che questi 2 metodi di ascolto vengano attivati ​​e ottengano l'istantanea più recente del database. Per questo, dobbiamo rimuovere manualmente l'elemento dalla nostra variabile locale _todoList solo quando l'eliminazione da Firebase ha esito positivo.
_deleteTodo (String todoId, int index) {
  _database.reference (). child ("todo"). child (todoId) .remove (). then ((_) {
    print ("Elimina $ todoId correttamente");
    setState (() {
      _todoList.removeAt (indice);
    });
  });
}

dimostrazione

Ecco come appare l'applicazione

Demo dell'app finale

Github

Codice sorgente disponibile:

https://github.com/tattwei46/flutter_login_demo

Apprezzamento

Grazie per aver dedicato del tempo a leggere questo post. Spero che ti aiuti nel tuo meraviglioso viaggio con Flutter. Se lo trovi utile, ti prego di incoraggiarmi a scrivere altri articoli come questo