Conheça o Firebase para Flutter

1. Antes de começar

Neste codelab, você vai aprender alguns dos princípios básicos de Firebase para criar aplicativos móveis da vibração para Android e iOS.

Pré-requisitos

Este codelab pressupõe que está familiarizado com vibração, e você tiver instalado o Flutter SDK , e um editor .

O que você vai criar

Neste codelab, você criará um RSVP de evento e um aplicativo de bate-papo do livro de visitas no Android, iOS, Web e macOS usando o Flutter. Você autenticará os usuários com o Firebase Authentication e sincronizará os dados usando o Cloud Firestore.

O que você precisará

Você pode executar este codelab usando qualquer um dos seguintes dispositivos:

Além dos itens acima, você também precisará de:

  • Um navegador de sua escolha, como o Chrome.
  • Um IDE ou editor de texto de sua escolha, tais como Android estúdio ou Código VS configurado com os plugins de dardo e vibração.
  • O mais recente stable versão do Flutter (ou beta se você gosta de viver no limite).
  • Uma conta do Google, como uma conta do Gmail, para criar e gerenciar seu projeto do Firebase.
  • O firebase ferramenta de linha de comando , logado em sua conta do Gmail.
  • O código de amostra do codelab. Veja a próxima etapa para saber como obter o código.

2. Obtenha o código de amostra

Vamos começar baixando a versão inicial do nosso projeto do GitHub.

Clonar o repositório GitHub a partir da linha de comando:

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

Alternativamente, se você tem cli do GitHub ferramenta instalada:

gh repo clone flutter/codelabs flutter-codelabs

O código de amostra deve ser clonado no flutter-codelabs directório, o qual contém o código de um conjunto de codelabs. O código para esta codelab está em flutter-codelabs/firebase-get-to-know-flutter .

A estrutura de diretório sob flutter-codelabs/firebase-get-to-know-flutter é uma série de instantâneos de onde você deve ser no final de cada etapa nomeado. Esta é a Etapa 2, portanto, localizar os arquivos correspondentes é tão fácil quanto:

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

Se você quiser avançar ou ver como algo deve ficar após uma etapa, procure no diretório com o nome da etapa em que você está interessado.

Importar o aplicativo inicial

Abrir ou importar os flutter-codelabs/firebase-get-to-know-flutter/step_02 diretório em seu IDE preferido. Este diretório contém o código inicial para o codelab, que consiste em um aplicativo de encontro Flutter ainda não funcional.

Localize os arquivos para trabalhar

O código neste aplicativo está espalhado por vários diretórios. Essa divisão de funcionalidade foi projetada para facilitar o trabalho, agrupando o código por funcionalidade.

Localize os seguintes arquivos no projeto:

  • lib/main.dart : Este arquivo contém o principal ponto de entrada eo widget do aplicativo.
  • lib/src/widgets.dart : Este arquivo contém um punhado de widgets para ajudar a padronizar o estilo da aplicação. Estes são usados ​​para compor a tela do app inicial.
  • lib/src/authentication.dart : Este arquivo contém uma implementação parcial do FirebaseUI Auth com um conjunto de widgets para criar uma experiência de usuário de login para autenticação baseada Firebase e-mail. Esses widgets para o fluxo de autenticação ainda não são usados ​​no aplicativo inicial, mas você os conectará em breve.

Você adicionará arquivos adicionais conforme necessário para construir o restante do aplicativo.

Revendo o lib/main.dart arquivo

Este aplicativo aproveita a google_fonts pacote para nos permitir fazer Roboto a fonte padrão em todo o aplicativo. Um exercício para o leitor motivado é explorar fonts.google.com e usar as fontes que você descobrir que existe em diferentes partes do aplicativo.

Você está utilizando os widgets auxiliares de lib/src/widgets.dart na forma de Header , Paragraph e IconAndDetail . Esses widgets reduzir a desordem no layout da página descrita em HomePage , eliminando código duplicado. Isso tem o benefício adicional de permitir uma aparência consistente.

Aqui está a aparência do seu aplicativo no Android, iOS, Web e macOS:

Visualização do aplicativo

3. Crie e configure um projeto do Firebase

Exibir as informações do evento é ótimo para seus convidados, mas apenas mostrar os eventos não é muito útil para ninguém. Vamos adicionar algumas funcionalidades dinâmicas a este aplicativo. Para isso, você precisará conectar o Firebase ao seu aplicativo. Para começar a usar o Firebase, você precisará criar e configurar um projeto do Firebase.

Criar um projeto do Firebase

  1. Entrar no Firebase .
  2. No console Firebase, clique em Add Project (ou Criar um projeto), eo nome do seu Firebase projeto Firebase-Flutter-Codelab.

4395e4e67c08043a.png

  1. Clique nas opções de criação do projeto. Aceite os termos do Firebase, se solicitado. Ignore a configuração do Google Analytics, pois você não usará o Analytics para este aplicativo.

b7138cde5f2c7b61.png

Para saber mais sobre os projetos Firebase, consulte Compreender projectos Firebase .

O aplicativo que você está criando usa vários produtos do Firebase disponíveis para aplicativos da Web:

  • Firebase autenticação para permitir que seus usuários para fazer login no seu aplicativo.
  • Nuvem Firestore para salvar dados estruturados na nuvem e obter notificação instantânea quando alterações de dados.
  • Regras de Segurança Firebase para garantir a sua base de dados.

Alguns desses produtos precisam de configuração especial ou precisam ser ativados usando o Firebase console.

Ativar e-mail de início de sessão para autenticação Firebase

Para permitir que os usuários assinem na aplicação web, você vai usar o sign-in E-mail / senha método para este codelab:

  1. No console Firebase, expanda o menu Build no painel esquerdo.
  2. Clique em Autenticação e clique no botão começar, em seguida, a guia método Sign-in (ou clique aqui para ir diretamente para o Sign-in guia método).
  3. Clique E-mail / senha na lista de Sign-in fornecedores, coloque o interruptor para a posição Ativar, e depois clique em Salvar. 58e3e3e23c2f16a4.png

Ativar o Cloud Firestore

O aplicativo da Web usa Nuvem Firestore para guardar as mensagens de bate-papo e receber novas mensagens de chat.

Ative o Cloud Firestore:

  1. Na seção de construção do o Firebase console, clique Nuvem Firestore.
  2. Clique em Criar banco de dados. 99e8429832d23fa3.png
  1. Selecione a opção Iniciar o modo de teste no. Leia o aviso sobre as regras de segurança. O modo de teste garante que você possa gravar livremente no banco de dados durante o desenvolvimento. Clique em Avançar. 6be00e26c72ea032.png
  1. Selecione o local para seu banco de dados (você pode usar apenas o padrão). Observe que esse local não pode ser alterado posteriormente. 278656eefcfb0216.png
  2. Clique em Ativar.

4. Configuração do Firebase

Para usar o Firebase com Flutter, você precisa seguir um processo para configurar o projeto Flutter para utilizar as bibliotecas FlutterFire corretamente:

  • Adicione as dependências do FlutterFire ao seu projeto
  • Registre a plataforma desejada no projeto Firebase
  • 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 chamados android , ios , macos e web . Esses diretórios 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 que você está utilizando neste aplicativo - Firebase Auth e Cloud Firestore. Execute os três comandos a seguir para adicionar as dependências.

$ flutter pub add firebase_core 
Resolving dependencies...
+ firebase_core 1.10.5
+ firebase_core_platform_interface 4.2.2
+ firebase_core_web 1.5.2
+ flutter_web_plugins 0.0.0 from sdk flutter
+ js 0.6.3
  test_api 0.4.3 (0.4.8 available)
Changed 5 dependencies!

O firebase_core é o código comum necessária para todos os plugins Firebase vibração.

$ flutter pub add firebase_auth
Resolving dependencies...
+ firebase_auth 3.3.3
+ firebase_auth_platform_interface 6.1.8
+ firebase_auth_web 3.3.4
+ intl 0.17.0
  test_api 0.4.3 (0.4.8 available)
Changed 4 dependencies!

O firebase_auth permite a integração com capacidade de autenticação de Firebase.

$ flutter pub add cloud_firestore
Resolving dependencies...
+ cloud_firestore 3.1.4
+ cloud_firestore_platform_interface 5.4.9
+ cloud_firestore_web 2.6.4
  test_api 0.4.3 (0.4.8 available)
Changed 3 dependencies!

O cloud_firestore permite o acesso ao armazenamento de dados em nuvem Firestore.

$ flutter pub add provider
Resolving dependencies...
+ nested 1.0.0
+ provider 6.0.1
  test_api 0.4.3 (0.4.8 available)
Changed 2 dependencies!

Embora tenha adicionado os pacotes necessários, você também precisa configurar os projetos iOS, Android, macOS e Web runner para utilizar o Firebase de forma adequada. Você também está usando o provider pacote que irá permitir a separação da lógica de negócios da lógica de exibição.

Instalando flutterfire

  1. A CLI do FlutterFire depende da CLI do Firebase subjacente. Se você não tiver feito isso, instalar ou atualizar para a versão mais recente do Firebase CLI .
  2. Em seguida, instalar o FlutterFire CLI executando o seguinte comando:
    $ dart pub global activate flutterfire_cli
    

Após a instalação, o flutterfire comando será globalmente disponível.

Configurando seus aplicativos

A CLI extrai informações do seu projeto do Firebase e aplicativos de 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 o guiará por vários processos:

  1. Seleção de um projeto do Firebase (com base no arquivo .firebaserc ou no Firebase Console).
  2. Pergunta para quais plataformas (por exemplo, Android, iOS, macOS e web) você deseja configurar.
  3. Identifique quais aplicativos do Firebase para as plataformas escolhidas devem ser usados ​​para extrair a configuração. Por padrão, a CLI tentará corresponder automaticamente aos aplicativos do Firebase com base na configuração atual do seu projeto.
  4. Gere um arquivo firebase_options.dart em seu projeto.

Configurar o macOS

O Flutter no macOS cria aplicativos totalmente em área restrita. Como este aplicativo está se integrando usando a rede para se comunicar com os servidores Firebase, você precisará 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>

Veja Direitos ea App Sandbox para mais detalhes.

5. Adicionar login do usuário (RSVP)

Agora que você adicionou Firebase para o aplicativo, você pode configurar um botão de RSVP que registra pessoas que usam autenticação Firebase . Para Android nativo, iOS nativo e Web, há pacotes FirebaseUI Auth pré-criados, mas para Flutter você precisará criar esse recurso.

O projeto que você recuperou na Etapa 2 incluiu um conjunto de widgets que implementa a interface do usuário para a maior parte do fluxo de autenticação. Você implementará a lógica de negócios para integrar o Firebase Authentication ao aplicativo.

Lógica de Negócios com Provedor

Você está indo para usar o provider pacote para fazer um objeto de estado aplicação centralizada disponível em toda a árvore de widgets da vibração do aplicativo. Para começar, modificar as importações no topo da lib/main.dart :

lib/main.dart

import 'package:firebase_auth/firebase_auth.dart'; // new
import 'package:firebase_core/firebase_core.dart'; // new
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:provider/provider.dart';           // new

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

Os import linhas introduzir Firebase Core e Auth, tração no provider pacote que você está usando para fazer o objeto de estado de aplicação disponível através da árvore widget, e incluir os widgets de autenticação de lib/src .

Este estado de objeto de aplicação, ApplicationState , tem duas responsabilidades principais para esta etapa, mas vai ganhar responsabilidades adicionais como você adicionar mais recursos para a aplicação em etapas posteriores. A primeira responsabilidade é inicializar a biblioteca Firebase com uma chamada para Firebase.initializeApp() , e depois há a manipulação do fluxo de autorização. Adicionar a seguinte classe ao final do lib/main.dart :

lib/main.dart

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

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

    FirebaseAuth.instance.userChanges().listen((user) {
      if (user != null) {
        _loginState = ApplicationLoginState.loggedIn;
      } else {
        _loginState = ApplicationLoginState.loggedOut;
      }
      notifyListeners();
    });
  }

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

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

  void startLoginFlow() {
    _loginState = ApplicationLoginState.emailAddress;
    notifyListeners();
  }

  Future<void> verifyEmail(
    String email,
    void Function(FirebaseAuthException e) errorCallback,
  ) async {
    try {
      var methods =
          await FirebaseAuth.instance.fetchSignInMethodsForEmail(email);
      if (methods.contains('password')) {
        _loginState = ApplicationLoginState.password;
      } else {
        _loginState = ApplicationLoginState.register;
      }
      _email = email;
      notifyListeners();
    } on FirebaseAuthException catch (e) {
      errorCallback(e);
    }
  }

  Future<void> signInWithEmailAndPassword(
    String email,
    String password,
    void Function(FirebaseAuthException e) errorCallback,
  ) async {
    try {
      await FirebaseAuth.instance.signInWithEmailAndPassword(
        email: email,
        password: password,
      );
    } on FirebaseAuthException catch (e) {
      errorCallback(e);
    }
  }

  void cancelRegistration() {
    _loginState = ApplicationLoginState.emailAddress;
    notifyListeners();
  }

  Future<void> registerAccount(
      String email,
      String displayName,
      String password,
      void Function(FirebaseAuthException e) errorCallback) async {
    try {
      var credential = await FirebaseAuth.instance
          .createUserWithEmailAndPassword(email: email, password: password);
      await credential.user!.updateDisplayName(displayName);
    } on FirebaseAuthException catch (e) {
      errorCallback(e);
    }
  }

  void signOut() {
    FirebaseAuth.instance.signOut();
  }
}

Vale a pena observar alguns pontos-chave nesta aula. O usuário inicia não autenticado, o aplicativo mostra um formulário solicitando o endereço de e-mail do usuário, dependendo se esse endereço de e-mail estiver registrado, o aplicativo solicitará o registro do usuário ou solicitará sua senha e, supondo que tudo dê certo, o usuário é autenticado.

Deve-se observar que esta não é uma implementação completa do fluxo de autenticação do FirebaseUI, pois não trata o caso de um usuário com uma conta existente que está tendo problemas para fazer login. A implementação desse recurso adicional é deixada como exercício para o leitor motivado.

Integrando o fluxo de autenticação

Agora que você tem o início do estado do aplicativo é hora de ligar o estado do aplicativo na inicialização do aplicativo e adicionar o fluxo de autenticação em HomePage . Atualize o ponto de entrada principal para integrar o estado do aplicativo através do provider pacote:

lib/main.dart

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

A modificação para o main função faz com que o pacote de provedor responsável por instanciar o objeto de estado aplicativo usando o ChangeNotifierProvider widget. Você está usando esta classe de provedor específico porque o objeto de estado aplicação estende ChangeNotifier e isso permite que o provider pacote para saber quando para exibir novamente os widgets dependentes. Finalmente, integrar o estado da aplicação com Authentication por meio da atualização HomePage s' build método:

lib/main.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'),
          // Add from here
          Consumer<ApplicationState>(
            builder: (context, appState, _) => Authentication(
              email: appState.email,
              loginState: appState.loginState,
              startLoginFlow: appState.startLoginFlow,
              verifyEmail: appState.verifyEmail,
              signInWithEmailAndPassword: appState.signInWithEmailAndPassword,
              cancelRegistration: appState.cancelRegistration,
              registerAccount: appState.registerAccount,
              signOut: appState.signOut,
            ),
          ),
          // to here
          const Divider(
            height: 8,
            thickness: 1,
            indent: 8,
            endIndent: 8,
            color: Colors.grey,
          ),
          const Header("What we'll be doing"),
          const Paragraph(
            'Join us for a day full of Firebase Workshops and Pizza!',
          ),
        ],
      ),
    );
  }
}

Você instanciar a Authentication widget, e envolvê-la em um Consumer widget. O Consumer widgets da maneira usual que o provider pacote pode ser usado para reconstruir parte da árvore quando o estado de aplicação mudanças. A Authentication widget é a interface do usuário de autenticação que você irá agora teste.

Testando o fluxo de autenticação

cdf2d25e436bd48d.png

Aqui está o início do fluxo de autenticação, onde o usuário pode tocar no botão RSVP, para iniciar o formulário de e-mail.

2a2cd6d69d172369.png

Ao inserir o e-mail, o sistema confirma se o usuário já está cadastrado, neste caso é solicitada uma senha ao usuário, ou caso o usuário não esteja cadastrado, então ele passa pelo formulário de cadastro.

e5e65065dba36b54.png

Certifique-se de tentar inserir uma senha curta (menos de seis caracteres) para verificar o fluxo de tratamento de erros. Se o usuário estiver registrado, ele verá a senha.

fbb3ea35fb4f67a.png

Nesta página, certifique-se de inserir senhas incorretas para verificar o tratamento de erros nesta página. Finalmente, quando o usuário estiver logado, você verá a experiência de login que oferece ao usuário a capacidade de sair novamente.

4ed811a25b0cf816.png

E com isso, você implementou um fluxo de autenticação. Parabéns!

6. Gravar mensagens no Cloud Firestore

Saber que os usuários estão chegando é ótimo, mas vamos dar aos convidados algo mais para fazer no aplicativo. E se eles pudessem deixar mensagens em um livro de visitas? Eles podem compartilhar por que estão animados para vir ou quem esperam conhecer.

Para armazenar as mensagens de bate-papo que os usuários escrevem no app, você vai usar Nuvem Firestore .

Modelo de dados

O Cloud 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ê irá armazenar cada mensagem do chat como um documento em uma coleção de nível superior chamado guestbook .

7c20dc8424bb1d84.png

Adicionar mensagens ao Firestore

Nesta seção, você adicionará a funcionalidade para que os usuários escrevam novas mensagens no banco de dados. Primeiro, você adiciona os elementos da interface do usuário (campo de formulário e botão de envio) e, em seguida, adiciona o código que conecta esses elementos ao banco de dados.

Primeiro, adicione as importações para a cloud_firestore pacote e dart:async .

lib/main.dart

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 os elementos de interface do usuário de um campo de mensagem e um botão de envio, adicionar um novo stateful Widget GuestBook na parte inferior da lib/main.dart .

lib/main.dart

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

  @override
  _GuestBookState createState() => _GuestBookState();
}

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

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

Há alguns pontos de interesse aqui. Primeiro, você está instanciando um Form para que o upi possa validar se a mensagem realmente tem algum conteúdo e mostrar ao usuário uma mensagem de erro se não houver. A maneira de validar um formulário envolve acessando o estado forma por trás da forma, e para isso você usar um GlobalKey . Para mais informações sobre Keys, e como usá-los, consulte o Flutter Widgets 101 episódio "Quando usar as teclas" .

Além disso, observe a maneira como os widgets são colocados para fora, você tem uma Row , com uma TextFormField e uma StyledButton , que em si contém uma Row . Observe também o TextFormField é envolto em um Expanded widget, isso força o TextFormField para ocupar qualquer espaço extra na linha. Para entender melhor por que isso é necessário, por favor leia as restrições Compreensão .

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. Para fazer isso, editar o corpo de HomePage para adicionar as duas linhas seguintes na parte inferior do ListView crianças 's:

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

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

Visualização do aplicativo

Um usuário clicar no botão ENVIAR irá acionar o trecho de código abaixo. Acrescenta o conteúdo do campo de entrada de mensagem para o guestbook coleção de banco de dados. Especificamente, o addMessageToGuestBook método adiciona o conteúdo da mensagem para um novo documento (com um ID gerado automaticamente) ao guestbook coleção.

Note-se que FirebaseAuth.instance.currentUser.uid é uma referência para a identificação única gerada automaticamente que a autenticação Firebase dá para todos os usuários conectados.

Fazer outra alteração para o lib/main.dart arquivo. Adicione o addMessageToGuestBook método. Você conectará a interface do usuário e esse recurso na próxima etapa.

lib/main.dart

class ApplicationState extends ChangeNotifier {

  // Current content of ApplicationState elided ...

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

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

Conectando a interface do usuário ao banco de dados

Você tem uma IU em que o usuário pode inserir o texto que deseja adicionar ao Livro de visitas e tem o código para adicionar a entrada ao Cloud Firestore. Agora tudo que você precisa fazer é conectar os dois juntos. Em lib/main.dart faça a seguinte alteração à HomePage widget.

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

Você substituiu as duas linhas que adicionou no início desta etapa pela implementação completa. Você está novamente usando Consumer<ApplicationState> para tornar o estado do aplicativo disponível para a parte da árvore que você está prestando. Isso permite que você reaja a alguém inserindo uma mensagem na interface do usuário e a publique no banco de dados. Na próxima seção você testará se as mensagens adicionadas são publicadas no banco de dados.

Teste o envio de mensagens

  1. Verifique se você está conectado ao aplicativo.
  2. Digite uma mensagem como "Hey lá!", E clique em Enviar.

Esta ação grava a mensagem no banco de dados do Cloud Firestore. No entanto, você ainda não verá a mensagem em seu aplicativo Flutter real porque ainda precisa implementar a recuperação dos dados. Você fará isso na próxima etapa.

Mas você pode ver a mensagem recém-adicionada no console do Firebase.

No console Firebase, no painel de banco de dados , você deve ver o guestbook coleção com a sua mensagem recém-adicionado. Se você continuar enviando mensagens, sua coleção de livros de visitas conterá muitos documentos, como este:

Console do Firebase

713870af0b3b63c.png

7. Leia as mensagens

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

Sincronizar mensagens

Para exibir mensagens, você precisará adicionar ouvintes que são acionados quando os dados são alterados e, em seguida, criar um elemento de interface do usuário que mostra novas mensagens. Você adicionará código ao estado do aplicativo que escuta as mensagens recém-adicionadas do aplicativo.

Logo acima da GuestBook widgets a seguinte classe de valor. Esta classe expõe uma visualização estruturada dos dados que você está armazenando no Cloud Firestore.

lib/main.dart

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

Na seção de ApplicationState onde você define Estado e getters, adicione as seguintes novas linhas:

lib/main.dart

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

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

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

E, finalmente, na seção de inicialização de ApplicationState , adicione o seguinte para inscrever-se a uma consulta sobre a coleção de documentos quando um usuário se conecta, e unsubscribe quando eles log out.

lib/main.dart

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

    FirebaseAuth.instance.userChanges().listen((user) {
      if (user != null) {
        _loginState = ApplicationLoginState.loggedIn;
        // Add from here
        _guestBookSubscription = FirebaseFirestore.instance
            .collection('guestbook')
            .orderBy('timestamp', descending: true)
            .snapshots()
            .listen((snapshot) {
          _guestBookMessages = [];
          for (final document in snapshot.docs) {
            _guestBookMessages.add(
              GuestBookMessage(
                name: document.data()['name'] as String,
                message: document.data()['text'] as String,
              ),
            );
          }
          notifyListeners();
        });
        // to here.
      } else {
        _loginState = ApplicationLoginState.loggedOut;
        // Add from here
        _guestBookMessages = [];
        _guestBookSubscription?.cancel();
        // to here.
      }
      notifyListeners();
    });
  }

Esta seção é importante, porque aqui é onde você construir uma consulta sobre o guestbook coleção, e pega em subscrever e cancelar a esta coleção. Você ouvir o riacho, onde você reconstruir uma cache local das mensagens no guestbook coleção, e também armazenar uma referência a essa assinatura para que você pode cancelar sua inscrição a partir dele mais tarde. Há muita coisa acontecendo aqui, e vale a pena gastar algum tempo em um depurador inspecionando o que acontece quando se obtém um modelo mental mais claro.

Para mais informações, consulte a documentação Nuvem Firestore .

No GuestBook Widget você precisa conectar esse estado mudar para a interface do usuário. Você modifica o widget adicionando uma lista de mensagens como parte de sua configuração.

lib/main.dart

class GuestBook extends StatefulWidget {
  // Modify the following line
  const GuestBook({required this.addMessage, required this.messages});
  final FutureOr<void> Function(String message) addMessage;
  final List<GuestBookMessage> messages; // new

  @override
  _GuestBookState createState() => _GuestBookState();
}

Em seguida, vamos expor essa nova configuração em _GuestBookState modificando a build método da seguinte forma.

lib/main.dart

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

  @override
  // Modify from here
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        // to here.
        Padding(
          padding: const EdgeInsets.all(8.0),
          child: Form(
            key: _formKey,
            child: Row(
              children: [
                Expanded(
                  child: TextFormField(
                    controller: _controller,
                    decoration: const InputDecoration(
                      hintText: 'Leave a message',
                    ),
                    validator: (value) {
                      if (value == null || value.isEmpty) {
                        return 'Enter your message to continue';
                      }
                      return null;
                    },
                  ),
                ),
                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ê embrulhar o conteúdo anterior do método de construção com uma Column widget, e depois na cauda da Column 's crianças que você adicionar uma coleção para gerar um novo Paragraph para cada mensagem na lista de mensagens.

Finalmente, agora você precisa atualizar o corpo de HomePage para construir corretamente GuestBook com o novo messages parâmetro.

lib/main.dart

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

Testar mensagens de sincronização

O Cloud Firestore sincroniza automaticamente e instantaneamente os dados com os clientes inscritos no banco de dados.

  1. As mensagens que você criou anteriormente no banco de dados devem ser exibidas no aplicativo. Sinta-se à vontade para escrever novas mensagens; eles devem aparecer instantaneamente.
  2. Se você abrir seu espaço de trabalho em várias janelas ou guias, as mensagens serão sincronizadas em tempo real entre as guias.
  3. (Opcional) Você pode tentar manualmente apagar, modificar ou adicionar novas mensagens diretamente na seção Banco de Dados do console Firebase; todas as alterações devem aparecer na interface do usuário.

Parabéns! Você está lendo documentos do Cloud Firestore em seu aplicativo!

App revisão p

8. Configure regras básicas de segurança

Você configurou inicialmente o Cloud 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 deve ser parte integrante da estrutura e do comportamento do seu aplicativo.

As Regras de Segurança permitem controlar o acesso a documentos e coleções em seu banco de dados. A sintaxe de regras flexíveis permite que você crie regras que correspondam a qualquer coisa, desde todas as gravações em todo o banco de dados até operações em um documento específico.

Você pode escrever regras de segurança para o Cloud Firestore no console do Firebase:

  1. Na seção Desenvolver do Firebase console, clique em banco de dados e, em seguida, selecione a guia Regras (ou clique aqui para ir diretamente à guia Regras).
  2. Você deve ver as seguintes regras de segurança padrão, juntamente com um aviso sobre as regras serem públicas.

7767a2d2e64e7275.png

Identificar coleções

Primeiro, identifique as coleções nas quais o aplicativo grava dados.

Em match /databases/{database}/documents , identificar 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.
  }
}

Adicionar regras de segurança

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 tentando gravar no documento tem um UID de autenticação correspondente.

Adicione as regras de leitura e gravação ao seu conjunto de regras, conforme mostrado abaixo:

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, para o livro de visitas, apenas usuários conectados podem ler mensagens (qualquer mensagem!), mas apenas o autor de uma mensagem pode editar uma mensagem.

Adicionar regras de validação

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 aprendeu

Gravar o status de RSVP de um participante

No momento, seu aplicativo apenas permite que as pessoas comecem a conversar se estiverem interessadas no evento. Além disso, a única maneira de saber se alguém está vindo é postando no chat. Vamos nos organizar e deixar as pessoas saberem quantas pessoas estão vindo.

Você adicionará alguns novos recursos ao estado do aplicativo. A primeira é a capacidade de um usuário logado indicar se está participando ou não. A segunda capacidade é um contador de quantas pessoas estão realmente participando.

Em lib/main.dart , adicione o seguinte para a seção acessores para ativar o código UI para interagir com este estado:

lib/main.dart

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

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

Atualização ApplicationState 's init método da seguinte forma:

lib/main.dart

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

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

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

O acima adiciona uma consulta sempre assinada para descobrir o número de participantes e uma segunda consulta que está ativa apenas enquanto um usuário está conectado para descobrir se o usuário está participando. Em seguida, adicione o seguinte enumeração após a GuestBookMessage declaração:

lib/main.dart

enum Attending { yes, no, unknown }

Agora você vai definir um novo widget que funciona como botões de rádio antigos. Ele começa em um estado indeterminado, com nem sim nem não selecionado, mas uma vez que o usuário seleciona se está participando ou não, você mostra essa opção destacada com um botão preenchido e a outra opção retrocedendo com uma renderização plana.

lib/main.dart

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

  @override
  Widget build(BuildContext context) {
    switch (state) {
      case Attending.yes:
        return Padding(
          padding: 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'),
              ),
            ],
          ),
        );
    }
  }
}

Em seguida, você precisa atualizar HomePage método build 's para tirar proveito de YesNoSelection , permitindo um usuário logado para nomear se eles estão presentes. Você também exibirá o número de participantes para este evento.

lib/main.dart

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

Adicionar regras

Como você já tem algumas regras configuradas, os novos dados que você está adicionando com os botões serão rejeitados. Você precisa atualizar as regras para permitir a adição ao attendees coleção.

Para a attendees coleção, desde que você usou o UID de autenticação como o nome do documento, você pode agarrá-lo e verificar se o apresentador uid é o mesmo que o documento que está escrevendo. Você permitirá que todos leiam a lista de participantes (já que não há dados privados lá), mas apenas o criador deve poder atualizá-la.

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

Adicionar regras de validação

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;

    }
  }
}

(Opcional) Você pode agora ver os resultados de clicar nos botões. Acesse o painel do Cloud Firestore no console do Firebase.

Visualização do aplicativo

10. Parabéns!

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

O que cobrimos

  • Autenticação do Firebase
  • Cloud Firestore
  • Regras de segurança do Firebase

Próximos passos

  • Quer saber mais sobre outros produtos Firebase? Talvez você queira armazenar arquivos de imagem que os usuários carregam? Ou enviar notificações para seus usuários? Confira a documentação Firebase . Quer saber mais sobre os plugins Flutter para Firebase? Confira FlutterFire para mais informações.
  • Quer saber mais sobre o Cloud Firestore? Talvez você queira aprender sobre subcoleções e transações? De cabeça para o codelab web Nuvem Firestore para uma codelab que entra em mais profundidade sobre Nuvem Firestore. Ou confira esta série YouTube para conhecer Nuvem Firestore !

Saber mais

Como foi?

Adoraríamos seu feedback! Por favor, preencha um (muito) pequeno formulário aqui .