Conosci Firebase per Flutter

1. Prima di iniziare

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

Prerequisiti

Cosa imparerai

  • Come creare un'app per la chat RSVP e il libro degli ospiti di un evento su Android, iOS, Web e macOS con Flutter.
  • Come autenticare gli utenti con Firebase Authentication e sincronizzare i dati con Firestore.

La schermata iniziale dell'app su Android

La schermata iniziale dell'app su iOS

Di cosa avrai bisogno

Uno qualsiasi dei seguenti dispositivi:

  • Un dispositivo Android o iOS fisico collegato al computer e impostato in modalità sviluppatore.
  • Il simulatore iOS (richiede gli strumenti Xcode ).
  • L'emulatore Android (richiede la configurazione in Android Studio ).

È inoltre necessario quanto segue:

  • Un browser a tua scelta, come Google Chrome.
  • Un IDE o un editor di testo di tua scelta configurato con i plug-in Dart e Flutter, come Android Studio o Visual Studio Code .
  • L'ultima versione stable di Flutter o beta se ti piace vivere al limite.
  • Un Account Google per la creazione e la gestione del tuo progetto Firebase.
  • La CLI Firebase ha effettuato l'accesso al tuo Account Google.

2. Ottieni il codice di esempio

Scarica la versione iniziale del tuo progetto da GitHub:

  1. Dalla riga di comando, clona il repository GitHub nella directory flutter-codelabs :
git clone https://github.com/flutter/codelabs.git flutter-codelabs

La directory flutter-codelabs contiene il codice per una raccolta di codelabs. Il codice per questo codelab si trova nella directory flutter-codelabs/firebase-get-to-know-flutter . La directory contiene una serie di istantanee che mostrano come dovrebbe apparire il tuo progetto alla fine di ogni passaggio. Ad esempio, sei al secondo passaggio.

  1. Trova i file corrispondenti per il secondo passaggio:
cd flutter-codelabs/firebase-get-to-know-flutter/step_02

Se vuoi andare avanti o vedere come dovrebbe apparire qualcosa dopo un passaggio, cerca nella directory denominata dopo il passaggio a cui sei interessato.

Importa l'app iniziale

  • Apri o importa la directory 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 che necessitano di lavoro

Il codice in questa app è distribuito su più directory. Questa suddivisione delle funzionalità semplifica il lavoro perché raggruppa il codice per funzionalità.

  • Individuare i seguenti file:
    • lib/main.dart : questo file contiene il punto di ingresso principale e il widget dell'app.
    • lib/home_page.dart : questo file contiene il widget della home page.
    • lib/src/widgets.dart : questo file contiene una manciata di widget per aiutare a standardizzare lo stile dell'app. Compongono la schermata dell'app iniziale.
    • lib/src/authentication.dart : questo file contiene un'implementazione parziale dell'autenticazione con una serie di widget per creare un'esperienza utente di accesso per l'autenticazione basata su posta elettronica Firebase. Questi widget per il flusso di autenticazione non sono ancora utilizzati nell'app iniziale, ma li aggiungerai presto.

Aggiungi file aggiuntivi secondo necessità per creare il resto dell'app.

Esaminare il file lib/main.dart

Questa app sfrutta il pacchetto google_fonts per rendere Roboto il carattere predefinito in tutta l'app. Puoi esplorare fonts.google.com e utilizzare i caratteri che trovi lì in diverse parti dell'app.

Utilizzi i widget di supporto dal file lib/src/widgets.dart sotto forma di Header , Paragraph e IconAndDetail . Questi widget eliminano il codice duplicato per ridurre la confusione nel layout della pagina descritto in HomePage . Ciò consente anche un aspetto coerente.

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

La schermata iniziale dell'app su Android

La schermata iniziale dell'app su iOS

La schermata iniziale dell'app sul Web

La schermata iniziale dell'app su macOS

3. Crea e configura un progetto Firebase

La visualizzazione delle informazioni sull'evento è ottima per i tuoi ospiti, ma non è molto utile per nessuno. È necessario aggiungere alcune funzionalità dinamiche all'app. Per fare ciò, devi connettere 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, fai clic su Aggiungi progetto o Crea un progetto .
  3. Nel campo Nome progetto , inserisci Firebase-Flutter-Codelab e quindi fai clic su Continua .

4395e4e67c08043a.png

  1. Fai clic sulle opzioni di creazione del progetto. Se richiesto, accetta i termini di Firebase, ma salta la configurazione di Google Analytics perché non lo utilizzerai per questa app.

b7138cde5f2c7b61.png

Per ulteriori informazioni sui progetti Firebase, consulta Comprendere i progetti Firebase .

L'app utilizza i seguenti prodotti Firebase, disponibili per le app Web:

  • Autenticazione: consente agli utenti di accedere alla tua app.
  • Firestore: salva i dati strutturati sul cloud e riceve notifiche istantanee quando i dati cambiano.
  • Regole di sicurezza Firebase: protegge il tuo database.

Alcuni di questi prodotti richiedono una configurazione speciale oppure è necessario abilitarli nella console Firebase.

Abilita l'autenticazione dell'accesso tramite posta elettronica

  1. Nel riquadro Panoramica progetto della console Firebase, espandi il menu Crea .
  2. Fai clic su Autenticazione > Inizia > Metodo di accesso > E-mail/Password > Abilita > Salva .

58e3e3e23c2f16a4.png

Abilita Fire Store

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

Abilita Firestore:

  • Nel menu Crea , fai clic su Database Firestore > Crea database .

99e8429832d23fa3.png

  1. Selezionare Avvia in modalità test e quindi leggere l'esclusione di responsabilità sulle regole di sicurezza. La modalità test garantisce la possibilità di scrivere liberamente nel database durante lo sviluppo.

6be00e26c72ea032.png

  1. Fare clic su Avanti e quindi selezionare la posizione per il database. È possibile utilizzare l'impostazione predefinita. Non potrai modificare la posizione in un secondo momento.

278656eefcfb0216.png

  1. Fare clic su Abilita .

4. Configura Firebase

Per utilizzare Firebase con Flutter, devi completare le seguenti attività per configurare il progetto Flutter per utilizzare correttamente le librerie FlutterFire :

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

Nella directory di livello superiore dell'app Flutter sono presenti le sottodirectory android , ios , macos e web , che contengono rispettivamente i file di configurazione specifici della piattaforma per iOS e Android.

Configura le dipendenze

Devi aggiungere le librerie FlutterFire per i due prodotti Firebase che utilizzi in questa app: Authentication e Firestore.

  • Dalla riga di comando, aggiungi le seguenti dipendenze:
$ flutter pub add firebase_core

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

$ flutter pub add firebase_auth

Il pacchetto firebase_auth consente l'integrazione con l'autenticazione.

$ flutter pub add cloud_firestore

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

$ flutter pub add provider

Il pacchetto firebase_ui_auth fornisce una serie di widget e utilità per aumentare la velocità degli sviluppatori con i flussi di autenticazione.

$ flutter pub add firebase_ui_auth

Hai aggiunto i pacchetti richiesti, ma devi anche configurare i progetti iOS, Android, macOS e Web runner per utilizzare Firebase in modo appropriato. Utilizzi anche il pacchetto provider che consente la separazione della logica aziendale dalla logica di visualizzazione.

Installa la CLI FlutterFire

La CLI FlutterFire dipende dalla CLI Firebase sottostante.

  1. Se non lo hai già fatto, installa la CLI Firebase sul tuo computer.
  2. Installa la CLI FlutterFire:
$ dart pub global activate flutterfire_cli

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

Configura le tue app

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

Nella root della tua app, esegui il comando configure :

$ flutterfire configure

Il comando di configurazione guida l'utente attraverso i seguenti processi:

  1. Seleziona un progetto Firebase basato sul file .firebaserc o dalla console Firebase.
  2. Determina le piattaforme per la configurazione, come Android, iOS, macOS e web.
  3. Identificare le app Firebase da cui estrarre la configurazione. Per impostazione predefinita, la CLI tenta 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 app completamente sandbox. Poiché questa app si integra con la rete per comunicare con i server Firebase, devi configurare la tua app 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 ulteriori informazioni, consulta Supporto desktop per Flutter .

5. Aggiungi la funzionalità RSVP

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

Il progetto recuperato in precedenza includeva una serie di widget che implementa l'interfaccia utente per la maggior parte del flusso di autenticazione. Implementi la logica aziendale per integrare l'autenticazione con l'app.

Aggiungi la logica aziendale con il pacchetto Provider

Utilizza il pacchetto provider per rendere disponibile un oggetto stato dell'app centralizzato nell'albero dei widget Flutter dell'app:

  1. Crea un nuovo file denominato app_state.dart con il seguente contenuto:

lib/app_state.dart

import 'package:firebase_auth/firebase_auth.dart'
    hide EmailAuthProvider, PhoneAuthProvider;
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_ui_auth/firebase_ui_auth.dart';
import 'package:flutter/material.dart';

import 'firebase_options.dart';

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

  bool _loggedIn = false;
  bool get loggedIn => _loggedIn;

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

    FirebaseUIAuth.configureProviders([
      EmailAuthProvider(),
    ]);

    FirebaseAuth.instance.userChanges().listen((user) {
      if (user != null) {
        _loggedIn = true;
      } else {
        _loggedIn = false;
      }
      notifyListeners();
    });
  }
}

Le istruzioni import introducono Firebase Core e Auth, inseriscono il pacchetto provider che rende disponibile l'oggetto stato dell'app nell'albero dei widget e includono i widget di autenticazione dal pacchetto firebase_ui_auth .

Questo oggetto stato dell'applicazione ApplicationState ha una responsabilità principale per questo passaggio, ovvero avvisare l'albero del widget che si è verificato un aggiornamento a uno stato autenticato.

Utilizzi un provider solo per comunicare lo stato di accesso di un utente all'app. Per consentire a un utente di accedere, utilizzi le interfacce utente fornite dal pacchetto firebase_ui_auth , che è un ottimo modo per avviare rapidamente le schermate di accesso nelle tue app.

Integrare il flusso di autenticazione

  1. Modifica le importazioni nella parte superiore del file lib/main.dart :

lib/main.dart

import 'package:firebase_ui_auth/firebase_ui_auth.dart'; // new
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';               // new
import 'package:google_fonts/google_fonts.dart';
import 'package:provider/provider.dart';                 // new

import 'app_state.dart';                                 // new
import 'home_page.dart';
  1. Connetti lo stato dell'app con l'inizializzazione dell'app e quindi aggiungi il flusso di autenticazione a HomePage :

lib/main.dart

void main() {
  // Modify from here...
  WidgetsFlutterBinding.ensureInitialized();

  runApp(ChangeNotifierProvider(
    create: (context) => ApplicationState(),
    builder: ((context, child) => const App()),
  ));
  // ...to here.
}

La modifica alla funzione main() rende il pacchetto provider responsabile della creazione di un'istanza dell'oggetto stato dell'app con il widget ChangeNotifierProvider . Utilizzi questa classe provider specifica perché l'oggetto stato dell'app estende la classe ChangeNotifier , che consente al pacchetto provider di sapere quando visualizzare nuovamente i widget dipendenti.

  1. Aggiorna la tua app per gestire la navigazione nelle diverse schermate fornite da FirebaseUI, creando una configurazione GoRouter :

lib/main.dart

// Add GoRouter configuration outside the App class
final _router = GoRouter(
  routes: [
    GoRoute(
      path: '/',
      builder: (context, state) => const HomePage(),
      routes: [
        GoRoute(
          path: 'sign-in',
          builder: (context, state) {
            return SignInScreen(
              actions: [
                ForgotPasswordAction(((context, email) {
                  final uri = Uri(
                    path: '/sign-in/forgot-password',
                    queryParameters: <String, String?>{
                      'email': email,
                    },
                  );
                  context.push(uri.toString());
                })),
                AuthStateChangeAction(((context, state) {
                  final user = switch (state) {
                    SignedIn state => state.user,
                    UserCreated state => state.credential.user,
                    _ => null
                  };
                  if (user == null) {
                    return;
                  }
                  if (state is UserCreated) {
                    user.updateDisplayName(user.email!.split('@')[0]);
                  }
                  if (!user.emailVerified) {
                    user.sendEmailVerification();
                    const snackBar = SnackBar(
                        content: Text(
                            'Please check your email to verify your email address'));
                    ScaffoldMessenger.of(context).showSnackBar(snackBar);
                  }
                  context.pushReplacement('/');
                })),
              ],
            );
          },
          routes: [
            GoRoute(
              path: 'forgot-password',
              builder: (context, state) {
                final arguments = state.uri.queryParameters;
                return ForgotPasswordScreen(
                  email: arguments['email'],
                  headerMaxExtent: 200,
                );
              },
            ),
          ],
        ),
        GoRoute(
          path: 'profile',
          builder: (context, state) {
            return ProfileScreen(
              providers: const [],
              actions: [
                SignedOutAction((context) {
                  context.pushReplacement('/');
                }),
              ],
            );
          },
        ),
      ],
    ),
  ],
);
// end of GoRouter configuration

// Change MaterialApp to MaterialApp.router and add the routerConfig
class App extends StatelessWidget {
  const App({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      title: 'Firebase Meetup',
      theme: ThemeData(
        buttonTheme: Theme.of(context).buttonTheme.copyWith(
              highlightColor: Colors.deepPurple,
            ),
        primarySwatch: Colors.deepPurple,
        textTheme: GoogleFonts.robotoTextTheme(
          Theme.of(context).textTheme,
        ),
        visualDensity: VisualDensity.adaptivePlatformDensity,
        useMaterial3: true,
      ),
      routerConfig: _router, // new
    );
  }
}

Ad ogni schermata è associato un diverso tipo di azione in base al nuovo stato del flusso di autenticazione. Dopo la maggior parte dei cambiamenti di stato nell'autenticazione, puoi reindirizzare a una schermata preferita, che si tratti della schermata iniziale o di una schermata diversa, come il profilo.

  1. Nel metodo build della classe HomePage , integra lo stato dell'app con il widget AuthFunc :

lib/home_page.dart

import 'package:firebase_auth/firebase_auth.dart' // new
    hide EmailAuthProvider, PhoneAuthProvider;    // new
import 'package:flutter/material.dart';           // new
import 'package:provider/provider.dart';          // new

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

class HomePage extends StatelessWidget {
  const HomePage({super.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, _) => AuthFunc(
                loggedIn: appState.loggedIn,
                signOut: () {
                  FirebaseAuth.instance.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 AuthFunc e lo avvolgi in un widget Consumer . Il widget Consumer è il modo consueto con cui è possibile utilizzare il pacchetto provider per ricostruire parte dell'albero quando cambia lo stato dell'app. Il widget AuthFunc sono i widget supplementari che testi.

Testare il flusso di autenticazione

cdf2d25e436bd48d.png

  1. Nell'app, tocca il pulsante RSVP per avviare SignInScreen .

2a2cd6d69d172369.png

  1. Inserire un indirizzo email. Se sei già registrato, il sistema ti chiederà di inserire una password. Altrimenti il ​​sistema ti chiederà di completare il modulo di registrazione.

e5e65065dba36b54.png

  1. Inserisci una password che contenga meno di sei caratteri per controllare il flusso di gestione degli errori. Se sei registrato vedrai invece la password per.
  2. Immettere password errate per controllare il flusso di gestione degli errori.
  3. Inserisci la password corretta. Viene visualizzata l'esperienza di accesso, che offre all'utente la possibilità di disconnettersi.

4ed811a25b0cf816.png

6. Scrivi messaggi su Firestore

È bello sapere che gli utenti stanno arrivando, ma devi dare agli ospiti qualcos'altro da fare nell'app. E se potessero lasciare messaggi nel libro degli ospiti? 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, utilizzi Firestore .

Modello di dati

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

7c20dc8424bb1d84.png

Aggiungi messaggi a Firestore

In questa sezione si aggiunge la funzionalità per consentire agli utenti di scrivere messaggi nel database. Per prima cosa aggiungi un campo modulo e un pulsante di invio, quindi aggiungi il codice che collega questi elementi al database.

  1. Crea un nuovo file denominato guest_book.dart , aggiungi un widget stateful GuestBook per costruire gli elementi dell'interfaccia utente di un campo messaggio e un pulsante di invio:

lib/guest_book.dart

import 'dart:async';

import 'package:flutter/material.dart';

import 'src/widgets.dart';

class GuestBook extends StatefulWidget {
  const GuestBook({required this.addMessage, super.key});

  final FutureOr<void> Function(String message) addMessage;

  @override
  State<GuestBook> 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, crei un'istanza di un modulo in modo da poter verificare che il messaggio contenga effettivamente contenuto e mostrare all'utente un messaggio di errore se non ce n'è. Per convalidare un modulo, accedi allo stato del modulo dietro il modulo con GlobalKey . Per ulteriori informazioni sulle chiavi e su come utilizzarle, vedere Quando utilizzare le chiavi .

Nota anche il modo in cui sono disposti i widget, hai una Row con un TextFormField e uno StyledButton , che contiene una Row . Tieni inoltre presente che TextFormField è racchiuso in un widget Expanded , che forza TextFormField a riempire qualsiasi spazio aggiuntivo nella riga. Per comprendere meglio il motivo per cui ciò è necessario, vedere Informazioni sui vincoli .

Ora che hai un widget che consente all'utente di inserire del testo da aggiungere al libro degli ospiti, devi visualizzarlo sullo schermo.

  1. Modifica il corpo di HomePage per aggiungere le seguenti due righe alla fine 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 qualcosa di utile. Aggiorna questo codice a breve per renderlo funzionale.

Anteprima dell'app

La schermata iniziale dell'app su Android con integrazione chat

La schermata iniziale dell'app su iOS con integrazione della chat

La schermata iniziale dell'app sul Web con integrazione chat

La schermata iniziale dell'app su macOS con integrazione della chat

Quando un utente fa clic su INVIA , viene attivato il seguente snippet di codice. Aggiunge il contenuto del campo di input del messaggio alla raccolta del guestbook del database. Nello specifico, il metodo addMessageToGuestBook aggiunge il contenuto del messaggio a un nuovo documento con un ID generato automaticamente nella raccolta guestbook .

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

  • Nel file lib/app_state.dart aggiungere il metodo addMessageToGuestBook . Connetterai questa funzionalità all'interfaccia utente nel passaggio successivo.

lib/app_state.dart

import 'package:cloud_firestore/cloud_firestore.dart'; // new
import 'package:firebase_auth/firebase_auth.dart'
    hide EmailAuthProvider, PhoneAuthProvider;
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_ui_auth/firebase_ui_auth.dart';
import 'package:flutter/material.dart';

import 'firebase_options.dart';

class ApplicationState extends ChangeNotifier {

  // Current content of ApplicationState elided ...

  // Add from here...
  Future<DocumentReference> addMessageToGuestBook(String message) {
    if (!_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.
}

Connetti l'interfaccia utente e il database

Hai un'interfaccia utente in cui l'utente può inserire il testo che desidera aggiungere al libro degli ospiti e hai il codice per aggiungere la voce a Firestore. Ora tutto ciò che devi fare è collegare i due.

  • Nel file lib/home_page.dart , apportare la seguente modifica al widget HomePage :

lib/home_page.dart

import 'package:firebase_auth/firebase_auth.dart'
    hide EmailAuthProvider, PhoneAuthProvider;
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

import 'app_state.dart';
import 'guest_book.dart';                         // new
import 'src/authentication.dart';
import 'src/widgets.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, _) => AuthFunc(
                loggedIn: appState.loggedIn,
                signOut: () {
                  FirebaseAuth.instance.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.loggedIn) ...[
                  const Header('Discussion'),
                  GuestBook(
                    addMessage: (message) =>
                        appState.addMessageToGuestBook(message),
                  ),
                ],
              ],
            ),
          ),
          // ...to here.
        ],
      ),
    );
  }
}

Hai sostituito le due righe aggiunte all'inizio di questo passaggio con l'implementazione completa. Utilizza nuovamente Consumer<ApplicationState> per rendere disponibile lo stato dell'app per la parte dell'albero di cui esegui il rendering. Ciò ti consente di reagire a qualcuno che inserisce un messaggio nell'interfaccia utente e di pubblicarlo nel database. Nella sezione successiva si verifica se i messaggi aggiunti vengono pubblicati nel database.

Prova a inviare messaggi

  1. Se necessario, accedi all'app.
  2. Inserisci un messaggio, ad esempio Hey there! , quindi fare clic su INVIA .

Questa azione scrive il messaggio nel database Firestore. Tuttavia, non vedi il messaggio nella tua vera app Flutter perché devi ancora implementare il recupero dei dati, cosa che farai nel passaggio successivo. Tuttavia, nella dashboard del database della console Firebase, puoi vedere il messaggio aggiunto nella raccolta guestbook . Se invii più messaggi, aggiungi più documenti alla raccolta del tuo guestbook . Ad esempio, vedere il seguente frammento di codice:

713870af0b3b63c.png

7. Leggi i messaggi

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

Sincronizza i messaggi

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

  1. Crea un nuovo file guest_book_message.dart , aggiungi la seguente classe per esporre una vista strutturata dei dati archiviati in Firestore.

lib/guest_book_message.dart

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

  final String name;
  final String message;
}
  1. Nel file lib/app_state.dart , aggiungi le seguenti importazioni:

lib/app_state.dart

import 'dart:async';                                     // new

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart'
    hide EmailAuthProvider, PhoneAuthProvider;
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_ui_auth/firebase_ui_auth.dart';
import 'package:flutter/material.dart';

import 'firebase_options.dart';
import 'guest_book_message.dart';                        // new
  1. Nella sezione di ApplicationState in cui definisci stato e getter, aggiungi le seguenti righe:

lib/app_state.dart

  bool _loggedIn = false;
  bool get loggedIn => _loggedIn;

  // Add from here...
  StreamSubscription<QuerySnapshot>? _guestBookSubscription;
  List<GuestBookMessage> _guestBookMessages = [];
  List<GuestBookMessage> get guestBookMessages => _guestBookMessages;
  // ...to here.
  1. Nella sezione di inizializzazione di ApplicationState , aggiungi le seguenti righe per sottoscrivere una query sulla raccolta di documenti quando un utente accede e annullare l'iscrizione quando si disconnette:

lib/app_state.dart

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

    FirebaseUIAuth.configureProviders([
      EmailAuthProvider(),
    ]);
    
    FirebaseAuth.instance.userChanges().listen((user) {
      if (user != null) {
        _loggedIn = true;
        _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();
        });
      } else {
        _loggedIn = false;
        _guestBookMessages = [];
        _guestBookSubscription?.cancel();
      }
      notifyListeners();
    });
  }

Questa sezione è importante perché è dove costruisci una query sulla raccolta guestbook e gestisci la sottoscrizione e l'annullamento della sottoscrizione a questa raccolta. Ascolti lo streaming, dove ricostruisci una cache locale dei messaggi nella raccolta guestbook e memorizzi anche un riferimento a questa iscrizione in modo da poterti annullare l'iscrizione in seguito. C'è molto da fare qui, quindi dovresti esplorarlo in un debugger per ispezionare cosa succede per ottenere un modello mentale più chiaro. Per ulteriori informazioni, consulta Ottenere aggiornamenti in tempo reale con Firestore .

  1. Nel file lib/guest_book.dart , aggiungi la seguente importazione:
import 'guest_book_message.dart';
  1. Nel widget GuestBook , aggiungi un elenco di messaggi come parte della configurazione per collegare questo stato mutevole all'interfaccia utente:

lib/guest_book.dart

class GuestBook extends StatefulWidget {
  // Modify the following line:
  const GuestBook({
    super.key, 
    required this.addMessage, 
    required this.messages,
  });

  final FutureOr<void> Function(String message) addMessage;
  final List<GuestBookMessage> messages; // new

  @override
  _GuestBookState createState() => _GuestBookState();
}
  1. In _GuestBookState , modifica il metodo build come segue per esporre questa configurazione:

lib/guest_book.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.
    );
  }
}

Avvolgi il contenuto precedente del metodo build() con un widget Column e quindi aggiungi una raccolta alla fine dei figli di Column per generare un nuovo Paragraph per ogni messaggio nell'elenco dei messaggi.

  1. Aggiorna il corpo di HomePage per costruire correttamente GuestBook con il nuovo parametro messages :

lib/home_page.dart

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

Testare la sincronizzazione dei messaggi

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

Testare la sincronizzazione dei messaggi:

  1. Nell'app, trova i messaggi che hai creato in precedenza nel database.
  2. Scrivi nuovi messaggi. Appaiono istantaneamente.
  3. Apri il tuo spazio di lavoro in più finestre o schede. I messaggi si sincronizzano in tempo reale attraverso le finestre e le schede.
  4. Facoltativo: nel menu Database della console Firebase, elimina, modifica o aggiungi manualmente nuovi messaggi. Tutte le modifiche vengono visualizzate nell'interfaccia utente.

Congratulazioni! Leggi i documenti Firestore nella tua app!

Anteprima dell'app

La schermata iniziale dell'app su Android con integrazione chat

La schermata iniziale dell'app su iOS con integrazione della chat

La schermata iniziale dell'app sul Web con integrazione chat

La schermata iniziale dell'app su macOS con integrazione della chat

8. Imposta le regole di sicurezza di base

Inizialmente hai configurato Firestore per utilizzare la modalità test, il che significa che il tuo database è aperto per letture e scritture. Tuttavia, dovresti utilizzare 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 è parte integrante della struttura e del comportamento della tua app.

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

Imposta le regole di sicurezza di base:

  1. Nel menu Sviluppo della console Firebase, fai clic su Database > Regole . Dovresti vedere le seguenti regole di sicurezza predefinite e un avviso relativo al fatto che le regole sono pubbliche:

7767a2d2e64e7275.png

  1. Identificare le raccolte in cui l'app scrive i dati:

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

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

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

  1. Aggiungi le regole di lettura e scrittura al tuo set di regole:
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 solo gli utenti che hanno effettuato l'accesso possono leggere i messaggi nel libro degli ospiti, ma solo l'autore del messaggio può modificarlo.

  1. Aggiungi la convalida dei dati per garantire 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: esercita ciò che hai imparato

Registra lo stato RSVP di un partecipante

Al momento, la tua app consente alle persone di chattare solo quando sono interessate all'evento. Inoltre, l'unico modo per sapere se qualcuno sta arrivando è quando lo dice nella chat.

In questo passaggio ti organizzi e fai sapere alle persone quante persone verranno. Aggiungi un paio di funzionalità allo stato dell'app. Il primo è la possibilità per un utente registrato di nominare se parteciperà. Il secondo è un contatore di quante persone partecipano.

  1. Nel file lib/app_state.dart , aggiungi le seguenti righe alla sezione accessori di ApplicationState in modo che il codice dell'interfaccia utente possa interagire con questo stato:

lib/app_state.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});
  }
}
  1. Aggiorna il metodo init() di ApplicationState come segue:

lib/app_state.dart

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

    FirebaseUIAuth.configureProviders([
      EmailAuthProvider(),
    ]);

    // 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) {
        _loggedIn = true;
        _emailVerified = user.emailVerified;
        _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 {
        _loggedIn = false;
        _emailVerified = false;
        _guestBookMessages = [];
        _guestBookSubscription?.cancel();
        _attendingSubscription?.cancel(); // new
      }
      notifyListeners();
    });
  }

Questo codice aggiunge una query sempre sottoscritta per determinare il numero di partecipanti e una seconda query attiva solo mentre un utente è connesso per determinare se l'utente partecipa.

  1. Aggiungi la seguente enumerazione nella parte superiore del file lib/app_state.dart .

lib/app_state.dart

enum Attending { yes, no, unknown }
  1. Crea un nuovo file yes_no_selection.dart , definisci un nuovo widget che agisce come pulsanti di opzione:

lib/yes_no_selection.dart

import 'package:flutter/material.dart';

import 'app_state.dart';
import 'src/widgets.dart';

class YesNoSelection extends StatelessWidget {
  const YesNoSelection(
      {super.key, 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: [
              FilledButton(
                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),
              FilledButton(
                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'),
              ),
            ],
          ),
        );
    }
  }
}

Inizia in uno stato indeterminato senza né No selezionati. Una volta che l'utente ha selezionato se partecipare, mostri l'opzione evidenziata con un pulsante pieno e l'altra opzione si allontana con un rendering piatto.

  1. Aggiorna il metodo build() di HomePage per sfruttare YesNoSelection , consentire a un utente che ha effettuato l'accesso di nominare se parteciperà e visualizzare il numero di partecipanti all'evento:

lib/home_page.dart

Consumer<ApplicationState>(
  builder: (context, appState, _) => Column(
    crossAxisAlignment: CrossAxisAlignment.start,
    children: [
      // Add from here...
      switch (appState.attendees) {
        1 => const Paragraph('1 person going'),
        >= 2 => Paragraph('${appState.attendees} people going'),
        _ => const Paragraph('No one going'),
      },
      // ...to here.
      if (appState.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

Hai già impostato alcune regole, quindi i dati che aggiungi con i pulsanti verranno rifiutati. È necessario aggiornare le regole per consentire aggiunte alla raccolta attendees .

  1. Nella raccolta attendees , prendi l'UID di autenticazione che hai utilizzato come nome del documento e verifica che l' uid del mittente sia lo stesso del documento che sta scrivendo:
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    // ... //
    match /attendees/{userId} {
      allow read: if true;
      allow write: if request.auth.uid == userId;
    }
  }
}

Ciò consente a tutti di leggere l'elenco dei partecipanti perché non ci sono dati privati, ma solo il creatore può aggiornarlo.

  1. Aggiungi la convalida dei dati per garantire 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;

    }
  }
}
  1. Facoltativo: nell'app, fai clic sui pulsanti per visualizzare i risultati nel dashboard Firestore nella console Firebase.

Anteprima dell'app

La schermata iniziale dell'app su Android

La schermata iniziale dell'app su iOS

La schermata iniziale dell'app sul Web

La schermata iniziale dell'app su macOS

10. Congratulazioni!

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

Saperne di più