Conosci Firebase per Flutter

1. Prima di iniziare

In questo codelab imparerai alcune delle basi di Firebase per creare app mobili Flutter per Android e iOS.

Prerequisiti

Questo codelab presuppone che tu abbia familiarità con Flutter e che tu abbia installato Flutter SDK e un editor .

Cosa creerai

In questo codelab creerai un'app RSVP per eventi e un'app di chat del libro degli ospiti su Android, iOS, Web e macOS utilizzando Flutter. Autenticherai gli utenti con l'autenticazione Firebase e sincronizzerai i dati utilizzando Cloud Firestore.

Di cosa avrai bisogno

Puoi eseguire questo codelab utilizzando uno dei seguenti dispositivi:

Oltre a quanto sopra, avrai bisogno anche di:

  • Un browser a tua scelta, come Chrome.
  • Un IDE o un editor di testo a tua scelta, come Android Studio o VS Code configurato con i plugin Dart e Flutter.
  • L'ultima versione stable di Flutter (o beta se ti piace vivere al limite).
  • Un account Google, come un account Gmail, per creare e gestire il tuo progetto Firebase.
  • Lo strumento da riga di comando di firebase , effettuato l'accesso al tuo account Gmail.
  • Il codice di esempio del codelab. Vedere il passaggio successivo per come ottenere il codice.

2. Ottieni il codice di esempio

Iniziamo scaricando la versione iniziale del nostro progetto da GitHub.

Clona il repository GitHub dalla riga di comando:

git clone https://github.com/flutter/codelabs.git flutter-codelabs

In alternativa, se hai installato lo strumento cli di GitHub :

gh repo clone flutter/codelabs flutter-codelabs

Il codice di esempio deve essere clonato nella directory flutter-codelabs , che contiene il codice per una raccolta di codelab. Il codice per questo codelab è in flutter-codelabs/firebase-get-to-know-flutter .

La struttura della directory in flutter-codelabs/firebase-get-to-know-flutter è una serie di istantanee di dove dovresti trovarti alla fine di ogni passaggio denominato. Questo è il passaggio 2, quindi individuare i file corrispondenti è facile come:

cd flutter-codelabs/firebase-get-to-know-flutter/step_02

Se vuoi andare avanti o vedere come dovrebbe essere qualcosa dopo un passaggio, cerca nella directory che prende il nome dal passaggio che ti interessa.

Importa l'app di avviamento

Apri o importa la flutter-codelabs/firebase-get-to-know-flutter/step_02 nel tuo IDE preferito. Questa directory contiene il codice iniziale per il codelab che consiste in un'app di incontro Flutter non ancora funzionante.

Individua i file su cui lavorare

Il codice in questa app è distribuito su più directory. Questa suddivisione delle funzionalità è progettata per semplificare il lavoro raggruppando il codice per funzionalità.

Individua i seguenti file nel progetto:

  • lib/main.dart : questo file contiene il punto di ingresso principale e il widget dell'applicazione.
  • lib/src/widgets.dart : questo file contiene una manciata di widget per aiutare a standardizzare lo stile dell'applicazione. Questi sono usati per comporre lo schermo dell'app di avviamento.
  • lib/src/authentication.dart : questo file contiene un'implementazione parziale di FirebaseUI Auth con una serie di widget per creare un'esperienza utente di accesso per l'autenticazione basata su e-mail Firebase. Questi widget per il flusso di autenticazione non sono ancora utilizzati nell'app di avvio, ma li collegherai presto.

Aggiungerai file aggiuntivi come richiesto per creare il resto dell'applicazione.

Revisione del file lib/main.dart

Questa app sfrutta il pacchetto google_fonts per consentirci di rendere Roboto il carattere predefinito in tutta l'app. Un esercizio per il lettore motivato è esplorare fonts.google.com e utilizzare i caratteri che trovi lì in diverse parti dell'app.

Stai utilizzando i widget di supporto da lib/src/widgets.dart sotto forma di Header , Paragraph e IconAndDetail . Questi widget riducono il disordine nel layout della pagina descritto in HomePage eliminando il codice duplicato. Questo ha l'ulteriore vantaggio di consentire un aspetto coerente.

Ecco come appare la tua app su Android, iOS, Web e macOS:

Anteprima dell'app

3. Crea e configura un progetto Firebase

Visualizzare le informazioni sull'evento è ottimo per i tuoi ospiti, ma mostrare semplicemente gli eventi non è molto utile per nessuno. Aggiungiamo alcune funzionalità dinamiche a questa app. Per questo, dovrai collegare Firebase alla tua app. Per iniziare con Firebase, devi creare e configurare un progetto Firebase.

Crea un progetto Firebase

  1. Accedi a Firebase .
  2. Nella console Firebase, fai clic su Aggiungi progetto (o Crea un progetto ) e assegna un nome al tuo progetto Firebase Firebase-Flutter-Codelab .

4395e4e67c08043a.png

  1. Fare clic sulle opzioni di creazione del progetto. Accetta i termini di Firebase se richiesto. Salta la configurazione di Google Analytics, perché non utilizzerai Analytics per questa app.

b7138cde5f2c7b61.png

Per ulteriori informazioni sui progetti Firebase, consulta Informazioni sui progetti Firebase .

L'app che stai creando utilizza diversi prodotti Firebase disponibili per le app Web:

  • Autenticazione Firebase per consentire ai tuoi utenti di accedere alla tua app.
  • Cloud Firestore per salvare i dati strutturati sul cloud e ricevere notifiche istantanee quando i dati cambiano.
  • Regole di sicurezza Firebase per proteggere il tuo database.

Alcuni di questi prodotti richiedono una configurazione speciale o devono essere abilitati utilizzando la console Firebase.

Abilita l'accesso e-mail per l'autenticazione Firebase

Per consentire agli utenti di accedere all'app Web, utilizzerai il metodo di accesso tramite e- mail/password per questo codelab:

  1. Nella console Firebase, espandi il menu Crea nel pannello di sinistra.
  2. Fare clic su Autenticazione e quindi fare clic sul pulsante Inizia , quindi sulla scheda Metodo di accesso (o fare clic qui per passare direttamente alla scheda Metodo di accesso).
  3. Fare clic su Email/Password nell'elenco dei provider di accesso, impostare l' opzione Abilita sulla posizione attiva, quindi fare clic su Salva . 58e3e3e23c2f16a4.png

Abilita Cloud Firestore

L'app Web utilizza Cloud Firestore per salvare i messaggi di chat e ricevere nuovi messaggi di chat.

Abilita Cloud Firestore:

  1. Nella sezione Build della console Firebase, fai clic su Cloud Firestore .
  2. Fare clic su Crea database . 99e8429832d23fa3.png
  1. Selezionare l'opzione Avvia in modalità test . Leggi il disclaimer sulle regole di sicurezza. La modalità test garantisce la possibilità di scrivere liberamente nel database durante lo sviluppo. Fare clic su Avanti . 6be00e26c72ea032.png
  1. Seleziona la posizione per il tuo database (puoi semplicemente usare l'impostazione predefinita). Tieni presente che questa posizione non può essere modificata in un secondo momento. 278656eefcfb0216.png
  2. Fare clic su Abilita .

4. Configurazione Firebase

Per utilizzare Firebase con Flutter, è necessario seguire una procedura per configurare il progetto Flutter per utilizzare correttamente le librerie FlutterFire:

  • Aggiungi le dipendenze FlutterFire al tuo progetto
  • Registra la piattaforma desiderata sul progetto Firebase
  • Scarica il file di configurazione specifico della piattaforma e aggiungilo al codice.

Nella directory di primo livello della tua app Flutter, ci sono sottodirectory denominate android , ios , macos e web . Queste directory contengono i file di configurazione specifici della piattaforma rispettivamente per iOS e Android.

Configura le dipendenze

Devi aggiungere le librerie FlutterFire per i due prodotti Firebase che stai utilizzando in questa app: Firebase Auth e Cloud Firestore. Esegui i tre comandi seguenti per aggiungere le dipendenze.

$ flutter pub add firebase_core 
Resolving dependencies...
+ firebase_core 1.10.5
+ firebase_core_platform_interface 4.2.2
+ firebase_core_web 1.5.2
+ flutter_web_plugins 0.0.0 from sdk flutter
+ js 0.6.3
  test_api 0.4.3 (0.4.8 available)
Changed 5 dependencies!

firebase_core è il codice comune richiesto per tutti i plugin Firebase Flutter.

$ flutter pub add firebase_auth
Resolving dependencies...
+ firebase_auth 3.3.3
+ firebase_auth_platform_interface 6.1.8
+ firebase_auth_web 3.3.4
+ intl 0.17.0
  test_api 0.4.3 (0.4.8 available)
Changed 4 dependencies!

firebase_auth consente l'integrazione con la funzionalità di autenticazione di Firebase.

$ flutter pub add cloud_firestore
Resolving dependencies...
+ cloud_firestore 3.1.4
+ cloud_firestore_platform_interface 5.4.9
+ cloud_firestore_web 2.6.4
  test_api 0.4.3 (0.4.8 available)
Changed 3 dependencies!

Il cloud_firestore consente l'accesso all'archiviazione dei dati di Cloud Firestore.

$ flutter pub add provider
Resolving dependencies...
+ nested 1.0.0
+ provider 6.0.1
  test_api 0.4.3 (0.4.8 available)
Changed 2 dependencies!

Dopo aver aggiunto i pacchetti richiesti, devi anche configurare i progetti iOS, Android, macOS e Web runner per utilizzare Firebase in modo appropriato. Si utilizza anche il pacchetto del provider che consentirà la separazione della logica aziendale dalla logica di visualizzazione.

Installazione flutterfire

La CLI FlutterFire dipende dalla CLI Firebase sottostante. Se non l'hai già fatto, assicurati che l'interfaccia a riga di comando di Firebase sia installata sul tuo computer.

Quindi, installa FlutterFire CLI eseguendo il comando seguente:

$ dart pub global activate flutterfire_cli

Una volta installato, il comando flutterfire sarà disponibile a livello globale.

Configurazione delle tue app

La CLI estrae le informazioni dal tuo progetto Firebase e dalle applicazioni del progetto selezionate per generare tutta la configurazione per una piattaforma specifica.

Nella radice della tua applicazione, esegui il comando configure:

$ flutterfire configure

Il comando di configurazione ti guiderà attraverso una serie di processi:

  1. Selezione di un progetto Firebase (basato sul file .firebaserc o dalla console Firebase).
  2. Chiedi per quali piattaforme (ad es. Android, iOS, macOS e Web) desideri configurare.
  3. Identifica le applicazioni Firebase per le piattaforme scelte per cui estrarre la configurazione. Per impostazione predefinita, la CLI tenterà di abbinare automaticamente le app Firebase in base alla configurazione del progetto corrente.
  4. Genera un file firebase_options.dart nel tuo progetto.

Configura macOS

Flutter su macOS crea applicazioni completamente sandbox. Poiché questa applicazione si sta integrando utilizzando la rete per comunicare con i server Firebase, dovrai configurare l'applicazione con i privilegi del client di rete.

macos/Runner/DebugProfile.entitlements

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>com.apple.security.app-sandbox</key>
	<true/>
	<key>com.apple.security.cs.allow-jit</key>
	<true/>
	<key>com.apple.security.network.server</key>
	<true/>
  <!-- Add the following two lines -->
	<key>com.apple.security.network.client</key>
	<true/>
</dict>
</plist>

macos/Runner/Release.entitlements

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>com.apple.security.app-sandbox</key>
	<true/>
  <!-- Add the following two lines -->
	<key>com.apple.security.network.client</key>
	<true/>
</dict>
</plist>

Per maggiori dettagli, vedere Diritti e App Sandbox .

5. Aggiungi l'accesso utente (RSVP)

Ora che hai aggiunto Firebase all'app, puoi impostare un pulsante RSVP che registra le persone che utilizzano l'autenticazione Firebase . Per Android nativo, iOS nativo e Web sono disponibili pacchetti FirebaseUI Auth predefiniti, ma per Flutter dovrai creare questa funzionalità.

Il progetto recuperato nel passaggio 2 includeva una serie di widget che implementano l'interfaccia utente per la maggior parte del flusso di autenticazione. Implementerai la logica aziendale per integrare l'autenticazione Firebase nell'applicazione.

Logica aziendale con provider

Utilizzerai il pacchetto del provider per rendere disponibile un oggetto stato dell'applicazione centralizzato nell'albero dell'applicazione dei widget Flutter. Per cominciare, modifica le importazioni nella parte superiore di lib/main.dart :

lib/main.dart

import 'package:firebase_auth/firebase_auth.dart'; // new
import 'package:firebase_core/firebase_core.dart'; // new
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:provider/provider.dart';           // new

import 'firebase_options.dart';                    // new
import 'src/authentication.dart';                  // new
import 'src/widgets.dart';

Le righe import introducono Firebase Core e Auth, estraggono il pacchetto del provider che stai utilizzando per rendere disponibile l'oggetto stato dell'applicazione tramite l'albero dei widget e includono i widget di autenticazione da lib/src .

Questo oggetto dello stato dell'applicazione, ApplicationState , ha due responsabilità principali per questo passaggio, ma acquisirà ulteriori responsabilità man mano che si aggiungono ulteriori funzionalità all'applicazione nei passaggi successivi. La prima responsabilità è inizializzare la libreria Firebase con una chiamata a Firebase.initializeApp() , quindi c'è la gestione del flusso di autorizzazione. Aggiungi la seguente classe alla fine di lib/main.dart :

lib/main.dart

class ApplicationState extends ChangeNotifier {
  ApplicationState() {
    init();
  }

  Future<void> init() async {
    await Firebase.initializeApp(
      options: DefaultFirebaseOptions.currentPlatform,
    );

    FirebaseAuth.instance.userChanges().listen((user) {
      if (user != null) {
        _loginState = ApplicationLoginState.loggedIn;
      } else {
        _loginState = ApplicationLoginState.loggedOut;
      }
      notifyListeners();
    });
  }

  ApplicationLoginState _loginState = ApplicationLoginState.loggedOut;
  ApplicationLoginState get loginState => _loginState;

  String? _email;
  String? get email => _email;

  void startLoginFlow() {
    _loginState = ApplicationLoginState.emailAddress;
    notifyListeners();
  }

  Future<void> verifyEmail(
    String email,
    void Function(FirebaseAuthException e) errorCallback,
  ) async {
    try {
      var methods =
          await FirebaseAuth.instance.fetchSignInMethodsForEmail(email);
      if (methods.contains('password')) {
        _loginState = ApplicationLoginState.password;
      } else {
        _loginState = ApplicationLoginState.register;
      }
      _email = email;
      notifyListeners();
    } on FirebaseAuthException catch (e) {
      errorCallback(e);
    }
  }

  Future<void> signInWithEmailAndPassword(
    String email,
    String password,
    void Function(FirebaseAuthException e) errorCallback,
  ) async {
    try {
      await FirebaseAuth.instance.signInWithEmailAndPassword(
        email: email,
        password: password,
      );
    } on FirebaseAuthException catch (e) {
      errorCallback(e);
    }
  }

  void cancelRegistration() {
    _loginState = ApplicationLoginState.emailAddress;
    notifyListeners();
  }

  Future<void> registerAccount(
      String email,
      String displayName,
      String password,
      void Function(FirebaseAuthException e) errorCallback) async {
    try {
      var credential = await FirebaseAuth.instance
          .createUserWithEmailAndPassword(email: email, password: password);
      await credential.user!.updateDisplayName(displayName);
    } on FirebaseAuthException catch (e) {
      errorCallback(e);
    }
  }

  void signOut() {
    FirebaseAuth.instance.signOut();
  }
}

Vale la pena notare alcuni punti chiave in questa classe. L'utente inizia senza autenticazione, l'app mostra un modulo che richiede l'indirizzo e-mail dell'utente, a seconda che l'indirizzo e-mail sia in archivio, l'app chiederà all'utente di registrarsi o richiederà la sua password, quindi supponendo che tutto funzioni, l'utente è autenticato.

Va notato che questa non è un'implementazione completa del flusso di autenticazione FirebaseUI, poiché non gestisce il caso di un utente con un account esistente che ha problemi di accesso. L'implementazione di questa funzionalità aggiuntiva è lasciata come esercizio al lettore motivato.

Integrazione del flusso di autenticazione

Ora che hai l'inizio dello stato dell'applicazione, è il momento di collegare lo stato dell'applicazione all'inizializzazione dell'app e aggiungere il flusso di autenticazione in HomePage . Aggiorna il punto di ingresso principale per integrare lo stato dell'applicazione tramite il pacchetto del provider :

lib/main.dart

void main() {
  // Modify from here
  runApp(
    ChangeNotifierProvider(
      create: (context) => ApplicationState(),
      builder: (context, _) => App(),
    ),
  );
  // to here.
}

La modifica alla funzione main rende il pacchetto del provider responsabile dell'istanza dell'oggetto stato dell'applicazione utilizzando il widget ChangeNotifierProvider . Stai usando questa specifica classe del provider perché l'oggetto dello stato dell'applicazione estende ChangeNotifier e ciò consente al pacchetto del provider di sapere quando visualizzare nuovamente i widget dipendenti. Infine, integra lo stato dell'applicazione con Authentication aggiornando il metodo di build di HomePage :

lib/main.dart

class HomePage extends StatelessWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Firebase Meetup'),
      ),
      body: ListView(
        children: <Widget>[
          Image.asset('assets/codelab.png'),
          const SizedBox(height: 8),
          const IconAndDetail(Icons.calendar_today, 'October 30'),
          const IconAndDetail(Icons.location_city, 'San Francisco'),
          // Add from here
          Consumer<ApplicationState>(
            builder: (context, appState, _) => Authentication(
              email: appState.email,
              loginState: appState.loginState,
              startLoginFlow: appState.startLoginFlow,
              verifyEmail: appState.verifyEmail,
              signInWithEmailAndPassword: appState.signInWithEmailAndPassword,
              cancelRegistration: appState.cancelRegistration,
              registerAccount: appState.registerAccount,
              signOut: appState.signOut,
            ),
          ),
          // to here
          const Divider(
            height: 8,
            thickness: 1,
            indent: 8,
            endIndent: 8,
            color: Colors.grey,
          ),
          const Header("What we'll be doing"),
          const Paragraph(
            'Join us for a day full of Firebase Workshops and Pizza!',
          ),
        ],
      ),
    );
  }
}

Crea un'istanza del widget Authentication e lo avvolgi in un widget Consumer . Il widget Consumer è il solito modo in cui il pacchetto del provider può essere utilizzato per ricostruire parte dell'albero quando cambia lo stato dell'applicazione. Il widget di Authentication è l'interfaccia utente di autenticazione che testerai ora.

Test del flusso di autenticazione

cdf2d25e436bd48d.png

Ecco l'inizio del flusso di autenticazione, in cui l'utente può toccare il pulsante RSVP, per avviare il modulo di posta elettronica.

2a2cd6d69d172369.png

All'inserimento dell'email il sistema conferma se l'utente è già registrato, in tal caso all'utente viene richiesta una password, in alternativa se l'utente non è registrato, si passa attraverso il form di registrazione.

e5e65065dba36b54.png

Assicurati di provare a inserire una password breve (meno di sei caratteri) per controllare il flusso di gestione degli errori. Se l'utente è registrato, vedrà invece la password per.

fbb3ea35fb4f67a.png

In questa pagina assicurati di inserire password errate per verificare la gestione degli errori in questa pagina. Infine, una volta che l'utente ha effettuato l'accesso, vedrai l'esperienza di accesso che offre all'utente la possibilità di disconnettersi nuovamente.

4ed811a25b0cf816.png

E con ciò, hai implementato un flusso di autenticazione. Congratulazioni!

6. Scrivi messaggi su Cloud Firestore

Sapere che gli utenti stanno arrivando è fantastico, ma diamo agli ospiti qualcos'altro da fare nell'app. E se potessero lasciare messaggi in un guestbook? Possono condividere il motivo per cui sono entusiasti di venire o chi sperano di incontrare.

Per archiviare i messaggi di chat che gli utenti scrivono nell'app, utilizzerai Cloud Firestore .

Modello di dati

Cloud Firestore è un database NoSQL e i dati archiviati nel database sono suddivisi in raccolte, documenti, campi e sottoraccolte. Memorizzerai ogni messaggio della chat come documento in una raccolta di primo livello chiamata guestbook .

7c20dc8424bb1d84.png

Aggiungi messaggi a Firestore

In questa sezione, aggiungerai la funzionalità per consentire agli utenti di scrivere nuovi messaggi nel database. Innanzitutto, aggiungi gli elementi dell'interfaccia utente (campo modulo e pulsante di invio), quindi aggiungi il codice che collega questi elementi al database.

Innanzitutto, aggiungi le importazioni per il pacchetto cloud_firestore e dart:async .

lib/main.dart

import 'dart:async';                                    // new

import 'package:cloud_firestore/cloud_firestore.dart';  // new
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:provider/provider.dart';

import 'firebase_options.dart';
import 'src/authentication.dart';
import 'src/widgets.dart';

Per costruire gli elementi dell'interfaccia utente di un campo messaggio e un pulsante di invio, aggiungi un nuovo widget stateful GuestBook in fondo a lib/main.dart .

lib/main.dart

class GuestBook extends StatefulWidget {
  const GuestBook({required this.addMessage});
  final FutureOr<void> Function(String message) addMessage;

  @override
  _GuestBookState createState() => _GuestBookState();
}

class _GuestBookState extends State<GuestBook> {
  final _formKey = GlobalKey<FormState>(debugLabel: '_GuestBookState');
  final _controller = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: Form(
        key: _formKey,
        child: Row(
          children: [
            Expanded(
              child: TextFormField(
                controller: _controller,
                decoration: const InputDecoration(
                  hintText: 'Leave a message',
                ),
                validator: (value) {
                  if (value == null || value.isEmpty) {
                    return 'Enter your message to continue';
                  }
                  return null;
                },
              ),
            ),
            const SizedBox(width: 8),
            StyledButton(
              onPressed: () async {
                if (_formKey.currentState!.validate()) {
                  await widget.addMessage(_controller.text);
                  _controller.clear();
                }
              },
              child: Row(
                children: const [
                  Icon(Icons.send),
                  SizedBox(width: 4),
                  Text('SEND'),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Ci sono un paio di punti di interesse qui. Innanzitutto, stai creando un'istanza di un modulo in modo da poter convalidare che il messaggio abbia effettivamente del contenuto e mostrare all'utente un messaggio di errore se non ce n'è. Il modo per convalidare un modulo prevede l'accesso allo stato del modulo dietro il modulo e per questo si utilizza un GlobalKey . Per ulteriori informazioni sulle chiavi e su come usarle, vedere l' episodio 101 di Flutter Widgets "Quando usare le chiavi" .

Nota anche il modo in cui sono disposti i widget, hai un Row , con un TextFormField e uno StyledButton , che a sua volta contiene un Row . Nota anche che TextFormField è racchiuso in un widget Expanded , questo costringe TextFormField a occupare spazio aggiuntivo nella riga. Per comprendere meglio il motivo per cui è necessario, leggi Comprensione dei vincoli .

Ora che hai un widget che consente all'utente di inserire del testo da aggiungere al Libro degli Ospiti, devi visualizzarlo sullo schermo. Per fare ciò, modifica il corpo di HomePage per aggiungere le seguenti due righe nella parte inferiore dei figli di ListView :

const Header("What we'll be doing"),
const Paragraph(
  'Join us for a day full of Firebase Workshops and Pizza!',
),
// Add the following two lines.
const Header('Discussion'),
GuestBook(addMessage: (message) => print(message)),

Anche se questo è sufficiente per visualizzare il Widget, non è sufficiente per fare nulla di utile. A breve aggiornerai questo codice per renderlo funzionante.

Anteprima dell'app

Un utente che fa clic sul pulsante INVIA attiverà lo snippet di codice riportato di seguito. Aggiunge il contenuto del campo di input del messaggio alla raccolta del libro degli guestbook del database. In particolare, il metodo addMessageToGuestBook aggiunge il contenuto del messaggio a un nuovo documento (con un ID generato automaticamente) alla raccolta del libro degli guestbook .

Tieni presente che FirebaseAuth.instance.currentUser.uid è un riferimento all'ID univoco generato automaticamente fornito dall'autenticazione Firebase per tutti gli utenti che hanno effettuato l'accesso.

Apportare un'altra modifica al file lib/main.dart . Aggiungi il metodo addMessageToGuestBook . Nel passaggio successivo collegherai l'interfaccia utente e questa funzionalità.

lib/main.dart

class ApplicationState extends ChangeNotifier {

  // Current content of ApplicationState elided ...

  // Add from here
  Future<DocumentReference> addMessageToGuestBook(String message) {
    if (_loginState != ApplicationLoginState.loggedIn) {
      throw Exception('Must be logged in');
    }

    return FirebaseFirestore.instance
        .collection('guestbook')
        .add(<String, dynamic>{
      'text': message,
      'timestamp': DateTime.now().millisecondsSinceEpoch,
      'name': FirebaseAuth.instance.currentUser!.displayName,
      'userId': FirebaseAuth.instance.currentUser!.uid,
    });
  }
  // To here
}

Cablaggio dell'interfaccia utente nel database

Hai un'interfaccia utente in cui l'utente può inserire il testo che desidera aggiungere al Guest Book e hai il codice per aggiungere la voce a Cloud Firestore. Ora tutto ciò che devi fare è collegare i due insieme. In lib/main.dart apporta la seguente modifica al widget HomePage .

lib/main.dart

class HomePage extends StatelessWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Firebase Meetup'),
      ),
      body: ListView(
        children: <Widget>[
          Image.asset('assets/codelab.png'),
          const SizedBox(height: 8),
          const IconAndDetail(Icons.calendar_today, 'October 30'),
          const IconAndDetail(Icons.location_city, 'San Francisco'),
          Consumer<ApplicationState>(
            builder: (context, appState, _) => Authentication(
              email: appState.email,
              loginState: appState.loginState,
              startLoginFlow: appState.startLoginFlow,
              verifyEmail: appState.verifyEmail,
              signInWithEmailAndPassword: appState.signInWithEmailAndPassword,
              cancelRegistration: appState.cancelRegistration,
              registerAccount: appState.registerAccount,
              signOut: appState.signOut,
            ),
          ),
          const Divider(
            height: 8,
            thickness: 1,
            indent: 8,
            endIndent: 8,
            color: Colors.grey,
          ),
          const Header("What we'll be doing"),
          const Paragraph(
            'Join us for a day full of Firebase Workshops and Pizza!',
          ),
          // Modify from here
          Consumer<ApplicationState>(
            builder: (context, appState, _) => Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                if (appState.loginState == ApplicationLoginState.loggedIn) ...[
                  const Header('Discussion'),
                  GuestBook(
                    addMessage: (message) =>
                        appState.addMessageToGuestBook(message),
                  ),
                ],
              ],
            ),
          ),
          // To here.
        ],
      ),
    );
  }
}

Hai sostituito le due righe che hai aggiunto all'inizio di questo passaggio con l'implementazione completa. Si sta utilizzando di nuovo Consumer<ApplicationState> per rendere disponibile lo stato dell'applicazione alla parte dell'albero di cui si esegue il rendering. Ciò ti consente di reagire a qualcuno che inserisce un messaggio nell'interfaccia utente e di pubblicarlo nel database. Nella sezione successiva verificherai se i messaggi aggiunti vengono pubblicati nel database.

Prova l'invio di messaggi

  1. Assicurati di aver effettuato l'accesso all'app.
  2. Inserisci un messaggio come "Ehi!", quindi fai clic su INVIA .

Questa azione scrive il messaggio nel database di Cloud Firestore. Tuttavia, non vedrai ancora il messaggio nella tua app Flutter effettiva perché devi ancora implementare il recupero dei dati. Lo farai nel passaggio successivo.

Ma puoi vedere il messaggio appena aggiunto nella console Firebase.

Nella console Firebase, nella dashboard del database , dovresti vedere la raccolta del libro degli guestbook con il messaggio appena aggiunto. Se continui a inviare messaggi, la tua raccolta di guestbook conterrà molti documenti, come questo:

Console Firebase

713870af0b3b63c.png

7. Leggi i messaggi

È bello che gli ospiti possano scrivere messaggi nel database, ma non possono ancora vederli nell'app. Risolviamolo!

Sincronizza i messaggi

Per visualizzare i messaggi, dovrai aggiungere listener che si attivano quando i dati cambiano e quindi creare un elemento dell'interfaccia utente che mostri i nuovi messaggi. Aggiungerai codice allo stato dell'applicazione che ascolta i messaggi appena aggiunti dall'app.

Appena sopra il widget GuestBook la seguente classe di valore. Questa classe espone una vista strutturata dei dati che stai archiviando in Cloud Firestore.

lib/main.dart

class GuestBookMessage {
  GuestBookMessage({required this.name, required this.message});
  final String name;
  final String message;
}

Nella sezione di ApplicationState in cui definisci lo stato e i getter, aggiungi le seguenti nuove righe:

lib/main.dart

  ApplicationLoginState _loginState = ApplicationLoginState.loggedOut;
  ApplicationLoginState get loginState => _loginState;

  String? _email;
  String? get email => _email;

  // Add from here
  StreamSubscription<QuerySnapshot>? _guestBookSubscription;
  List<GuestBookMessage> _guestBookMessages = [];
  List<GuestBookMessage> get guestBookMessages => _guestBookMessages;
  // to here.

Infine, nella sezione di inizializzazione di ApplicationState , aggiungi quanto segue per iscriverti a una query sulla raccolta di documenti quando un utente effettua l'accesso e annulla l'iscrizione quando si disconnette.

lib/main.dart

  Future<void> init() async {
    await Firebase.initializeApp(
      options: DefaultFirebaseOptions.currentPlatform,
    );

    FirebaseAuth.instance.userChanges().listen((user) {
      if (user != null) {
        _loginState = ApplicationLoginState.loggedIn;
        // Add from here
        _guestBookSubscription = FirebaseFirestore.instance
            .collection('guestbook')
            .orderBy('timestamp', descending: true)
            .snapshots()
            .listen((snapshot) {
          _guestBookMessages = [];
          for (final document in snapshot.docs) {
            _guestBookMessages.add(
              GuestBookMessage(
                name: document.data()['name'] as String,
                message: document.data()['text'] as String,
              ),
            );
          }
          notifyListeners();
        });
        // to here.
      } else {
        _loginState = ApplicationLoginState.loggedOut;
        // Add from here
        _guestBookMessages = [];
        _guestBookSubscription?.cancel();
        // to here.
      }
      notifyListeners();
    });
  }

Questa sezione è importante, poiché qui è dove costruisci una query sulla raccolta del libro degli guestbook e gestisci l'iscrizione e l'annullamento dell'iscrizione a questa raccolta. Ascolti lo streaming, in cui ricostruisci una cache locale dei messaggi nella raccolta del libro degli guestbook e memorizzi anche un riferimento a questa sottoscrizione in modo da poterla annullare in seguito. C'è molto da fare qui e vale la pena spendere un po' di tempo in un debugger per ispezionare cosa succede quando si ottiene un modello mentale più chiaro.

Per ulteriori informazioni, consulta la documentazione di Cloud Firestore .

Nel widget GuestBook è necessario collegare questo stato di modifica all'interfaccia utente. Puoi modificare il widget aggiungendo un elenco di messaggi come parte della sua configurazione.

lib/main.dart

class GuestBook extends StatefulWidget {
  // Modify the following line
  const GuestBook({required this.addMessage, required this.messages});
  final FutureOr<void> Function(String message) addMessage;
  final List<GuestBookMessage> messages; // new

  @override
  _GuestBookState createState() => _GuestBookState();
}

Successivamente, esponiamo questa nuova configurazione in _GuestBookState modificando il metodo build come segue.

lib/main.dart

class _GuestBookState extends State<GuestBook> {
  final _formKey = GlobalKey<FormState>(debugLabel: '_GuestBookState');
  final _controller = TextEditingController();

  @override
  // Modify from here
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        // to here.
        Padding(
          padding: const EdgeInsets.all(8.0),
          child: Form(
            key: _formKey,
            child: Row(
              children: [
                Expanded(
                  child: TextFormField(
                    controller: _controller,
                    decoration: const InputDecoration(
                      hintText: 'Leave a message',
                    ),
                    validator: (value) {
                      if (value == null || value.isEmpty) {
                        return 'Enter your message to continue';
                      }
                      return null;
                    },
                  ),
                ),
                const SizedBox(width: 8),
                StyledButton(
                  onPressed: () async {
                    if (_formKey.currentState!.validate()) {
                      await widget.addMessage(_controller.text);
                      _controller.clear();
                    }
                  },
                  child: Row(
                    children: const [
                      Icon(Icons.send),
                      SizedBox(width: 4),
                      Text('SEND'),
                    ],
                  ),
                ),
              ],
            ),
          ),
        ),
        // Modify from here
        const SizedBox(height: 8),
        for (var message in widget.messages)
          Paragraph('${message.name}: ${message.message}'),
        const SizedBox(height: 8),
      ],
      // to here.
    );
  }
}

Racchiudi il contenuto precedente del metodo build con un widget Column , quindi alla fine dei figli della Column aggiungi una raccolta per generare un nuovo Paragraph per ogni messaggio nell'elenco dei messaggi.

Infine, ora è necessario aggiornare il corpo della HomePage per costruire correttamente GuestBook con il nuovo parametro dei messages .

lib/main.dart

Consumer<ApplicationState>(
  builder: (context, appState, _) => Column(
    crossAxisAlignment: CrossAxisAlignment.start,
    children: [
      if (appState.loginState == ApplicationLoginState.loggedIn) ...[
        const Header('Discussion'),
        GuestBook(
          addMessage: (message) =>
              appState.addMessageToGuestBook(message),
          messages: appState.guestBookMessages, // new
        ),
      ],
    ],
  ),
),

Testare la sincronizzazione dei messaggi

Cloud Firestore sincronizza automaticamente e istantaneamente i dati con i clienti iscritti al database.

  1. I messaggi che hai creato in precedenza nel database dovrebbero essere visualizzati nell'app. Sentiti libero di scrivere nuovi messaggi; dovrebbero apparire all'istante.
  2. Se apri l'area di lavoro in più finestre o schede, i messaggi verranno sincronizzati in tempo reale tra le schede.
  3. (Facoltativo) Puoi provare a eliminare, modificare o aggiungere manualmente nuovi messaggi direttamente nella sezione Database della console Firebase; eventuali modifiche dovrebbero apparire nell'interfaccia utente.

Congratulazioni! Stai leggendo i documenti di Cloud Firestore nella tua app!

Recensione dell'app

8. Imposta le regole di sicurezza di base

Inizialmente hai configurato Cloud Firestore per utilizzare la modalità di test, il che significa che il tuo database è aperto per letture e scritture. Tuttavia, dovresti usare la modalità test solo durante le prime fasi di sviluppo. Come best practice, dovresti impostare regole di sicurezza per il tuo database mentre sviluppi la tua app. La sicurezza dovrebbe essere parte integrante della struttura e del comportamento della tua app.

Le regole di sicurezza consentono di controllare l'accesso a documenti e raccolte nel database. La sintassi flessibile delle regole consente di creare regole che corrispondono a qualsiasi cosa, da tutte le scritture nell'intero database alle operazioni su un documento specifico.

Puoi scrivere regole di sicurezza per Cloud Firestore nella console Firebase:

  1. Nella sezione Sviluppo della console Firebase, fai clic su Database , quindi seleziona la scheda Regole (oppure fai clic qui per andare direttamente alla scheda Regole ).
  2. Dovresti vedere le seguenti regole di sicurezza predefinite, insieme a un avviso sulle regole che sono pubbliche.

7767a2d2e64e7275.png

Identifica le raccolte

Innanzitutto, identifica le raccolte in cui l'app scrive i dati.

In match /databases/{database}/documents , identifica la raccolta che vuoi proteggere:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /guestbook/{entry} {
     // You'll add rules here in the next step.
  }
}

Aggiungi regole di sicurezza

Poiché hai utilizzato l'UID di autenticazione come campo in ogni documento del libro degli ospiti, puoi ottenere l'UID di autenticazione e verificare che chiunque tenti di scrivere sul documento disponga di un UID di autenticazione corrispondente.

Aggiungi le regole di lettura e scrittura al tuo set di regole come mostrato di seguito:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /guestbook/{entry} {
      allow read: if request.auth.uid != null;
      allow write:
        if request.auth.uid == request.resource.data.userId;
    }
  }
}

Ora, per il libro degli ospiti, solo gli utenti che hanno eseguito l'accesso possono leggere i messaggi (qualsiasi messaggio!), ma solo l'autore di un messaggio può modificare un messaggio.

Aggiungi regole di convalida

Aggiungi la convalida dei dati per assicurarti che tutti i campi previsti siano presenti nel documento:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /guestbook/{entry} {
      allow read: if request.auth.uid != null;
      allow write:
      if request.auth.uid == request.resource.data.userId
          && "name" in request.resource.data
          && "text" in request.resource.data
          && "timestamp" in request.resource.data;
    }
  }
}

9. Passaggio bonus: esercitati su ciò che hai imparato

Registra lo stato RSVP di un partecipante

Al momento, la tua app consente alle persone di iniziare a chattare solo se sono interessate all'evento. Inoltre, l'unico modo per sapere se qualcuno sta arrivando è pubblicarlo nella chat. Organizziamoci e facciamo sapere alla gente quante persone stanno arrivando.

Stai per aggiungere un paio di nuove funzionalità allo stato dell'applicazione. Il primo è la possibilità per un utente che ha effettuato l'accesso di nominare se sta partecipando o meno. La seconda capacità è un contatore di quante persone stanno effettivamente partecipando.

In lib/main.dart , aggiungi quanto segue alla sezione delle funzioni di accesso per consentire al codice dell'interfaccia utente di interagire con questo stato:

lib/main.dart

int _attendees = 0;
int get attendees => _attendees;

Attending _attending = Attending.unknown;
StreamSubscription<DocumentSnapshot>? _attendingSubscription;
Attending get attending => _attending;
set attending(Attending attending) {
  final userDoc = FirebaseFirestore.instance
      .collection('attendees')
      .doc(FirebaseAuth.instance.currentUser!.uid);
  if (attending == Attending.yes) {
    userDoc.set(<String, dynamic>{'attending': true});
  } else {
    userDoc.set(<String, dynamic>{'attending': false});
  }
}

Aggiorna il metodo init di ApplicationState come segue:

lib/main.dart

  Future<void> init() async {
    await Firebase.initializeApp(
      options: DefaultFirebaseOptions.currentPlatform,
    );

    // Add from here
    FirebaseFirestore.instance
        .collection('attendees')
        .where('attending', isEqualTo: true)
        .snapshots()
        .listen((snapshot) {
      _attendees = snapshot.docs.length;
      notifyListeners();
    });
    // To here

    FirebaseAuth.instance.userChanges().listen((user) {
      if (user != null) {
        _loginState = ApplicationLoginState.loggedIn;
        _guestBookSubscription = FirebaseFirestore.instance
            .collection('guestbook')
            .orderBy('timestamp', descending: true)
            .snapshots()
            .listen((snapshot) {
          _guestBookMessages = [];
          for (final document in snapshot.docs) {
            _guestBookMessages.add(
              GuestBookMessage(
                name: document.data()['name'] as String,
                message: document.data()['text'] as String,
              ),
            );
          }
          notifyListeners();
        });
        // Add from here
        _attendingSubscription = FirebaseFirestore.instance
            .collection('attendees')
            .doc(user.uid)
            .snapshots()
            .listen((snapshot) {
          if (snapshot.data() != null) {
            if (snapshot.data()!['attending'] as bool) {
              _attending = Attending.yes;
            } else {
              _attending = Attending.no;
            }
          } else {
            _attending = Attending.unknown;
          }
          notifyListeners();
        });
        // to here
      } else {
        _loginState = ApplicationLoginState.loggedOut;
        _guestBookMessages = [];
        _guestBookSubscription?.cancel();
        _attendingSubscription?.cancel(); // new
      }
      notifyListeners();
    });
  }

Quanto sopra aggiunge una query sempre sottoscritta per scoprire il numero di partecipanti e una seconda query attiva solo mentre un utente è connesso per scoprire se l'utente sta partecipando. Quindi, aggiungi la seguente enumerazione dopo la dichiarazione GuestBookMessage :

lib/main.dart

enum Attending { yes, no, unknown }

Ora definirai un nuovo widget che agisce come i vecchi pulsanti di opzione. Inizia in uno stato indeterminato, senza né sì né no selezionati, ma una volta che l'utente ha selezionato se sta partecipando o meno, allora mostri quell'opzione evidenziata con un pulsante pieno e l'altra opzione che si allontana con un rendering piatto.

lib/main.dart

class YesNoSelection extends StatelessWidget {
  const YesNoSelection({required this.state, required this.onSelection});
  final Attending state;
  final void Function(Attending selection) onSelection;

  @override
  Widget build(BuildContext context) {
    switch (state) {
      case Attending.yes:
        return Padding(
          padding: const EdgeInsets.all(8.0),
          child: Row(
            children: [
              ElevatedButton(
                style: ElevatedButton.styleFrom(elevation: 0),
                onPressed: () => onSelection(Attending.yes),
                child: const Text('YES'),
              ),
              const SizedBox(width: 8),
              TextButton(
                onPressed: () => onSelection(Attending.no),
                child: const Text('NO'),
              ),
            ],
          ),
        );
      case Attending.no:
        return Padding(
          padding: const EdgeInsets.all(8.0),
          child: Row(
            children: [
              TextButton(
                onPressed: () => onSelection(Attending.yes),
                child: const Text('YES'),
              ),
              const SizedBox(width: 8),
              ElevatedButton(
                style: ElevatedButton.styleFrom(elevation: 0),
                onPressed: () => onSelection(Attending.no),
                child: const Text('NO'),
              ),
            ],
          ),
        );
      default:
        return Padding(
          padding: const EdgeInsets.all(8.0),
          child: Row(
            children: [
              StyledButton(
                onPressed: () => onSelection(Attending.yes),
                child: const Text('YES'),
              ),
              const SizedBox(width: 8),
              StyledButton(
                onPressed: () => onSelection(Attending.no),
                child: const Text('NO'),
              ),
            ],
          ),
        );
    }
  }
}

Successivamente, è necessario aggiornare il metodo di compilazione di HomePage per sfruttare YesNoSelection , consentendo a un utente connesso di nominare se sta partecipando. Verrà inoltre visualizzato il numero di partecipanti per questo evento.

lib/main.dart

Consumer<ApplicationState>(
  builder: (context, appState, _) => Column(
    crossAxisAlignment: CrossAxisAlignment.start,
    children: [
      // Add from here
      if (appState.attendees >= 2)
        Paragraph('${appState.attendees} people going')
      else if (appState.attendees == 1)
        const Paragraph('1 person going')
      else
        const Paragraph('No one going'),
      // To here.
      if (appState.loginState == ApplicationLoginState.loggedIn) ...[
        // Add from here
        YesNoSelection(
          state: appState.attending,
          onSelection: (attending) => appState.attending = attending,
        ),
        // To here.
        const Header('Discussion'),
        GuestBook(
          addMessage: (message) =>
              appState.addMessageToGuestBook(message),
          messages: appState.guestBookMessages,
        ),
      ],
    ],
  ),
),

Aggiungi regole

Poiché hai già impostato alcune regole, i nuovi dati che stai aggiungendo con i pulsanti verranno rifiutati. Dovrai aggiornare le regole per consentire l'aggiunta alla raccolta dei attendees .

Per la raccolta dei attendees , poiché hai utilizzato l'UID di autenticazione come nome del documento, puoi prenderlo e verificare che l' uid del mittente sia lo stesso del documento che stanno scrivendo. Consentirai a tutti di leggere l'elenco dei partecipanti (poiché non ci sono dati privati ​​lì), ma solo il creatore dovrebbe essere in grado di aggiornarlo.

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    // ... //
    match /attendees/{userId} {
      allow read: if true;
      allow write: if request.auth.uid == userId;
    }
  }
}

Aggiungi regole di convalida

Aggiungi la convalida dei dati per assicurarti che tutti i campi previsti siano presenti nel documento:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    // ... //
    match /attendees/{userId} {
      allow read: if true;
      allow write: if request.auth.uid == userId
          && "attending" in request.resource.data;

    }
  }
}

(Facoltativo) È ora possibile visualizzare i risultati del clic sui pulsanti. Vai alla dashboard di Cloud Firestore nella console Firebase.

Anteprima dell'app

10. Congratulazioni!

Hai utilizzato Firebase per creare un'applicazione Web interattiva in tempo reale!

Di cosa abbiamo parlato

  • Autenticazione Firebase
  • Cloud Firestore
  • Regole di sicurezza Firebase

Prossimi passi

  • Vuoi saperne di più sugli altri prodotti Firebase? Forse vuoi archiviare i file di immagine che gli utenti caricano? O inviare notifiche ai tuoi utenti? Consulta la documentazione di Firebase . Vuoi saperne di più sui plugin Flutter per Firebase? Dai un'occhiata a FlutterFire per ulteriori informazioni.
  • Vuoi saperne di più su Cloud Firestore? Forse vuoi conoscere le sottoraccolte e le transazioni? Vai al codelab web di Cloud Firestore per un codelab che approfondisce ulteriormente Cloud Firestore. Oppure dai un'occhiata a questa serie di YouTube per conoscere Cloud Firestore !

Scopri di più

Com'è andata?

Ci piacerebbe il tuo feedback! Si prega di compilare un modulo (molto) breve qui .