Conozca Firebase para Flutter

Organiza tus páginas con colecciones Guarda y categoriza el contenido según tus preferencias.

1. Antes de comenzar

En este laboratorio de código, aprenderá algunos de los conceptos básicos de Firebase para crear aplicaciones móviles de Flutter para Android e iOS.

requisitos previos

Este codelab asume que está familiarizado con Flutter y que ha instalado el SDK de Flutter 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:

  • Un dispositivo físico (Android o iOS) conectado a su computadora y configurado en modo desarrollador.
  • El simulador de iOS. (Requiere instalar las herramientas de Xcode ).
  • El emulador de Android. (Requiere configuración en Android Studio ).

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 Android Studio o VS Code configurado con los complementos Dart y Flutter.
  • La última versión stable de Flutter (o beta si te gusta vivir al límite).
  • Una cuenta de Google, como una cuenta de Gmail, para crear y administrar su proyecto de Firebase.
  • La herramienta de línea de comandos de firebase , inició sesión en 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.

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

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

Alternativamente, si tiene instalada la herramienta cli de GitHub :

gh repo clone flutter/codelabs flutter-codelabs

El código de muestra debe clonarse en el directorio flutter-codelabs , que contiene el código para una colección de codelabs. El código de este laboratorio de programación se encuentra en flutter-codelabs/firebase-get-to-know-flutter .

La estructura de directorios en flutter-codelabs/firebase-get-to-know-flutter es una serie de instantáneas de dónde deberías estar al final de cada paso nombrado. 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

Abra o importe el flutter-codelabs/firebase-get-to-know-flutter/step_02 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 varios 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 Auth con un conjunto de widgets para crear una experiencia de usuario de inicio de sesión para la autenticación basada en correo electrónico de Firebase. 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.

Revisando el archivo lib/main.dart

Esta aplicación aprovecha el paquete google_fonts para permitirnos hacer que Roboto sea la fuente predeterminada en toda la aplicación. Un ejercicio para el lector motivado es explorar fonts.google.com y usar las fuentes que descubra allí en diferentes partes de la aplicación.

Está utilizando los widgets auxiliares de lib/src/widgets.dart en forma de Header , Paragraph e icono y IconAndDetail . Estos widgets reducen el desorden en el diseño de la página descrito en HomePage al eliminar el 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. Inicie sesión en Firebase .
  2. En la consola de Firebase, haga clic en Agregar proyecto (o Crear un proyecto ) y asigne a su proyecto de Firebase el nombre Firebase-Flutter-Codelab .

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 Firebase, consulte Comprender los proyectos de Firebase .

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

  • Firebase Authentication para permitir que sus usuarios inicien sesión en su aplicación.
  • Cloud Firestore para guardar datos estructurados en la nube y recibir notificaciones instantáneas cuando cambien los datos.
  • Reglas de seguridad de Firebase para proteger su base de datos.

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

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

Para permitir que los usuarios inicien sesión en la aplicación web, utilizará el método de inicio de sesión de correo electrónico/contraseña para este laboratorio de código:

  1. En Firebase console, expanda el menú Build en el panel izquierdo.
  2. Haga clic en Autenticación y luego haga clic en el botón Comenzar , luego en la pestaña Método de inicio de sesión (o haga clic aquí para ir directamente a la pestaña Método de inicio de sesión ).
  3. Haga clic en Correo electrónico/Contraseña en la lista de proveedores de inicio de sesión , configure el interruptor Habilitar en la posición de encendido y luego haga clic en Guardar . 58e3e3e23c2f16a4.png

Habilitar Cloud Firestore

La aplicación web usa Cloud Firestore para guardar mensajes de chat y recibir nuevos mensajes de chat.

Habilitar Cloud Firestore:

  1. En la sección Crear de la consola Firebase, haga clic en Cloud Firestore .
  2. Haga clic en Crear base de datos . 99e8429832d23fa3.png
  1. Seleccione la opción Iniciar 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 adelante. 278656eefcfb0216.png
  2. Haga clic en Habilitar .

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 su aplicación Flutter, 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

firebase_core es el código común requerido para todos los complementos de Firebase Flutter.

$ flutter pub add firebase_auth

firebase_auth permite la integración con la capacidad de autenticación de Firebase.

$ flutter pub add cloud_firestore

El cloud_firestore permite el acceso al almacenamiento de datos de Cloud Firestore.

$ flutter pub add provider

El paquete firebase_ui_auth proporciona un conjunto de widgets y utilidades específicamente para aumentar la velocidad del desarrollador con flujos de autenticación.

$ flutter pub add firebase_ui_auth

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 está utilizando el paquete provider que habilitará la separación de la lógica comercial de la lógica de visualización.

Instalando flutterfire

La CLI de FlutterFire depende de la CLI de Firebase subyacente. Si aún no lo ha hecho, asegúrese de que Firebase CLI esté instalado en su máquina.

A continuación, instale FlutterFire CLI ejecutando el siguiente comando:

$ dart pub global activate flutterfire_cli

Una vez instalado, el comando flutterfire estará disponible globalmente.

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>

Consulte Derechos y la zona de pruebas de la aplicación para obtener más detalles.

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

Ahora que agregó Firebase a la aplicación, puede configurar un botón RSVP que registre a las personas que usan la autenticación de 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

Vas a utilizar el paquete del provider para hacer que un objeto de estado de la aplicación centralizado esté disponible en todo el árbol de widgets de Flutter de la aplicación. Para empezar, modifica las importaciones en la parte superior de lib/main.dart :

lib/principal.dardo

import 'dart:async';                                     // new
import 'package:firebase_auth/firebase_auth.dart'        // new
    hide EmailAuthProvider, PhoneAuthProvider;           // new
import 'package:firebase_core/firebase_core.dart';       // new
import 'package:firebase_ui_auth/firebase_ui_auth.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 líneas de import presentan Firebase Core y Auth, extraen el paquete del provider que está utilizando para que el objeto de estado de la aplicación esté disponible a través del árbol de widgets e incluyen los widgets de autenticación de firebase_ui_auth .

Este objeto de estado de la aplicación, ApplicationState , tiene una responsabilidad principal para este paso, que es alertar al árbol de widgets de que hubo una actualización de un estado autenticado. Agrega la siguiente clase al final de lib/main.dart :

lib/principal.dardo

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

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

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

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

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

Estamos utilizando un proveedor aquí para comunicar a la aplicación el estado de inicio de sesión de un usuario, nada más. Para iniciar sesión de un usuario, vamos a utilizar las IU proporcionadas por firebase_ui_auth que es una excelente manera de iniciar rápidamente las pantallas de inicio de sesión para sus aplicaciones.

Integración del flujo de autenticación

Ahora que tiene el inicio del estado de la aplicación, es hora de conectar el estado de la aplicación a la inicialización de la aplicación y agregar el flujo de autenticación a HomePage . Actualice el punto de entrada principal para integrar el estado de la aplicación a través del paquete del provider :

lib/principal.dardo

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

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

La modificación de la función main hace que el paquete del proveedor sea responsable de instanciar el objeto de estado de la aplicación mediante el widget ChangeNotifierProvider . Está utilizando esta clase de proveedor específica porque el objeto de estado de la aplicación amplía ChangeNotifier y esto permite que el paquete del provider sepa cuándo volver a mostrar los widgets dependientes.

Dado que estamos usando FirebaseUI para Flutter, vamos a actualizar nuestra aplicación para manejar la navegación a las diferentes pantallas que nos proporciona FirebaseUI. Para hacer esto, agregamos una propiedad initialRoute y agregamos nuestras pantallas preferidas a las que podemos enrutar bajo la propiedad de routes . Los cambios deberían verse así:

lib/principal.dardo

class App extends StatelessWidget {
  const App({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      //Start adding here
      initialRoute: '/home',
      routes: {
        '/home': (context) {
          return const HomePage();
        },
        '/sign-in': ((context) {
          return SignInScreen(
            actions: [
              ForgotPasswordAction(((context, email) {
                Navigator.of(context)
                    .pushNamed('/forgot-password', arguments: {'email': email});
              })),
              AuthStateChangeAction(((context, state) {
                if (state is SignedIn || state is UserCreated) {
                  var user = (state is SignedIn)
                      ? state.user
                      : (state as UserCreated).credential.user;
                  if (user == null) {
                    return;
                  }
                  if (state is UserCreated) {
                    user.updateDisplayName(user.email!.split('@')[0]);
                  }
                  if (!user.emailVerified) {
                    user.sendEmailVerification();
                    const snackBar = SnackBar(
                        content: Text(
                            'Please check your email to verify your email address'));
                    ScaffoldMessenger.of(context).showSnackBar(snackBar);
                  }
                  Navigator.of(context).pushReplacementNamed('/home');
                }
              })),
            ],
          );
        }),
        '/forgot-password': ((context) {
          final arguments = ModalRoute.of(context)?.settings.arguments
              as Map<String, dynamic>?;

          return ForgotPasswordScreen(
            email: arguments?['email'] as String,
            headerMaxExtent: 200,
          );
        }),
        '/profile': ((context) {
          return ProfileScreen(
            providers: [],
            actions: [
              SignedOutAction(
                ((context) {
                  Navigator.of(context).pushReplacementNamed('/home');
                }),
              ),
            ],
          );
        })
      },
      // end adding here
      title: 'Firebase Meetup',
      theme: ThemeData(
        buttonTheme: Theme.of(context).buttonTheme.copyWith(
              highlightColor: Colors.deepPurple,
            ),
        primarySwatch: Colors.deepPurple,
        textTheme: GoogleFonts.robotoTextTheme(
          Theme.of(context).textTheme,
        ),
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
    );
  }
}

Cada pantalla tiene un tipo diferente de acción asociada con ella en función del nuevo estado del flujo de autenticación. Después de la mayoría de los cambios de estado en la autenticación, podemos volver a enrutar a una pantalla preferida, ya sea la pantalla de inicio o una pantalla diferente, como el perfil. Finalmente, integre el estado de la aplicación con AuthFunc actualizando el método de compilación de HomePage :

lib/principal.dardo

class HomePage extends StatelessWidget {
  const HomePage({super.key});

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

Crea una instancia del widget AuthFunc y lo envuelve en un widget de Consumer . El widget del consumidor de la forma habitual en que el paquete del provider se puede usar para reconstruir parte del árbol cuando cambia el estado de la aplicación. El widget AuthFunc son los widgets complementarios que probará ahora.

Prueba del flujo de autenticación

cdf2d25e436bd48d.png

Aquí está el comienzo del flujo de autenticación, donde el usuario puede tocar el botón RSVP para iniciar SignInScreen .

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.

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, utilizará Cloud 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. Almacenará cada mensaje del chat como un documento en una colección de nivel superior llamada libro de 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.

Primero, agregue importaciones para el paquete cloud_firestore 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 la interfaz de usuario de un campo de mensaje y un botón de envío, agregue un nuevo GuestBook de widget con estado en la parte inferior de lib/main.dart .

lib/principal.dardo

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

  @override
  State<GuestBook> createState() => _GuestBookState();
}

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

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

Hay un par de puntos de interés aquí. En primer lugar, está creando una instancia de un formulario para que 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 del formulario detrás del formulario, y para esto usas una GlobalKey . Para obtener más información sobre las teclas y cómo usarlas, consulte el episodio "Cuándo usar las teclas" de Flutter Widgets 101 .

También tenga en cuenta la forma en que se distribuyen los widgets, tiene un Row , con un TextFormField y un StyledButton , que a su vez contiene un Row . También tenga en cuenta que TextFormField está envuelto en un widget Expanded , esto obliga a TextFormField a ocupar cualquier espacio adicional en la fila. Para comprender mejor por qué esto es necesario, lea Comprender las restricciones .

Ahora que tiene un widget que le permite al usuario ingresar texto para agregarlo al Libro de invitados, debe mostrarlo en la pantalla. Para hacerlo, edite el cuerpo de HomePage para agregar las siguientes dos líneas en la parte inferior de los elementos secundarios de ListView :

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

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 que haga clic en el botón ENVIAR activará el fragmento de código a continuación. Agrega el contenido del campo de entrada del mensaje a la colección del libro de guestbook de la base de datos. Específicamente, el método addMessageToGuestBook agrega el contenido del mensaje a un nuevo documento (con una identificación generada automáticamente) a la colección del libro de guestbook .

Tenga en cuenta que FirebaseAuth.instance.currentUser.uid es una referencia a la ID única generada automáticamente que Firebase Authentication otorga a todos los usuarios que iniciaron sesión.

Realice otro cambio en el archivo lib/main.dart . Agregue el método addMessageToGuestBook . 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 (!_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 realice el siguiente cambio en el widget de página de HomePage .

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, _) => AuthFunc(
                loggedIn: appState.loggedIn,
                signOut: () {
                  FirebaseAuth.instance.signOut();
                }),
          ),
          const Divider(
            height: 8,
            thickness: 1,
            indent: 8,
            endIndent: 8,
            color: Colors.grey,
          ),
          const Header("What we'll be doing"),
          const Paragraph(
            'Join us for a day full of Firebase Workshops and Pizza!',
          ),
          // Modify from here
          Consumer<ApplicationState>(
            builder: (context, appState, _) => Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                if (appState.loggedIn) ...[
                  const Header('Discussion'),
                  GuestBook(
                    addMessage: (message) =>
                        appState.addMessageToGuestBook(message),
                  ),
                ],
              ],
            ),
          ),
          // To here.
        ],
      ),
    );
  }
}

Reemplazó las dos líneas que agregó al comienzo de este paso con la implementación completa. Está utilizando nuevamente Consumer<ApplicationState> para que el estado de la aplicación esté disponible para la parte del árbol que está representando. 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. Ingrese un mensaje como "¡Hola!" y luego 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 de Firebase, en el panel de control de la base de datos, debería ver la colección del libro de guestbook con su mensaje recién agregado. 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 encima del widget GuestBook 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 define el estado y los captadores, agregue las siguientes líneas nuevas:

lib/principal.dardo

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

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

Y finalmente, en la sección de inicialización de ApplicationState , agregue lo siguiente para suscribirse a una consulta sobre la colección de documentos cuando un usuario inicia sesión y cancelar la suscripción cuando cierra la sesión.

lib/principal.dardo

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

    FirebaseUIAuth.configureProviders([
      EmailAuthProvider(),
    ]);
    
    FirebaseAuth.instance.userChanges().listen((user) {
      if (user != null) {
        _loggedIn = true;
        _guestBookSubscription = FirebaseFirestore.instance
            .collection('guestbook')
            .orderBy('timestamp', descending: true)
            .snapshots()
            .listen((snapshot) {
          _guestBookMessages = [];
          for (final document in snapshot.docs) {
            _guestBookMessages.add(
              GuestBookMessage(
                name: document.data()['name'] as String,
                message: document.data()['text'] as String,
              ),
            );
          }
          notifyListeners();
        });
      } else {
        _loggedIn = false;
        _guestBookMessages = [];
        _guestBookSubscription?.cancel();
      }
      notifyListeners();
    });
  }

Esta sección es importante, ya que aquí es donde construye una consulta sobre la colección del libro de guestbook y maneja la suscripción y cancelación de suscripción a esta colección. Usted escucha la transmisión, donde reconstruye un caché local de los mensajes en la colección del libro de guestbook , y también almacena una referencia a esta suscripción para que pueda darse de baja 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 Cloud Firestore .

En el widget GuestBook , debe conectar este estado cambiante a 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({super.key, 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, exponemos esta nueva configuración en _GuestBookState modificando el método de build 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.
    );
  }
}

Envuelve el contenido anterior del método de compilación con un widget de Column y luego, al final de los elementos secundarios de la Column , agrega una colección para generar un nuevo Paragraph para cada mensaje en la lista de mensajes.

Finalmente, ahora necesita actualizar el cuerpo de HomePage para construir correctamente GuestBook con el nuevo parámetro de messages .

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 intentar eliminar, modificar o agregar nuevos mensajes manualmente directamente en la sección Base de datos de Firebase console; cualquier cambio debería aparecer en la interfaz de usuario.

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

Revisión de la aplicació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 la sección Desarrollar de la consola Firebase, haga clic en Base de datos y luego seleccione la pestaña 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 , identifique 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 , agregue lo siguiente a la sección de accesores para permitir que el código de la interfaz de usuario interactúe 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});
  }
}

Actualice el método init de ApplicationState de la siguiente manera:

lib/principal.dardo

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

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

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

    FirebaseAuth.instance.userChanges().listen((user) {
      if (user != null) {
        _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, agregue la siguiente enumeración después de la declaración GuestBookMessage :

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(
      {super.key, required this.state, required this.onSelection});
  final Attending state;
  final void Function(Attending selection) onSelection;

  @override
  Widget build(BuildContext context) {
    switch (state) {
      case Attending.yes:
        return Padding(
          padding: const EdgeInsets.all(8.0),
          child: Row(
            children: [
              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 el método de compilación de HomePage para aprovechar YesNoSelection , lo que permite que un usuario que haya iniciado sesión nomine si asistirá. 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.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. Deberá actualizar las reglas para permitir agregar a la colección de attendees .

Para la recopilación de attendees , dado que usó el UID de autenticación como nombre del documento, puede tomarlo y verificar que el UID del uid sea el mismo que el del documento que está 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 hacer 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? Consulte la documentación de Firebase . ¿Quieres obtener más información sobre los complementos de Flutter para Firebase? Consulte FlutterFire para obtener más información.
  • ¿Quieres saber más sobre Cloud Firestore? ¿Quizás quiera aprender sobre subcolecciones y transacciones? Diríjase al laboratorio de código web de Cloud Firestore para ver un laboratorio de código que profundiza en Cloud Firestore. ¡O echa un vistazo a esta serie de YouTube para conocer Cloud Firestore !

Aprende más

¿Como le fue?

Nos encantaría recibir tus comentarios! Complete un (muy) breve formulario aquí .