Conozca Firebase para Flutter

1. Antes de comenzar

En este laboratorio de programación, usted aprenderá algunos de los conceptos básicos de Firebase para crear aplicaciones móviles alboroto para Android y el IOS.

requisitos previos

Este codelab supone que está familiarizado con aleteo, y ha instalado el SDK de aleteo , y un editor .

lo que vas a crear

En este codelab, creará una aplicación de chat de libro de visitas y RSVP para eventos en Android, iOS, la Web y macOS usando Flutter. Autenticará a los usuarios con Firebase Authentication y sincronizará los datos con Cloud Firestore.

Lo que necesitarás

Puede ejecutar este codelab usando cualquiera de los siguientes dispositivos:

Además de lo anterior, también necesitarás:

  • Un navegador de su elección, como Chrome.
  • Un IDE o editor de texto de su elección, como Estudio Android o Código VS configurado con los plugins del dardo y el aleteo.
  • La última stable versión de trémolo (o beta si disfrutan de vivir en el borde).
  • Una cuenta de Google, como una cuenta de Gmail, para crear y administrar su proyecto de Firebase.
  • La firebase herramienta de línea de comandos , acceda a su cuenta de Gmail.
  • El código de muestra del codelab. Consulte el siguiente paso para saber cómo obtener el código.

2. Obtenga el código de muestra

Comencemos descargando la versión inicial de nuestro proyecto desde GitHub.

Clonar el repositorio GitHub desde la línea de comandos:

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

Alternativamente, si usted tiene la CLI de GitHub herramienta instalada:

gh repo clone flutter/codelabs flutter-codelabs

El código de ejemplo debe ser clonado en el flutter-codelabs directorio, que contiene el código para una colección de codelabs. El código para esta codelab está en flutter-codelabs/firebase-get-to-know-flutter .

La estructura de directorios bajo el flutter-codelabs/firebase-get-to-know-flutter es una serie de instantáneas de donde debe estar al final de cada paso llamado. Este es el Paso 2, por lo que localizar los archivos coincidentes es tan fácil como:

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

Si desea saltar hacia adelante o ver cómo debería verse algo después de un paso, busque en el directorio que lleva el nombre del paso que le interesa.

Importar la aplicación de inicio

Abrir o importar el flutter-codelabs/firebase-get-to-know-flutter/step_02 directorio en su IDE preferido. Este directorio contiene el código de inicio para el laboratorio de código, que consiste en una aplicación de reunión de Flutter que aún no funciona.

Ubique los archivos para trabajar

El código de esta aplicación se distribuye en varios directorios. Esta división de funcionalidad está diseñada para facilitar el trabajo, agrupando el código por funcionalidad.

Localice los siguientes archivos en el proyecto:

  • lib/main.dart : Este archivo contiene el punto de entrada principal y el widget de la aplicación.
  • lib/src/widgets.dart : Este archivo contiene un puñado de widgets para ayudar a estandarizar el estilo de la aplicación. Estos se utilizan para componer la pantalla de la aplicación de inicio.
  • lib/src/authentication.dart : Este archivo contiene una implementación parcial de FirebaseUI de autenticación con un conjunto de widgets para crear una experiencia de usuario de inicio de sesión para la autenticación basada Firebase correo electrónico. Estos widgets para el flujo de autenticación aún no se usan en la aplicación de inicio, pero los conectará pronto.

Agregará archivos adicionales según sea necesario para construir el resto de la aplicación.

La revisión de la lib/main.dart archivo

Esta aplicación aprovecha la google_fonts paquete que nos permita realizar Roboto la fuente predeterminada a lo largo de toda la aplicación. Un ejercicio para el lector motivado es explorar fonts.google.com y utilizar las fuentes que descubren que hay en diferentes partes de la aplicación.

Que están utilizando los widgets de ayudante de lib/src/widgets.dart en forma de Header , Paragraph y IconAndDetail . Estos widgets reducir el desorden en el diseño de la página se describe en la HomePage mediante la eliminación de código duplicado. Esto tiene el beneficio adicional de permitir una apariencia uniforme.

Así es como se ve su aplicación en Android, iOS, la Web y macOS:

Vista previa de la aplicación

3. Crea y configura un proyecto de Firebase

Mostrar la información del evento es excelente para sus invitados, pero solo mostrar los eventos no es muy útil para nadie. Agreguemos algunas funciones dinámicas a esta aplicación. Para esto, deberá conectar Firebase a su aplicación. Para comenzar con Firebase, deberá crear y configurar un proyecto de Firebase.

Crear un proyecto de Firebase

  1. Iniciar sesión en Firebase .
  2. En la consola Firebase, haga clic en Agregar proyecto (o crear un proyecto), y el nombre de su proyecto Firebase Firebase-flutter-experimento de código.

4395e4e67c08043a.png

  1. Haga clic en las opciones de creación de proyectos. Acepte los términos de Firebase si se le solicita. Omita la configuración de Google Analytics, porque no usará Analytics para esta aplicación.

b7138cde5f2c7b61.png

Para obtener más información sobre los proyectos de base de fuego, consulte Comprender proyectos de base de fuego .

La aplicación que estás creando usa varios productos de Firebase que están disponibles para aplicaciones web:

  • Firebase autenticación para permitir a los usuarios que inician sesión en la aplicación.
  • Nube Firestore para guardar datos estructurados en la nube y obtener una notificación instantánea cuando cambian los datos.
  • Firebase Reglas de seguridad para proteger su base de datos.

Algunos de estos productos necesitan una configuración especial o deben habilitarse mediante la consola Firebase.

Habilitar el correo electrónico de inicio de sesión para la autenticación Firebase

Para permitir que los usuarios accedan a la aplicación web, va a utilizar el inicio de sesión de correo electrónico / contraseña para este método de laboratorio de programación:

  1. En la consola Firebase, expanda el menú Generar en el panel izquierdo.
  2. Haga clic en Autenticación y, a continuación, haga clic en el botón para empezar, a continuación, en la pestaña de inicio de sesión en el método (o haga clic aquí para ir directamente a la pestaña Inicio de sesión método).
  3. Haga clic en Correo electrónico / contraseña en la lista de inicio de sesión de proveedores, ajuste el interruptor en la posición de encendido Habilitar y, a continuación, haga clic en Guardar. 58e3e3e23c2f16a4.png

Habilitar Cloud Firestore

La aplicación web utiliza la nube Firestore para guardar los mensajes de chat y recibir nuevos mensajes de chat.

Habilitar Cloud Firestore:

  1. En la sección de construcción de la consola Firebase, haga clic en la nube Firestore.
  2. Haga clic en Crear base de datos. 99e8429832d23fa3.png
  1. Seleccione la opción de inicio en modo de prueba. Lea el descargo de responsabilidad sobre las reglas de seguridad. El modo de prueba garantiza que pueda escribir libremente en la base de datos durante el desarrollo. Haga clic en Siguiente. 6be00e26c72ea032.png
  1. Seleccione la ubicación de su base de datos (puede usar la ubicación predeterminada). Tenga en cuenta que esta ubicación no se puede cambiar más tarde. 278656eefcfb0216.png
  2. Haga clic en Activar.

4. Configuración de base de fuego

Para usar Firebase con Flutter, debe seguir un proceso para configurar el proyecto Flutter para utilizar las bibliotecas FlutterFire correctamente:

  • Agregue las dependencias de FlutterFire a su proyecto
  • Registre la plataforma deseada en el proyecto Firebase
  • Descargue el archivo de configuración específico de la plataforma y agréguelo al código.

En el directorio de nivel superior de la aplicación del alboroto, hay subdirectorios llamados android , ios , macos y web . Estos directorios contienen los archivos de configuración específicos de la plataforma para iOS y Android, respectivamente.

Configurar dependencias

Debe agregar las bibliotecas de FlutterFire para los dos productos de Firebase que está utilizando en esta aplicación: Firebase Auth y Cloud Firestore. Ejecute los siguientes tres comandos para agregar las dependencias.

$ 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!

El firebase_core es el código común requerido para todos los plugins Firebase alboroto.

$ 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!

El firebase_auth permite la integración con capacidad de autenticación de 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!

El cloud_firestore permite el acceso al almacenamiento de datos de la nube 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!

Si bien ha agregado los paquetes necesarios, también debe configurar los proyectos iOS, Android, macOS y Web runner para utilizar Firebase de manera adecuada. También se utiliza el provider paquete que va a permitir la separación de la lógica de negocio de la lógica de visualización.

Instalación flutterfire

  1. La CLI de FlutterFire depende de la CLI de Firebase subyacente. Si no lo ha hecho ya, instalar o actualizar a la última versión de la Firebase CLI .
  2. A continuación, instalar el FlutterFire CLI ejecutando el siguiente comando:
    $ dart pub global activate flutterfire_cli
    

Después de la instalación, el flutterfire comando estará disponible a nivel mundial.

Configurando tus aplicaciones

La CLI extrae información de su proyecto de Firebase y aplicaciones de proyectos seleccionados para generar toda la configuración para una plataforma específica.

En la raíz de su aplicación, ejecute el comando de configuración:

$ flutterfire configure

El comando de configuración lo guiará a través de una serie de procesos:

  1. Seleccionar un proyecto de Firebase (basado en el archivo .firebaserc o desde Firebase Console).
  2. Indique para qué plataformas (p. ej., Android, iOS, macOS y web) le gustaría configurar.
  3. Identifique qué aplicaciones de Firebase para las plataformas elegidas se deben usar para extraer la configuración. De forma predeterminada, la CLI intentará hacer coincidir automáticamente las aplicaciones de Firebase según la configuración actual de su proyecto.
  4. Genere un archivo firebase_options.dart en su proyecto.

Configurar macOS

Flutter en macOS crea aplicaciones completamente protegidas. Como esta aplicación se está integrando mediante la red para comunicarse con los servidores de Firebase, deberá configurar su aplicación con privilegios de cliente de red.

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>

Ver Prestaciones y el recinto de seguridad de aplicaciones para obtener más detalles.

5. Agregar inicio de sesión de usuario (RSVP)

Ahora que ha agregado Firebase a la aplicación, se puede configurar un botón de RSVP que registra las personas que utilizan la autenticación Firebase . Para Android nativo, iOS nativo y Web, hay paquetes FirebaseUI Auth preconstruidos, pero para Flutter necesitarás construir esta capacidad.

El proyecto que recuperó en el Paso 2 incluía un conjunto de widgets que implementa la interfaz de usuario para la mayor parte del flujo de autenticación. Implementará la lógica comercial para integrar Firebase Authentication en la aplicación.

Lógica de negocios con proveedor

Usted va a utilizar el provider paquete para hacer un objeto de estado centralizada de aplicaciones disponibles a través del árbol de la aplicación de widgets alboroto. Para empezar, modificar las importaciones en la parte superior de la lib/main.dart :

lib/principal.dardo

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';

Las import líneas introducen Firebase Core y Auth, tirón en el provider paquete que está utilizando para hacer que el objeto de estado de aplicación disponible a través del árbol de widgets, e incluyen los reproductores de autenticación de lib/src .

Este objeto estado de la aplicación, ApplicationState , tiene dos responsabilidades principales de este paso, pero ganará responsabilidades adicionales a medida que agrega más capacidades para la aplicación en los pasos posteriores. La primera responsabilidad es inicializar la biblioteca Firebase con una llamada a Firebase.initializeApp() , y luego está el manejo del flujo de autorización. Añadir la siguiente clase al final de lib/main.dart :

lib/principal.dardo

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 señalar algunos puntos clave en esta clase. El usuario comienza sin autenticarse, la aplicación muestra un formulario que solicita la dirección de correo electrónico del usuario, dependiendo de si esa dirección de correo electrónico está registrada, la aplicación le pedirá al usuario que se registre o solicitará su contraseña y, suponiendo que todo funcione, el usuario está autenticado.

Debe tenerse en cuenta que esta no es una implementación completa del flujo de autenticación de FirebaseUI, ya que no maneja el caso de un usuario con una cuenta existente que tiene problemas para iniciar sesión. La implementación de esta capacidad adicional se deja como un ejercicio para el lector motivado.

Integración del flujo de autenticación

Ahora que tiene el inicio del estado de la aplicación es el momento de conectar el estado de la aplicación en la inicialización de la aplicación y añadir el flujo de autenticación en la HomePage . Actualizar el punto de entrada principal para integrar estado de la aplicación a través del provider paquete:

lib/principal.dardo

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

La modificación a la main función hace que el paquete de proveedor responsable de instanciar el objeto de estado de aplicación utilizando el ChangeNotifierProvider de widgets. Está utilizando esta clase específica del proveedor debido a que el objeto de estado de aplicación se extiende ChangeNotifier y esto permite que el provider paquete para saber cuándo hay que volver a mostrar los widgets dependientes. Por último, integrar el estado de la aplicación con Authentication mediante la actualización HomePage 's build método:

lib/principal.dardo

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!',
          ),
        ],
      ),
    );
  }
}

Se ejemplariza la Authentication del widget, y se envuelve en una Consumer de widgets. El Consumidor widget de la forma habitual que el provider paquete se puede utilizar para reconstruir parte del árbol cuando cambia el estado de la aplicación. La Authentication widget es la interfaz de usuario de autenticación que se quiere ahora prueba.

Prueba del flujo de autenticación

cdf2d25e436bd48d.png

Aquí está el inicio del flujo de autenticación, donde el usuario puede tocar el botón RSVP para iniciar el formulario de correo electrónico.

2a2cd6d69d172369.png

Al ingresar el correo electrónico, el sistema confirma si el usuario ya está registrado, en cuyo caso se le solicita al usuario una contraseña, alternativamente si el usuario no está registrado, entonces pasa por el formulario de registro.

e5e65065dba36b54.png

Asegúrese de intentar ingresar una contraseña corta (menos de seis caracteres) para verificar el flujo de manejo de errores. Si el usuario está registrado, verá la contraseña en su lugar.

fbb3ea35fb4f67a.png

En esta página, asegúrese de ingresar contraseñas incorrectas para verificar el manejo de errores en esta página. Finalmente, una vez que el usuario haya iniciado sesión, verá la experiencia de inicio de sesión que le ofrece al usuario la posibilidad de cerrar sesión nuevamente.

4ed811a25b0cf816.png

Y con eso, ha implementado un flujo de autenticación. ¡Felicitaciones!

6. Escribir mensajes en Cloud Firestore

Saber que los usuarios van a venir es genial, pero démosles a los invitados algo más que hacer en la aplicación. ¿Y si pudieran dejar mensajes en un libro de visitas? Pueden compartir por qué están emocionados de venir oa quién esperan conocer.

Para almacenar los mensajes de chat que los usuarios escriben en la aplicación, va a utilizar la nube Firestore .

Modelo de datos

Cloud Firestore es una base de datos NoSQL y los datos almacenados en la base de datos se dividen en colecciones, documentos, campos y subcolecciones. Va a almacenar cada mensaje de la charla como un documento en una colección de nivel superior llamado guestbook .

7c20dc8424bb1d84.png

Agregar mensajes a Firestore

En esta sección, agregará la funcionalidad para que los usuarios escriban nuevos mensajes en la base de datos. Primero, agrega los elementos de la interfaz de usuario (campo de formulario y botón de envío), y luego agrega el código que conecta estos elementos a la base de datos.

En primer lugar, añadir las importaciones para el cloud_firestore paquete y dart:async .

lib/principal.dardo

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';

Para construir los elementos de interfaz de usuario de un campo de mensaje y un botón de envío, añadir un nuevo widget de stateful GuestBook en la parte inferior de la lib/main.dart .

lib/principal.dardo

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'),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Hay un par de puntos de interés aquí. En primer lugar, está creando una instancia de un formulario para que upi pueda validar que el mensaje realmente tiene algún contenido y mostrarle al usuario un mensaje de error si no hay ninguno. La forma de validar un formulario implica acceder al estado de forma tras la forma, y para ello se utiliza un GlobalKey . Para obtener más información sobre las teclas, y cómo usarlos, por favor ver el aleteo Reproductores 101 episodio "Al utilizar las teclas" .

También tenga en cuenta la forma en que los controles se disponen, tiene una Row , con un TextFormField y una StyledButton , que a su vez contiene una Row . También tenga en cuenta el TextFormField se envuelve en un Expanded de widgets, esto obliga al TextFormField que ocupan ningún espacio adicional en la fila. Para entender mejor por qué esto es necesario, por favor, lea las limitaciones Comprensión .

Ahora que tiene un widget que le permite al usuario ingresar texto para agregarlo al Libro de invitados, debe mostrarlo en la pantalla. Para ello, editar el cuerpo de la HomePage para añadir las dos líneas siguientes en la parte inferior de la ListView los niños 's:

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)),

Si bien esto es suficiente para mostrar el Widget, no es suficiente para hacer nada útil. Actualizará este código en breve para que funcione.

Vista previa de la aplicación

Un usuario hace clic en el botón ENVIAR disparará el siguiente fragmento de código. Se añade el contenido del campo de entrada mensaje al guestbook colección de la base de datos. Específicamente, el addMessageToGuestBook método añade el contenido del mensaje a un nuevo documento (con un ID generado automáticamente) al guestbook colección.

Tenga en cuenta que FirebaseAuth.instance.currentUser.uid es una referencia a la identificación única generada automáticamente que la autenticación Firebase da para todos los usuarios conectados.

Hacer otro cambio en el lib/main.dart archivo. Añadir el addMessageToGuestBook método. Conectará la interfaz de usuario y esta capacidad en el siguiente paso.

lib/principal.dardo

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
}

Conexión de la interfaz de usuario en la base de datos

Tiene una interfaz de usuario en la que el usuario puede ingresar el texto que desea agregar al libro de invitados y tiene el código para agregar la entrada a Cloud Firestore. Ahora todo lo que necesita hacer es conectar los dos juntos. En lib/main.dart hacer el siguiente cambio en la HomePage de widgets.

lib/principal.dardo

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.
        ],
      ),
    );
  }
}

Reemplazó las dos líneas que agregó al comienzo de este paso con la implementación completa. Usted está utilizando de nuevo Consumer<ApplicationState> para hacer que el estado de la aplicación a disposición de la parte del árbol se renderiza. Esto le permite reaccionar cuando alguien ingresa un mensaje en la interfaz de usuario y publicarlo en la base de datos. En la siguiente sección probará si los mensajes agregados se publican en la base de datos.

Probar el envío de mensajes

  1. Asegúrate de haber iniciado sesión en la aplicación.
  2. Introduzca un mensaje como "Hola!" Y, a continuación, haga clic en ENVIAR.

Esta acción escribe el mensaje en su base de datos de Cloud Firestore. Sin embargo, aún no verá el mensaje en su aplicación Flutter real porque aún necesita implementar la recuperación de datos. Lo harás en el siguiente paso.

Pero puede ver el mensaje recién agregado en la consola de Firebase.

En la consola Firebase, en el panel de control de base de datos , usted debe ver el guestbook de recogida con el mensaje que acaba de agregar. Si continúa enviando mensajes, su colección de libros de visitas contendrá muchos documentos, como este:

consola base de fuego

713870af0b3b63c.png

7. Leer mensajes

Es genial que los invitados puedan escribir mensajes en la base de datos, pero aún no pueden verlos en la aplicación. ¡Arreglemos eso!

Sincronizar mensajes

Para mostrar mensajes, deberá agregar oyentes que se activen cuando cambien los datos y luego crear un elemento de interfaz de usuario que muestre nuevos mensajes. Agregará código al estado de la aplicación que escucha los mensajes recién agregados desde la aplicación.

Justo por encima del GuestBook widget de la siguiente clase de valor. Esta clase expone una vista estructurada de los datos que está almacenando en Cloud Firestore.

lib/principal.dardo

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

En la sección de ApplicationState donde se define el estado y captadores, añadir las siguientes nuevas líneas:

lib/principal.dardo

  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.

Y, por último, en la sección de inicialización de ApplicationState , añada lo siguiente a suscribirse a una consulta sobre la colección de documentos cuando un usuario se conecta, y darse de baja cuando se conectan a cabo.

lib/principal.dardo

  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();
    });
  }

Esta sección es importante, ya que aquí es donde se construye una consulta sobre el guestbook de recogida, y el mango de suscripción y cancelar la suscripción a esta colección. Usted escucha a la corriente, donde se reconstruye una caché local de los mensajes en el guestbook colección, y también almacena una referencia a esta suscripción para que pueda darse de baja de él más tarde. Están sucediendo muchas cosas aquí, y vale la pena pasar un tiempo en un depurador inspeccionando qué sucede y cuándo para obtener un modelo mental más claro.

Para obtener más información, consulte la documentación de la nube Firestore .

En el GuestBook widget es necesario conectar este estado cambiante de la interfaz de usuario. Usted modifica el widget agregando una lista de mensajes como parte de su configuración.

lib/principal.dardo

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();
}

A continuación, se expone esta nueva configuración en _GuestBookState modificando la build método de la siguiente manera.

lib/principal.dardo

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

En que se coloca el contenido anterior del método de aumento con una Column flash, y luego en la cola de la Column los niños 's se agrega una colección para generar un nuevo Paragraph para cada mensaje en la lista de mensajes.

Por último, ahora tiene que actualizar el cuerpo de HomePage para construir correctamente GuestBook con el nuevo messages de parámetros.

lib/principal.dardo

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
        ),
      ],
    ],
  ),
),

Prueba sincronizando mensajes

Cloud Firestore sincroniza de forma automática e instantánea los datos con los clientes suscritos a la base de datos.

  1. Los mensajes que creó anteriormente en la base de datos deben mostrarse en la aplicación. Siéntete libre de escribir nuevos mensajes; deberían aparecer al instante.
  2. Si abre su espacio de trabajo en varias ventanas o pestañas, los mensajes se sincronizarán en tiempo real en todas las pestañas.
  3. (Opcional) Puede probar manualmente la supresión, modificación o adición de nuevos mensajes directamente en la sección Base de datos de la consola Firebase; cualquier cambio debería aparecer en la interfaz de usuario.

¡Felicidades! ¡Está leyendo documentos de Cloud Firestore en su aplicación!

Aplicación opinión p

8. Establezca reglas básicas de seguridad

Inicialmente configuró Cloud Firestore para usar el modo de prueba, lo que significa que su base de datos está abierta para lecturas y escrituras. Sin embargo, solo debe usar el modo de prueba durante las primeras etapas de desarrollo. Como práctica recomendada, debe configurar reglas de seguridad para su base de datos a medida que desarrolla su aplicación. La seguridad debe ser parte integral de la estructura y el comportamiento de su aplicación.

Las reglas de seguridad le permiten controlar el acceso a documentos y colecciones en su base de datos. La sintaxis de reglas flexibles le permite crear reglas que coincidan con cualquier cosa, desde todas las escrituras en toda la base de datos hasta operaciones en un documento específico.

Puedes escribir reglas de seguridad para Cloud Firestore en Firebase console:

  1. En sección Revelado de la consola Firebase, haga clic en Base de datos y seleccione la ficha Reglas (o haga clic aquí para ir directamente a la pestaña Reglas).
  2. Debería ver las siguientes reglas de seguridad predeterminadas, junto con una advertencia acerca de que las reglas son públicas.

7767a2d2e64e7275.png

Identificar colecciones

Primero, identifique las colecciones en las que la aplicación escribe datos.

En match /databases/{database}/documents , identificar la colección que desea proteger:

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

Agregar reglas de seguridad

Debido a que usó el UID de autenticación como un campo en cada documento del libro de visitas, puede obtener el UID de autenticación y verificar que cualquier persona que intente escribir en el documento tenga un UID de autenticación coincidente.

Agregue las reglas de lectura y escritura a su conjunto de reglas como se muestra a continuación:

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;
    }
  }
}

Ahora, para el libro de visitas, solo los usuarios registrados pueden leer mensajes (¡cualquier mensaje!), pero solo el autor de un mensaje puede editar un mensaje.

Agregar reglas de validación

Agregue validación de datos para asegurarse de que todos los campos esperados estén presentes en el 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. Paso de bonificación: practica lo que has aprendido

Registrar el estado de RSVP de un asistente

En este momento, su aplicación solo permite que las personas comiencen a chatear si están interesadas en el evento. Además, la única forma de saber si alguien va a venir es si lo publican en el chat. Organicémonos y hagámosle saber a la gente cuántas personas vienen.

Agregará un par de capacidades nuevas al estado de la aplicación. La primera es la posibilidad de que un usuario que haya iniciado sesión indique si asistirá o no. La segunda capacidad es un contador de cuántas personas asisten realmente.

En lib/main.dart , añada lo siguiente a la sección de descriptores de acceso para que el código de interfaz de usuario para interactuar con este estado:

lib/principal.dardo

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});
  }
}

Actualización ApplicationState 's init método de la siguiente manera:

lib/principal.dardo

  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();
    });
  }

Lo anterior agrega una consulta siempre suscrita para averiguar el número de asistentes y una segunda consulta que solo está activa mientras un usuario está conectado para saber si el usuario está asistiendo. A continuación, añadir la siguiente enumeración después de la GuestBookMessage declaración:

lib/principal.dardo

enum Attending { yes, no, unknown }

Ahora va a definir un nuevo widget que actúa como los botones de radio de antaño. Comienza en un estado indeterminado, sin seleccionar sí ni no, pero una vez que el usuario selecciona si asistirá o no, muestra esa opción resaltada con un botón relleno y la otra opción retrocede con una representación plana.

lib/principal.dardo

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'),
              ),
            ],
          ),
        );
    }
  }
}

A continuación, debe actualizar HomePage método de aumento 's para tomar ventaja de YesNoSelection , lo que permite un usuario conectado a nominar si asisten. También mostrará el número de asistentes a este evento.

lib/principal.dardo

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,
        ),
      ],
    ],
  ),
),

Agregar reglas

Debido a que ya tiene algunas reglas configuradas, los nuevos datos que está agregando con los botones serán rechazados. Tendrá que actualizar las normas para permitir la adición a la attendees colección.

Para el attendees colección, ya que utilizó el UID de autenticación como el nombre del documento, puede agarrarlo y verificar que el remitente del uid es el mismo que el documento que están escribiendo. Permitirá que todos lean la lista de asistentes (ya que no hay datos privados allí), pero solo el creador debería poder actualizarla.

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

Agregar reglas de validación

Agregue validación de datos para asegurarse de que todos los campos esperados estén presentes en el 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;

    }
  }
}

(Opcional) Ahora puede ver los resultados de clic en los botones. Vaya a su panel de control de Cloud Firestore en la consola de Firebase.

Vista previa de la aplicación

10. ¡Felicitaciones!

Ha utilizado Firebase para crear una aplicación web interactiva en tiempo real.

Lo que hemos cubierto

  • Autenticación de base de fuego
  • Tienda de fuego en la nube
  • Reglas de seguridad de Firebase

Próximos pasos

  • ¿Quieres obtener más información sobre otros productos de Firebase? ¿Quizás desee almacenar archivos de imagen que cargan los usuarios? ¿O enviar notificaciones a tus usuarios? Echa un vistazo a la documentación Firebase . ¿Quieres obtener más información sobre los complementos de Flutter para Firebase? Salida FlutterFire para más información.
  • ¿Quieres saber más sobre Cloud Firestore? ¿Quizás quiera aprender sobre subcolecciones y transacciones? Pásate por el laboratorio de programación Web Cloud Firestore para un laboratorio de programación que entra en más profundidad sobre la nube Firestore. O echa un vistazo a esta serie de YouTube para llegar a conocer la nube Firestore !

Aprende más

¿Como le fue?

Nos encantaría recibir tus comentarios! Por favor llene una (muy) breve formulario aquí .