Conheça o Firebase para Flutter

1. Antes de começar

Neste codelab, você aprenderá alguns conceitos básicos do Firebase para criar aplicativos móveis Flutter para Android e iOS.

Pré-requisitos

O que você aprenderá

  • Como construir um aplicativo de RSVP de evento e bate-papo de livro de visitas no Android, iOS, Web e macOS com Flutter.
  • Como autenticar usuários com Firebase Authentication e sincronizar dados com Firestore.

A tela inicial do aplicativo no Android

A tela inicial do aplicativo no iOS

O que você precisará

Qualquer um dos seguintes dispositivos:

  • Um dispositivo físico Android ou iOS conectado ao seu computador e configurado no modo de desenvolvedor.
  • O simulador iOS (requer ferramentas Xcode ).
  • O emulador Android (requer configuração no Android Studio ).

Você também precisa do seguinte:

  • Um navegador de sua preferência, como o Google Chrome.
  • Um IDE ou editor de texto de sua preferência configurado com os plug-ins Dart e Flutter, como Android Studio ou Visual Studio Code .
  • A versão stable mais recente do Flutter ou beta se você gosta de viver no limite.
  • Uma Conta Google para a criação e gerenciamento do seu projeto Firebase.
  • A CLI Firebase fez login na sua Conta do Google.

2. Obtenha o código de exemplo

Baixe a versão inicial do seu projeto no GitHub:

  1. Na linha de comando, clone o repositório GitHub no diretório flutter-codelabs :
git clone https://github.com/flutter/codelabs.git flutter-codelabs

O diretório flutter-codelabs contém o código de uma coleção de codelabs. O código deste codelab está no diretório flutter-codelabs/firebase-get-to-know-flutter . O diretório contém uma série de instantâneos que mostram como seu projeto deve ficar ao final de cada etapa. Por exemplo, você está na segunda etapa.

  1. Encontre os arquivos correspondentes para a segunda etapa:
cd flutter-codelabs/firebase-get-to-know-flutter/step_02

Se você quiser avançar ou ver como algo deve ficar depois de uma etapa, procure no diretório nomeado após a etapa na qual você está interessado.

Importe o aplicativo inicial

  • Abra ou importe o diretório flutter-codelabs/firebase-get-to-know-flutter/step_02 em seu IDE preferido. Este diretório contém o código inicial do codelab, que consiste em um aplicativo Flutter Meetup ainda não funcional.

Localize os arquivos que precisam de trabalho

O código neste aplicativo está espalhado por vários diretórios. Essa divisão de funcionalidades facilita o trabalho porque agrupa o código por funcionalidade.

  • Localize os seguintes arquivos:
    • lib/main.dart : Este arquivo contém o ponto de entrada principal e o widget do aplicativo.
    • lib/home_page.dart : Este arquivo contém o widget da página inicial.
    • lib/src/widgets.dart : Este arquivo contém vários widgets para ajudar a padronizar o estilo do aplicativo. Eles compõem a tela do aplicativo inicial.
    • lib/src/authentication.dart : este arquivo contém uma implementação parcial de autenticação com um conjunto de widgets para criar uma experiência de usuário de login para autenticação baseada em email do Firebase. Esses widgets para o fluxo de autenticação ainda não são usados ​​no aplicativo inicial, mas você os adicionará em breve.

Você adiciona arquivos adicionais conforme necessário para criar o restante do aplicativo.

Revise o arquivo lib/main.dart

Este aplicativo aproveita o pacote google_fonts para tornar Roboto a fonte padrão em todo o aplicativo. Você pode explorar fonts.google.com e usar as fontes que encontrar lá em diferentes partes do aplicativo.

Você usa os widgets auxiliares do arquivo lib/src/widgets.dart na forma de Header , Paragraph e IconAndDetail . Esses widgets eliminam código duplicado para reduzir a confusão no layout da página descrito em HomePage . Isso também permite uma aparência consistente.

Esta é a aparência do seu aplicativo no Android, iOS, na Web e no macOS:

A tela inicial do aplicativo no Android

A tela inicial do aplicativo no iOS

A tela inicial do aplicativo na web

A tela inicial do aplicativo no macOS

3. Crie e configure um projeto Firebase

A exibição de informações do evento é ótima para seus convidados, mas não é muito útil para ninguém por si só. Você precisa adicionar alguma funcionalidade dinâmica ao aplicativo. Para fazer isso, você precisa conectar o Firebase ao seu aplicativo. Para começar a usar o Firebase, você precisa criar e configurar um projeto do Firebase.

Crie um projeto do Firebase

  1. Faça login no Firebase .
  2. No console, clique em Adicionar projeto ou Criar um projeto .
  3. No campo Nome do projeto , insira Firebase-Flutter-Codelab e clique em Continuar .

4395e4e67c08043a.png

  1. Clique nas opções de criação do projeto. Se solicitado, aceite os termos do Firebase, mas ignore a configuração do Google Analytics porque você não o usará neste aplicativo.

b7138cde5f2c7b61.png

Para saber mais sobre os projetos do Firebase, consulte Entender os projetos do Firebase .

O aplicativo usa os seguintes produtos Firebase, que estão disponíveis para aplicativos da web:

  • Autenticação: permite que os usuários façam login no seu aplicativo.
  • Firestore: salva dados estruturados na nuvem e recebe notificações instantâneas quando os dados mudam.
  • Regras de segurança do Firebase: protege seu banco de dados.

Alguns desses produtos precisam de configuração especial ou você precisa ativá-los no console do Firebase.

Habilitar autenticação de login por e-mail

  1. No painel Visão geral do projeto do console do Firebase, expanda o menu Construir .
  2. Clique em Autenticação > Primeiros passos > Método de login > Email/Senha > Habilitar > Salvar .

58e3e3e23c2f16a4.png

Ativar Firestore

O aplicativo da web usa o Firestore para salvar mensagens de bate-papo e receber novas mensagens de bate-papo.

Ative o Firestore:

  • No menu Construir , clique em Banco de dados Firestore > Criar banco de dados .

99e8429832d23fa3.png

  1. Selecione Iniciar em modo de teste e leia a isenção de responsabilidade sobre as regras de segurança. O modo de teste garante que você possa gravar livremente no banco de dados durante o desenvolvimento.

6be00e26c72ea032.png

  1. Clique em Avançar e selecione o local do seu banco de dados. Você pode usar o padrão. Você não pode alterar o local posteriormente.

278656eefcfb0216.png

  1. Clique em Habilitar .

4. Configurar o Firebase

Para usar o Firebase com Flutter, você precisa concluir as seguintes tarefas para configurar o projeto Flutter para usar as bibliotecas FlutterFire corretamente:

  1. Adicione as dependências FlutterFire ao seu projeto.
  2. Cadastre a plataforma desejada no projeto Firebase.
  3. Baixe o arquivo de configuração específico da plataforma e adicione-o ao código.

No diretório de nível superior do seu aplicativo Flutter, existem subdiretórios android , ios , macos e web , que contêm os arquivos de configuração específicos da plataforma para iOS e Android, respectivamente.

Configurar dependências

Você precisa adicionar as bibliotecas FlutterFire para os dois produtos Firebase usados ​​neste aplicativo: Authentication e Firestore.

  • Na linha de comando, adicione as seguintes dependências:
$ flutter pub add firebase_core

O pacote firebase_core é o código comum necessário para todos os plug-ins do Firebase Flutter.

$ flutter pub add firebase_auth

O pacote firebase_auth permite integração com autenticação.

$ flutter pub add cloud_firestore

O pacote cloud_firestore permite acesso ao armazenamento de dados do Firestore.

$ flutter pub add provider

O pacote firebase_ui_auth fornece um conjunto de widgets e utilitários para aumentar a velocidade do desenvolvedor com fluxos de autenticação.

$ flutter pub add firebase_ui_auth

Você adicionou os pacotes necessários, mas também precisa configurar os projetos iOS, Android, macOS e Web runner para usar o Firebase adequadamente. Você também usa o pacote provider que permite a separação da lógica de negócios da lógica de exibição.

Instale a CLI do FlutterFire

A CLI do FlutterFire depende da CLI do Firebase subjacente.

  1. Caso ainda não tenha feito isso, instale a CLI do Firebase em sua máquina.
  2. Instale a CLI do FlutterFire:
$ dart pub global activate flutterfire_cli

Uma vez instalado, o comando flutterfire está disponível globalmente.

Configure seus aplicativos

A CLI extrai informações do seu projeto do Firebase e dos aplicativos do projeto selecionados para gerar toda a configuração para uma plataforma específica.

Na raiz do seu aplicativo, execute o comando configure :

$ flutterfire configure

O comando de configuração orienta você pelos seguintes processos:

  1. Selecione um projeto do Firebase baseado no arquivo .firebaserc ou no Firebase Console.
  2. Determine plataformas para configuração, como Android, iOS, macOS e web.
  3. Identifique os aplicativos do Firebase dos quais extrair a configuração. Por padrão, a CLI tenta corresponder automaticamente os aplicativos do Firebase com base na configuração atual do projeto.
  4. Gere um arquivo firebase_options.dart em seu projeto.

Configurar macOS

O Flutter no macOS cria aplicativos totalmente em sandbox. Como este aplicativo se integra à rede para se comunicar com os servidores Firebase, você precisa configurar seu aplicativo com privilégios de cliente de rede.

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>

Para obter mais informações, consulte Suporte de desktop para Flutter .

5. Adicione funcionalidade RSVP

Agora que adicionou o Firebase ao aplicativo, você pode criar um botão RSVP que registra pessoas com Authentication . Para Android nativo, iOS nativo e Web, existem pacotes FirebaseUI Auth pré-construídos, mas você precisa criar esse recurso para Flutter.

O projeto recuperado anteriormente incluía um conjunto de widgets que implementa a interface do usuário para a maior parte do fluxo de autenticação. Você implementa a lógica de negócios para integrar a autenticação ao aplicativo.

Adicione lógica de negócios com o pacote Provider

Use o pacote provider para disponibilizar um objeto de estado do aplicativo centralizado em toda a árvore de widgets Flutter do aplicativo:

  1. Crie um novo arquivo chamado app_state.dart com o seguinte conteúdo:

lib/app_state.dart

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

import 'firebase_options.dart';

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

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

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

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

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

As instruções import apresentam o Firebase Core e o Auth, extraem o pacote provider que disponibiliza o objeto de estado do aplicativo em toda a árvore de widgets e incluem os widgets de autenticação do pacote firebase_ui_auth .

Este objeto de estado do aplicativo ApplicationState tem uma responsabilidade principal nesta etapa, que é alertar a árvore de widgets de que houve uma atualização para um estado autenticado.

Você só usa um provedor para comunicar o estado de login de um usuário ao aplicativo. Para permitir que um usuário faça login, use as UIs fornecidas pelo pacote firebase_ui_auth , que é uma ótima maneira de inicializar rapidamente as telas de login em seus aplicativos.

Integre o fluxo de autenticação

  1. Modifique as importações na parte superior do arquivo lib/main.dart :

lib/main.dart

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

import 'app_state.dart';                                 // new
import 'home_page.dart';
  1. Conecte o estado do aplicativo à inicialização do aplicativo e adicione o fluxo de autenticação a HomePage :

lib/main.dart

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

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

A modificação na função main() torna o pacote do provedor responsável pela instanciação do objeto de estado do aplicativo com o widget ChangeNotifierProvider . Você usa essa classe provider específica porque o objeto de estado do aplicativo estende a classe ChangeNotifier , que permite que o pacote provider saiba quando exibir novamente os widgets dependentes.

  1. Atualize seu aplicativo para lidar com a navegação em diferentes telas que o FirebaseUI fornece para você, criando uma configuração GoRouter :

lib/main.dart

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

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

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

Cada tela possui um tipo diferente de ação associada com base no novo estado do fluxo de autenticação. Após a maioria das alterações de estado na autenticação, você pode redirecionar de volta para uma tela preferida, seja a tela inicial ou uma tela diferente, como o perfil.

  1. No método de construção da classe HomePage , integre o estado do aplicativo ao widget AuthFunc :

lib/home_page.dart

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

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

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

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

Você instancia o widget AuthFunc e o agrupa em um widget Consumer . O widget Consumidor é a maneira usual pela qual o pacote provider pode ser usado para reconstruir parte da árvore quando o estado do aplicativo muda. O widget AuthFunc são os widgets complementares que você testa.

Teste o fluxo de autenticação

cdf2d25e436bd48d.png

  1. No aplicativo, toque no botão RSVP para iniciar o SignInScreen .

2a2cd6d69d172369.png

  1. Insira um endereço de e-mail. Se você já estiver registrado, o sistema solicitará que você insira uma senha. Caso contrário, o sistema solicitará que você preencha o formulário de registro.

e5e65065dba36b54.png

  1. Insira uma senha com menos de seis caracteres para verificar o fluxo de tratamento de erros. Se você estiver registrado, verá a senha.
  2. Insira senhas incorretas para verificar o fluxo de tratamento de erros.
  3. Digite a senha correta. Você vê a experiência de login, que oferece ao usuário a capacidade de sair.

4ed811a25b0cf816.png

6. Escreva mensagens para o Firestore

É ótimo saber que os usuários estão chegando, mas você precisa dar aos convidados outra coisa para fazer no aplicativo. E se eles pudessem deixar mensagens em um livro de visitas? Eles podem compartilhar por que estão entusiasmados em vir ou quem esperam conhecer.

Para armazenar as mensagens de chat que os usuários escrevem no aplicativo, você usa o Firestore .

Modelo de dados

Firestore é um banco de dados NoSQL e os dados armazenados no banco de dados são divididos em coleções, documentos, campos e subcoleções. Você armazena cada mensagem do chat como um documento em uma coleção guestbook , que é uma coleção de nível superior.

7c20dc8424bb1d84.png

Adicionar mensagens ao Firestore

Nesta seção, você adiciona a funcionalidade para que os usuários gravem mensagens no banco de dados. Primeiro, você adiciona um campo de formulário e um botão de envio e, em seguida, adiciona o código que conecta esses elementos ao banco de dados.

  1. Crie um novo arquivo chamado guest_book.dart , adicione um widget com estado GuestBook para construir os elementos da interface do usuário de um campo de mensagem e um botão de envio:

lib/livro_convidados.dart

import 'dart:async';

import 'package:flutter/material.dart';

import 'src/widgets.dart';

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

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

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

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

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

Existem alguns pontos de interesse aqui. Primeiro, você instancia um formulário para poder validar se a mensagem realmente contém conteúdo e mostrar ao usuário uma mensagem de erro, se não houver nenhuma. Para validar um formulário, você acessa o estado do formulário por trás do formulário com um GlobalKey . Para obter mais informações sobre chaves e como usá-las, consulte Quando usar chaves .

Observe também a forma como os widgets são dispostos, você tem um Row com um TextFormField e um StyledButton , que contém um Row . Observe também que TextFormField está encapsulado em um widget Expanded , o que força o TextFormField a preencher qualquer espaço extra na linha. Para entender melhor por que isso é necessário, consulte Noções básicas sobre restrições .

Agora que você tem um widget que permite ao usuário inserir algum texto para adicionar ao Livro de Visitas, você precisa colocá-lo na tela.

  1. Edite o corpo de HomePage para adicionar as duas linhas a seguir no final dos filhos do 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)),

Embora isso seja suficiente para exibir o widget, não é suficiente para fazer algo útil. Você atualiza esse código em breve para torná-lo funcional.

Visualização do aplicativo

A tela inicial do aplicativo no Android com integração de chat

A tela inicial do aplicativo no iOS com integração de chat

A tela inicial do aplicativo na web com integração de chat

A tela inicial do aplicativo no macOS com integração de chat

Quando um usuário clica em ENVIAR , o seguinte snippet de código é acionado. Ele adiciona o conteúdo do campo de entrada da mensagem à coleção de guestbook do banco de dados. Especificamente, o método addMessageToGuestBook adiciona o conteúdo da mensagem a um novo documento com um ID gerado automaticamente na coleção guestbook .

Observe que FirebaseAuth.instance.currentUser.uid é uma referência ao ID exclusivo gerado automaticamente que o Authentication fornece para todos os usuários logados.

  • No arquivo lib/app_state.dart , adicione o método addMessageToGuestBook . Você conecta esse recurso à interface do usuário na próxima etapa.

lib/app_state.dart

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

import 'firebase_options.dart';

class ApplicationState extends ChangeNotifier {

  // Current content of ApplicationState elided ...

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

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

Conecte UI e banco de dados

Você tem uma IU onde o usuário pode inserir o texto que deseja adicionar ao livro de visitas e tem o código para adicionar a entrada ao Firestore. Agora tudo que você precisa fazer é conectar os dois.

  • No arquivo lib/home_page.dart , faça a seguinte alteração no widget HomePage :

lib/home_page.dart

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

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

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

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

Você substituiu as duas linhas adicionadas no início desta etapa pela implementação completa. Você usa novamente Consumer<ApplicationState> para disponibilizar o estado do aplicativo para a parte da árvore que você renderiza. Isso permite reagir a alguém que insere uma mensagem na IU e publicá-la no banco de dados. Na próxima seção, você testará se as mensagens adicionadas serão publicadas no banco de dados.

Teste o envio de mensagens

  1. Se necessário, faça login no aplicativo.
  2. Digite uma mensagem, como Hey there! e clique em ENVIAR .

Esta ação grava a mensagem no banco de dados do Firestore. No entanto, você não vê a mensagem em seu aplicativo Flutter real porque ainda precisa implementar a recuperação dos dados, o que será feito na próxima etapa. No entanto, no painel do banco de dados do console do Firebase, você pode ver sua mensagem adicionada na coleção guestbook . Se você enviar mais mensagens, adicionará mais documentos à sua coleção de guestbook . Por exemplo, veja o seguinte trecho de código:

713870af0b3b63c.png

7. Leia mensagens

É ótimo que os convidados possam escrever mensagens no banco de dados, mas ainda não possam vê-las no aplicativo. É hora de consertar isso!

Sincronizar mensagens

Para exibir mensagens, você precisa adicionar ouvintes que são acionados quando os dados são alterados e, em seguida, criar um elemento de UI que mostre novas mensagens. Você adiciona código ao estado do aplicativo que escuta mensagens recém-adicionadas do aplicativo.

  1. Crie um novo arquivo guest_book_message.dart e adicione a classe a seguir para expor uma visualização estruturada dos dados que você armazena no Firestore.

lib/guest_book_message.dart

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

  final String name;
  final String message;
}
  1. No arquivo lib/app_state.dart , adicione as seguintes importações:

lib/app_state.dart

import 'dart:async';                                     // new

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

import 'firebase_options.dart';
import 'guest_book_message.dart';                        // new
  1. Na seção ApplicationState onde você define estado e getters, adicione as seguintes linhas:

lib/app_state.dart

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

  // Add from here...
  StreamSubscription<QuerySnapshot>? _guestBookSubscription;
  List<GuestBookMessage> _guestBookMessages = [];
  List<GuestBookMessage> get guestBookMessages => _guestBookMessages;
  // ...to here.
  1. Na seção de inicialização de ApplicationState , adicione as seguintes linhas para assinar uma consulta na coleção de documentos quando um usuário fizer login e cancelar a assinatura quando fizer logout:

lib/app_state.dart

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

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

Esta seção é importante porque é onde você constrói uma consulta sobre a coleção de guestbook e trata da inscrição e cancelamento da assinatura dessa coleção. Você ouve o stream, onde reconstrói um cache local das mensagens na coleção guestbook e também armazena uma referência a essa assinatura para que possa cancelar a assinatura mais tarde. Há muita coisa acontecendo aqui, então você deve explorá-la em um depurador para inspecionar o que acontece para obter um modelo mental mais claro. Para obter mais informações, consulte Obtenha atualizações em tempo real com o Firestore .

  1. No arquivo lib/guest_book.dart , adicione a seguinte importação:
import 'guest_book_message.dart';
  1. No widget GuestBook , adicione uma lista de mensagens como parte da configuração para conectar esse estado de mudança à interface do usuário:

lib/livro_convidados.dart

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

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

  @override
  _GuestBookState createState() => _GuestBookState();
}
  1. Em _GuestBookState , modifique o método build da seguinte forma para expor esta configuração:

lib/livro_convidados.dart

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

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

Você envolve o conteúdo anterior do método build() com um widget Column e, em seguida, adiciona uma coleção no final dos filhos de Column para gerar um novo Paragraph para cada mensagem na lista de mensagens.

  1. Atualize o corpo da HomePage para construir corretamente GuestBook com o novo parâmetro messages :

lib/home_page.dart

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

Testar sincronização de mensagens

O Firestore sincroniza dados de forma automática e instantânea com clientes inscritos no banco de dados.

Teste a sincronização de mensagens:

  1. No aplicativo, encontre as mensagens que você criou anteriormente no banco de dados.
  2. Escreva novas mensagens. Eles aparecem instantaneamente.
  3. Abra seu espaço de trabalho em várias janelas ou guias. As mensagens são sincronizadas em tempo real nas janelas e guias.
  4. Opcional: no menu Banco de dados do Firebase Console, exclua, modifique ou adicione novas mensagens manualmente. Todas as alterações aparecem na IU.

Parabéns! Você lê documentos do Firestore em seu aplicativo!

Visualização do aplicativo

A tela inicial do aplicativo no Android com integração de chat

A tela inicial do aplicativo no iOS com integração de chat

A tela inicial do aplicativo na web com integração de chat

A tela inicial do aplicativo no macOS com integração de chat

8. Configure regras básicas de segurança

Inicialmente, você configurou o Firestore para usar o modo de teste, o que significa que seu banco de dados está aberto para leituras e gravações. No entanto, você só deve usar o modo de teste durante os estágios iniciais de desenvolvimento. Como prática recomendada, você deve configurar regras de segurança para seu banco de dados à medida que desenvolve seu aplicativo. A segurança é parte integrante da estrutura e do comportamento do seu aplicativo.

As regras de segurança do Firebase permitem controlar o acesso a documentos e coleções em seu banco de dados. A sintaxe de regras flexível permite criar regras que correspondem a qualquer coisa, desde todas as gravações no banco de dados inteiro até operações em um documento específico.

Configure regras básicas de segurança:

  1. No menu Desenvolvedor do console do Firebase, clique em Banco de dados > Regras . Você deverá ver as seguintes regras de segurança padrão e um aviso sobre as regras serem públicas:

7767a2d2e64e7275.png

  1. Identifique as coleções nas quais o aplicativo grava dados:

Em match /databases/{database}/documents , identifique a coleção que você deseja proteger:

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

Como você usou o UID de autenticação como um campo em cada documento do livro de visitas, você pode obter o UID de autenticação e verificar se qualquer pessoa que tentar gravar no documento possui um UID de autenticação correspondente.

  1. Adicione as regras de leitura e gravação ao seu conjunto de regras:
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;
    }
  }
}

Agora, apenas usuários conectados podem ler mensagens no livro de visitas, mas somente o autor de uma mensagem pode editá-la.

  1. Adicione validação de dados para garantir que todos os campos esperados estejam presentes no 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. Etapa bônus: pratique o que você aprendeu

Registre o status de confirmação de presença de um participante

No momento, seu aplicativo só permite que as pessoas conversem quando estiverem interessadas no evento. Além disso, a única maneira de saber se alguém está vindo é quando ele avisa no chat.

Nesta etapa, você se organiza e informa quantas pessoas estão chegando. Você adiciona alguns recursos ao estado do aplicativo. A primeira é a capacidade de um usuário logado indicar se está participando. O segundo é um contador de quantas pessoas estão participando.

  1. No arquivo lib/app_state.dart , adicione as seguintes linhas à seção de acessadores do ApplicationState para que o código da UI possa interagir com esse estado:

lib/app_state.dart

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

Attending _attending = Attending.unknown;
StreamSubscription<DocumentSnapshot>? _attendingSubscription;
Attending get attending => _attending;
set attending(Attending attending) {
  final userDoc = FirebaseFirestore.instance
      .collection('attendees')
      .doc(FirebaseAuth.instance.currentUser!.uid);
  if (attending == Attending.yes) {
    userDoc.set(<String, dynamic>{'attending': true});
  } else {
    userDoc.set(<String, dynamic>{'attending': false});
  }
}
  1. Atualize o método init() do ApplicationState da seguinte forma:

lib/app_state.dart

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

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

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

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

Este código adiciona uma consulta sempre inscrita para determinar o número de participantes e uma segunda consulta que só fica ativa enquanto um usuário está conectado para determinar se o usuário está participando.

  1. Adicione a seguinte enumeração na parte superior do arquivo lib/app_state.dart .

lib/app_state.dart

enum Attending { yes, no, unknown }
  1. Crie um novo arquivo yes_no_selection.dart , defina um novo widget que atue como botões de opção:

lib/yes_no_selection.dart

import 'package:flutter/material.dart';

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

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

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

Ele começa em um estado indeterminado sem Sim nem Não selecionado. Depois que o usuário seleciona se vai participar, você mostra essa opção destacada com um botão preenchido e a outra opção recua com uma renderização plana.

  1. Atualize o método build() do HomePage para aproveitar YesNoSelection , permitir que um usuário logado indique se está participando e exibir o número de participantes do evento:

lib/home_page.dart

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

Adicionar regras

Você já configurou algumas regras, então os dados que você adicionar com os botões serão rejeitados. Você precisa atualizar as regras para permitir acréscimos à coleção attendees .

  1. Na coleção attendees , pegue o UID de autenticação que você usou como nome do documento e verifique se o uid do remetente é o mesmo do documento que ele está escrevendo:
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    // ... //
    match /attendees/{userId} {
      allow read: if true;
      allow write: if request.auth.uid == userId;
    }
  }
}

Isso permite que todos leiam a lista de participantes porque não há dados privados nela, mas apenas o criador pode atualizá-la.

  1. Adicione validação de dados para garantir que todos os campos esperados estejam presentes no documento:
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    // ... //
    match /attendees/{userId} {
      allow read: if true;
      allow write: if request.auth.uid == userId
          && "attending" in request.resource.data;

    }
  }
}
  1. Opcional: no aplicativo, clique nos botões para ver os resultados no painel do Firestore no console do Firebase.

Visualização do aplicativo

A tela inicial do aplicativo no Android

A tela inicial do aplicativo no iOS

A tela inicial do aplicativo na web

A tela inicial do aplicativo no macOS

10. Parabéns!

Você usou o Firebase para criar um aplicativo da Web interativo e em tempo real.

Saber mais