Google se compromete a impulsar la igualdad racial para las comunidades afrodescendientes. Obtén información al respecto.

Conozca Firebase para Flutter

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.

Prerrequisitos

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 laboratorio de código, creará una aplicación de chat de libro de visitas y RSVP de evento en Android, iOS, la Web y macOS usando Flutter. Autenticarás a los usuarios con Firebase Authentication y sincronizarás los datos con Cloud Firestore.

Lo que necesitarás

Puede ejecutar este laboratorio de código utilizando cualquiera de los siguientes dispositivos:

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

  • 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.
  • El código de muestra del codelab. Consulte el siguiente paso para saber cómo obtener el código.

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 Flutter que aún no funciona.

Ubique los archivos en los que 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.

Busque 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. 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 utilizan 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 y sensación consistentes.

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

Vista previa de la aplicación

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

Crea 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 utilizará 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á creando utiliza 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 de 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.

Habilita 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 predeterminada). Tenga en cuenta que esta ubicación no se puede cambiar más adelante. 278656eefcfb0216.png
  2. Haga clic en Activar.

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

  • Añadir las dependencias FlutterFire a pubspec.yaml
  • Registra la plataforma deseada en el proyecto de 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 ios y android . Estos directorios contienen los archivos de configuración específicos de la plataforma para iOS y Android, respectivamente.

Configurar dependencias

Debe agregar las bibliotecas FlutterFire para los dos productos de Firebase que está utilizando en esta aplicación: Firebase Auth y Cloud Firestore. Editar pubspec.yaml y añadir las siguientes dependencias:

pubspec.yaml

dependencies:
  flutter:
    sdk: flutter
  cloud_firestore: ^1.0.0 # new
  firebase_auth: ^1.0.0   # new
  google_fonts: ^2.0.0
  provider: ^5.0.0        # new

Si bien ha agregado los paquetes requeridos, también debe configurar los proyectos de ejecución de iOS, Android, macOS y Web 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.

Configurar iOS

  1. En la consola de Firebase , seleccione Visión general del proyecto en la barra de navegación de la izquierda y haga clic en el botón IOS bajo Comience agregando Firebase a su aplicación.

Debería ver el siguiente cuadro de diálogo:

c42139f18fb9a2ee.png

  1. El valor importante proporcionar es el paquete ID IOS. Obtiene el ID del paquete al realizar los siguientes tres pasos.
  1. En la herramienta de línea de comandos, vaya al directorio de nivel superior de su aplicación Flutter.
  2. Ejecute el comando open ios/Runner.xcworkspace para abrir Xcode.
  1. En Xcode, haga clic en el corredor de nivel superior en el panel izquierdo, a continuación, seleccione Runner bajo objetivos, para mostrar la ficha General en el panel de la derecha, como se muestra. Copiar el valor Bundle identificador.

9d67acd88c718763.png

  1. Volver al cuadro de diálogo Firebase, pegue el copiado Bundle identificador en el campo ID de paquete de iOS, y haga clic en Registro de aplicaciones.
  1. Continuando en Firebase, siga las instrucciones para descargar el archivo de configuración GoogleService-Info.plist .
  2. Vuelve a Xcode. Tenga en cuenta que Runner tiene una subcarpeta también llamado Runner (mostrado en la imagen anterior).
  3. Arrastre el GoogleService-Info.plist archivo (que acaba de descargar) en esa subcarpeta Runner.
  4. En el cuadro de diálogo que aparece en Xcode, haga clic en Finalizar.
  5. Siéntase libre de cerrar Xcode en este punto, ya que no es necesario en el futuro.
  6. Regrese a la consola de Firebase. En el paso de configuración, haga clic en Siguiente, omita los pasos restantes, y volver a la página principal de la consola Firebase.

Terminaste de configurar tu aplicación Flutter para iOS. Para más detalles, consulte la documentación de instalación FlutterFire IOS .

Configurar Android

  1. En la consola Firebase , seleccione Visión general del proyecto en la barra de navegación de la izquierda y haga clic en el botón de Android bajo Comience agregando Firebase a su aplicación.

Debería ver el siguiente cuadro de diálogo: 8254fc299e82f528.png

  1. El valor es importante proporcionar el nombre del paquete de Android. Obtiene el nombre del paquete cuando realiza los siguientes dos pasos:
  1. En su directorio de aplicación del alboroto, abra el archivo android/app/src/main/AndroidManifest.xml .
  2. En el manifest elemento, encontrar el valor de cadena del package atributo. Este valor es el nombre del paquete de Android (algo así como com.yourcompany.yourproject ). Copie este valor.
  3. En el cuadro de diálogo Firebase, pegue el nombre del paquete copiado en el campo nombre del paquete Android.
  4. No es necesario el certificado de firma de depuración SHA-1 para este laboratorio de programación. Deje este espacio en blanco.
  5. Haga clic en Registro de aplicaciones.
  6. Continuando en Firebase, siga las instrucciones para descargar el archivo de configuración de google-services.json .
  7. Vaya a su directorio de aplicación del alboroto, y mover el google-services.json archivo (que acaba de descargar) en el android/app directorio.
  8. De vuelta en la consola de Firebase, omita los pasos restantes y vuelva a la página principal de la consola de Firebase.
  9. Editar su android/build.gradle añadir los google-services plugin de dependencia:

android / build.gradle

dependencies {
        classpath 'com.android.tools.build:gradle:4.1.0'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        classpath 'com.google.gms:google-services:4.3.5'  // new
}
  1. Editar su android/app/build.gradle para permitir que los google-services de plugin:

android / app / build.gradle

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
apply plugin: 'com.google.gms.google-services'  // new
  1. Firebase requiere que Multidex esté habilitado, y una forma de hacerlo es establecer el SDK mínimo admitido en 21 o superior. Editar su android/app/build.gradle para actualizar minSdkVersion :

android / app / build.gradle

defaultConfig {
    applicationId "com.example.gtk_flutter"
    minSdkVersion 21  // Updated
    targetSdkVersion 30
    versionCode flutterVersionCode.toInteger()
    versionName flutterVersionName
}

Terminaste de configurar tu aplicación Flutter para Android. Para más detalles, consulte la documentación de instalación FlutterFire Android .

Configurar para la Web

  1. En la consola Firebase , seleccione Visión general del proyecto en la barra de navegación de la izquierda y haga clic en el botón Web bajo Comience agregando Firebase a su aplicación.

25b14deff9e589ce.png

  1. Dar a esta aplicación un apodo, y haga clic en el botón de aplicación Registro. Vamos a dejar el alojamiento de Firebase desactivado para este tutorial, ya que solo lo ejecutaremos localmente. No dude en leer más sobre Firebase Hosting aquí. 9c697cc1b309c806.png
  2. Editar la sección del cuerpo de la web/index.html archivo de la siguiente manera. Asegúrese de añadir los firebaseConfig datos de la etapa anterior.

web / index.html

<body>
  <script>
    if ('serviceWorker' in navigator) {
      window.addEventListener('flutter-first-frame', function () {
        navigator.serviceWorker.register('flutter_service_worker.js');
      });
    }
  </script>

  <!-- Add from here -->
  <script src="https://www.gstatic.com/firebasejs/7.20.0/firebase-app.js"></script>
  <script src="https://www.gstatic.com/firebasejs/7.20.0/firebase-auth.js"></script>
  <script src="https://www.gstatic.com/firebasejs/7.20.0/firebase-firestore.js"></script>
  <script>
    // Your web app's Firebase configuration
    var firebaseConfig = {
      // Replace this with your firebaseConfig
    };
    // Initialize Firebase
    firebase.initializeApp(firebaseConfig);
  </script>
  <!-- to here. -->

  <script src="main.dart.js" type="application/javascript"></script>
</body>

Terminaste de configurar tu aplicación Flutter para la Web. Para más detalles, consulte la documentación de instalación FlutterFire Web .

Configurar macOS

Los pasos de configuración para macOS son casi idénticos a los de iOS. Vamos a la reutilización fichero de configuración GoogleService-Info.plist del IOS pasos anteriores.

  1. Ejecute el comando open macos/Runner.xcworkspace para abrir Xcode.
  1. Arrastre el GoogleService-Info.plist archivo en la subcarpeta Runner. Este fue creado en los pasos anteriores Configurar iOS. c2b9229a605fd738.png
  2. En el macos/Runner/DebugProfile.entitlements archivo, añadir un com.apple.security.network.client derecho, y lo puso a true . 8bee5665e35d3f34.png
  3. En el macos/Runner/Release.entitlements archivo, también añadir un com.apple.security.network.client derecho, y lo puso a true . 41e2e23b7928546a.png
  4. Siéntase libre de cerrar Xcode en este punto, ya que no es necesario en el futuro.

Terminaste de configurar tu aplicación Flutter para macOS. Para más detalles, consulte la documentación de instalación FlutterFire macOS , y el soporte de sobremesa para el trémolo página.

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 prediseñados, pero para Flutter necesitará desarrollar 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ás la lógica empresarial para integrar Firebase Authentication en la aplicación.

Lógica empresarial 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 / main.dart

import 'package:firebase_core/firebase_core.dart'; // new
import 'package:firebase_auth/firebase_auth.dart'; // new
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:provider/provider.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 / main.dart

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

  Future<void> init() async {
    await Firebase.initializeApp();

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

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

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

  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!.updateProfile(displayName: 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 solicitando la dirección de correo electrónico del usuario, dependiendo de si esa dirección de correo electrónico está en el archivo, la aplicación le pedirá al usuario que se registre o solicitará su contraseña, y luego, asumiendo que todo funciona, 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.

Integrando el 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 / main.dart

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 / main.dart

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Firebase Meetup'),
      ),
      body: ListView(
        children: <Widget>[
          Image.asset('assets/codelab.png'),
          SizedBox(height: 8),
          IconAndDetail(Icons.calendar_today, 'October 30'),
          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
          Divider(
            height: 8,
            thickness: 1,
            indent: 8,
            endIndent: 8,
            color: Colors.grey,
          ),
          Header("What we'll be doing"),
          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 al usuario se le solicita 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 de 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 ofrece al usuario la posibilidad de cerrar sesión nuevamente.

4ed811a25b0cf816.png

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

Saber que los usuarios van a venir es genial, pero demos 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 o con 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 / main.dart

import 'dart:async';                                    // new
import 'package:cloud_firestore/cloud_firestore.dart';  // new
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:provider/provider.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 / main.dart

class GuestBook extends StatefulWidget {
  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;
                },
              ),
            ),
            SizedBox(width: 8),
            StyledButton(
              onPressed: () async {
                if (_formKey.currentState!.validate()) {
                  await widget.addMessage(_controller.text);
                  _controller.clear();
                }
              },
              child: Row(
                children: [
                  Icon(Icons.send),
                  SizedBox(width: 4),
                  Text('SEND'),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Aquí hay un par de puntos de interés. En primer lugar, está creando una instancia de un formulario para que upi pueda validar que el mensaje en realidad tiene algo de contenido y mostrarle al usuario un mensaje de error si no lo hay. 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 visitas, 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:

Header("What we'll be doing"),
Paragraph(
  'Join us for a day full of Firebase Workshops and Pizza!',
),
// Add the following two lines.
Header('Discussion'),
GuestBook(addMessage: (String 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 sea funcional.

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 / main.dart

class ApplicationState extends ChangeNotifier {

  // Current content of ApplicationState elided ...

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

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

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

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

lib / main.dart

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Firebase Meetup'),
      ),
      body: ListView(
        children: <Widget>[
          Image.asset('assets/codelab.png'),
          SizedBox(height: 8),
          IconAndDetail(Icons.calendar_today, 'October 30'),
          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,
            ),
          ),
          Divider(
            height: 8,
            thickness: 1,
            indent: 8,
            endIndent: 8,
            color: Colors.grey,
          ),
          Header("What we'll be doing"),
          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) ...[
                  Header('Discussion'),
                  GuestBook(
                    addMessage: (String 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.

Prueba de envío de mensajes

  1. Asegúrese 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 tu 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 los 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 sigue enviando mensajes, su colección de libros de visitas contendrá muchos documentos, como este:

Consola de Firebase

713870af0b3b63c.png

Es encantador 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 almacena en Cloud Firestore.

lib / main.dart

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 / main.dart

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

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

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

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 / main.dart

  Future<void> init() async {
    await Firebase.initializeApp();

    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 = [];
          snapshot.docs.forEach((document) {
            _guestBookMessages.add(
              GuestBookMessage(
                name: document.data()['name'],
                message: document.data()['text'],
              ),
            );
          });
          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 cuando 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. Modifica el widget agregando una lista de mensajes como parte de su configuración.

lib / main.dart

class GuestBook extends StatefulWidget {
  // Modify the following line
  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 / main.dart

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

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

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

Probar la sincronización de mensajes

Cloud Firestore sincroniza los datos de forma automática e instantánea 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 instantáneamente.
  2. Si abre su espacio de trabajo en varias ventanas o pestañas, los mensajes se sincronizarán en tiempo real entre 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ás leyendo documentos de Cloud Firestore en tu aplicación!

Aplicación opinión p

Inicialmente configuraste Cloud Firestore para usar el modo de prueba, lo que significa que tu 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 flexible le permite crear reglas que coincidan con cualquier cosa, desde todas las escrituras en toda la base de datos hasta las 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 que hayan iniciado sesión 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;
    }
  }
}

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 publica en el chat. Organicemos y hagamos saber a la gente cuántas personas vendrán.

Va a agregar un par de nuevas capacidades al estado de la aplicación. La primera es la capacidad de un usuario que ha iniciado sesión para nominar 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 / main.dart

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

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

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

lib / main.dart

  Future<void> init() async {
    await Firebase.initializeApp();

    // 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 = [];
          snapshot.docs.forEach((document) {
            _guestBookMessages.add(
              GuestBookMessage(
                name: document.data()['name'],
                message: document.data()['text'],
              ),
            );
          });
          notifyListeners();
        });
        // Add from here
        _attendingSubscription = FirebaseFirestore.instance
            .collection('attendees')
            .doc(user.uid)
            .snapshots()
            .listen((snapshot) {
          if (snapshot.data() != null) {
            if (snapshot.data()!['attending']) {
              _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 / main.dart

enum Attending { yes, no, unknown }

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

lib / main.dart

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

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

Consumer<ApplicationState>(
  builder: (context, appState, _) => Column(
    crossAxisAlignment: CrossAxisAlignment.start,
    children: [
      // Add from here
      if (appState.attendees >= 2)
        Paragraph('${appState.attendees} people going')
      else if (appState.attendees == 1)
        Paragraph('1 person going')
      else
        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.
        Header('Discussion'),
        GuestBook(
          addMessage: (String message) =>
              appState.addMessageToGuestBook(message),
          messages: appState.guestBookMessages,
        ),
      ],
    ],
  ),
),

Agregar reglas

Debido a que ya tiene configuradas algunas reglas, 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 allí no hay datos privados), 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. Ve a tu panel de Cloud Firestore en Firebase console.

Vista previa de la aplicación

¡Usaste Firebase para crear una aplicación web interactiva en tiempo real!

Lo que hemos cubierto

  • Autenticación de Firebase
  • Cloud Firestore
  • Reglas de seguridad de Firebase

Próximos pasos

  • ¿Quieres obtener más información sobre otros productos de Firebase? ¿Quizás desea almacenar archivos de imagen que cargan los usuarios? ¿O enviar notificaciones a sus 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 obtener más información sobre Cloud Firestore? ¿Quizás quieras 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í .