Flutter Firestore para diversas plataformas

Objetivos

Neste codelab, você criará um app de recomendações de restaurantes para várias plataformas com tecnologia do Flutter (link em inglês) e do Cloud Firestore.

O app finalizado pode ser executado no Android, no iOS e na Web com uma única base de código Dart.

5e7215e72fa571b.png

O que você aprenderá

  • Ler e gravar dados no Cloud Firestore usando um app do Flutter
  • Detectar mudanças nos dados do Cloud Firestore em tempo real
  • Usar o recurso Firebase Authentication e as regras de segurança para proteger os dados do Cloud Firestore
  • Gravar consultas e transações complexas do Cloud Firestore

O que você quer aprender neste codelab?

Ainda não conheço bem o assunto e quero ter uma boa visão geral. Conheço um pouco sobre esse assunto, mas quero me atualizar. Estou procurando exemplos de código para usar no meu projeto. Estou procurando uma explicação de algo específico.

O que é necessário

Se você não conhece bem o Flutter ou o Firestore, primeiro conclua o codelab do Firebase para Flutter:

Para concluir este codelab, você precisa do seguinte:

  • Um ambiente de desenvolvimento integrado ou editor de texto de sua preferência, como o Android Studio ou o VS Code (link em inglês) configurado com os plug-ins Dart e Flutter
  • O navegador Google Chrome e alguma familiaridade com o Chrome DevTools
  • Uma versão recente do Flutter (link em inglês) (canal beta ou mais recente) que tenha compatibilidade com a Web ativada. Você configurará a compatibilidade com a Web durante este codelab, mas consulte a página de compatibilidade com a Web para o Flutter (link em inglês) para ver mais informações
  • A ferramenta npm, para instalar as ferramentas de linha de comando firebase oficiais para a parte final deste codelab (Implantar índices, Proteger seus dados e Implantar no Firebase Hosting)
  • (Opcional) Um emulador ou dispositivo Android conectado, se você quiser compilar seu app para Android
  • (Opcional) Um Mac com uma versão bem recente do XCode, se você quiser compilar seu app para iOS

Criar um projeto do Firebase

  1. No Console do Firebase, clique em Adicionar projeto e nomeie o projeto como FriendlyEats. Lembre-se do ID do projeto do Firebase ou clique no ícone Editar para usar o ID que preferir.
  2. Clique em Criar projeto.

O aplicativo criado usa diversos serviços do Firebase disponíveis na Web:

  • Firebase Authentication: usado para identificar seus usuários com facilidade.
  • Cloud Firestore: usado para salvar dados estruturados no Cloud e receber notificações instantâneas quando os dados são atualizados.
  • Firebase Hosting: usado para hospedar e exibir seus recursos estáticos.

Agora, você aprenderá a configurar e ativar os serviços usando o Console do Firebase.

Ativar a autenticação anônima

Embora a autenticação não seja o foco deste codelab, é importante ter uma forma desse processo no seu app. Você usará o Login anônimo, o que significa que o usuário será conectado silenciosamente sem nenhuma solicitação.

Para ativar o Login anônimo, faça o seguinte:

  1. No Console do Firebase, localize a seção Desenvolver na barra de navegação à esquerda.
  2. Clique em Autenticação e depois na guia Método de login, ou acesse o Console do Firebase diretamente.
  3. Ative o provedor de login Anônimo e clique em Salvar.

fee6c3ebdf904459.png

A ativação do Login anônimo permitirá que o aplicativo faça login dos usuários silenciosamente quando eles acessarem o app da Web. Para saber mais, consulte a documentação de autenticação anônima.

Ativar o Cloud Firestore

O app usa o Cloud Firestore para salvar e receber informações sobre restaurantes e notas para eles.

Para ativar o Cloud Firestore, faça o seguinte:

  1. Na seção Desenvolver do Console do Firebase, clique em Banco de dados.
  2. Clique em Criar banco de dados no painel do Cloud Firestore.

57e83568e05c7710.png

  1. Selecione a opção Iniciar no modo de teste e clique em Ativar depois de ler a exoneraçã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. Você tornará seu banco de dados mais seguro posteriormente neste codelab.

daef1061fc25acc7.png

Clone o repositório do GitHub (link em inglês) da linha de comando:

git clone https://github.com/FirebaseExtended/codelab-friendlyeats-flutter.git friendlyeats-flutter

O exemplo de código precisa ser clonado para o diretório 📁friendlyeats-flutter. De agora em diante, execute comandos desse diretório:

cd friendlyeats-flutter

Importar o app inicial

Abra ou importe o diretório 📁friendlyeats-flutter no ambiente de desenvolvimento integrado de sua preferência. O diretório contém o código inicial do codelab, que consiste em um app de recomendação de restaurantes ainda não funcional.

Você tornará o app funcional ao longo deste codelab, então o código nesse diretório será editado em breve.

Localizar os arquivos em que você trabalhará

Embora o ponto de entrada usual de um app do Flutter seja o arquivo lib/main.dart dele, neste codelab, você se concentrará nos dados.

Localize os seguintes arquivos no projeto:

  • lib/src/model/data.dart: o arquivo principal que você modifica durante este codelab. Ele contém toda a lógica para ler e gravar dados do Firestore.
  • web/index.html: o arquivo que o navegador carrega para iniciar seu aplicativo. Você modificará esse arquivo para instalar e inicializar as bibliotecas do Firebase para a Web.

CLI do Firebase

A interface de linha de comando (CLI) do Firebase permite implantar o app da Web e a configuração do Firebase diretamente dos arquivos no projeto.

  1. Instale a CLI executando o seguinte comando do npm:
npm -g install firebase-tools
  1. Verifique se a CLI foi instalada corretamente executando o seguinte comando:
firebase --version

A CLI do Firebase precisa estar na versão v7.4.0 ou mais recente.

  1. Autorize a CLI do Firebase executando o seguinte comando:
firebase login

O repositório que você clonou na etapa anterior já tem um arquivo firebase.json com alguma configuração de projeto pronta (localização de outros arquivos de configuração, implantação de hospedagem e assim por diante). Agora, você precisa associar a cópia de trabalho do app ao projeto do Firebase:

  1. Confira se a linha de comando está acessando o diretório local do seu app.
  2. Associe o app ao projeto do Firebase executando o seguinte comando:
firebase use --add
  1. Quando solicitado, selecione o ID do projeto e atribua um alias ao projeto do Firebase.

O alias será útil se você tiver vários ambientes (produção, preparo e assim por diante). Neste codelab, basta usar o alias default.

  1. Siga as instruções fornecidas na linha de comando.

Ativar a compatibilidade com a Web para o Flutter

Para compilar seu app do Flutter para execução na Web, é necessário ativar esse recurso, que está na versão Beta. Para ativar a compatibilidade com a Web, digite o seguinte:

$ flutter channel beta
$ flutter upgrade
$ flutter config --enable-web

No seu ambiente de desenvolvimento integrado sob o menu suspenso de dispositivos, ou na linha de comando com flutter devices, você verá os itens Chrome e Web server listados.

O dispositivo Chrome inicia automaticamente esse navegador. O Web server inicia um servidor que hospeda o app para que você possa carregá-lo em qualquer navegador.

Use o dispositivo Chrome durante o desenvolvimento para poder usar o DevTools e use o Web server quando quiser testar em outros navegadores.

Depois de criar um projeto do Firebase, você pode configurar um ou mais apps para usar esse projeto. Faça o seguinte:

  • Registrar o ID específico da plataforma do app no Firebase.
  • Gere arquivos de configuração para seu app.
  • Adicione a configuração ao ponto certo nas pastas do projeto.

ac27fbbadff7a3b9.png

Se você estiver desenvolvendo seu app do Flutter para várias plataformas, será necessário registrar cada plataforma em que ele será executado no mesmo projeto do Firebase.

Este codelab se concentra na plataforma da Web, porque o iOS e o Android são abordados no codelab do Firebase para Flutter. Acesse-o se você quiser adicionar compatibilidade para Android ou iOS ao seu app FriendlyEats.

No seu app do Flutter, há um arquivo web/index.html especial usado como ponto de entrada para o app ao executá-lo na Web. Esse ponto de entrada precisa ser modificado com uma configuração específica para o projeto, permitindo que o aplicativo da Web se conecte ao back-end do Firebase.

Configurar para a Web

  1. No Console do Firebase, selecione Visão geral do projeto na barra de navegação à esquerda e clique no botão Web abaixo da mensagem Comece adicionando o Firebase ao seu aplicativo. Você verá a seguinte caixa de diálogo:

f76ed55f71f15953.png

  1. Dê um apelido ao app. Esse é o valor usado no console do Firestore para identificar a versão da Web do app.
  2. Clique em Registrar app.
  3. Após o registro do seu app, a etapa Adicionar o SDK do Firebase fornece um código que você precisa colar no arquivo web/index.html do app do Flutter. Quando estiver concluído, ele terá esta aparência:

web/index.html

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>friendlyeats</title>

  <!-- The core Firebase JS SDK is always required and must be listed first -->
  <script src="https://www.gstatic.com/firebasejs/7.5.1/firebase-app.js"></script>

  <!-- TODO: Add SDKs for Firebase products that you want to use
      https://firebase.google.com/docs/web/setup#available-libraries -->

  <script>
    // Your web app's Firebase configuration
    var firebaseConfig = {
      apiKey: "YoUr_RaNdOm_API_kEy",
      authDomain: "your-project-name.firebaseapp.com",
      databaseURL: "https://your-project-name.firebaseio.com",
      projectId: "your-project-name",
      storageBucket: "your-project-name.appspot.com",
      messagingSenderId: "012345678901",
      appId: "1:109876543210:web:r4nd0mH3xH45h"
    };
    // Initialize Firebase
    firebase.initializeApp(firebaseConfig);
  </script>

</head>
<body>
  <script src="main.dart.js" type="application/javascript"></script>
</body>
</html>
  1. Há um comentário TODO no código que você acabou de colar. Corrigiremos isso agora. Como o codelab usa o Firebase Auth e o Firestore, adicione agora as tags de script para esses produtos:

web/index.html

  ...
  <!-- TODO: Add SDKs for Firebase products that you want to use
      https://firebase.google.com/docs/web/setup#available-libraries -->

  <script src="https://www.gstatic.com/firebasejs/7.5.1/firebase-auth.js"></script>
  <script src="https://www.gstatic.com/firebasejs/7.5.1/firebase-firestore.js"></script>

  <script>
    // Your web app's Firebase configuration
    var firebaseConfig = {
      ...
  1. Salve o arquivo web/index.html e clique em Continuar no console na caixa de diálogo Adicionar o Firebase ao seu app da Web.
  2. Seu app do Flutter está pronto para se conectar ao Firebase.

d43a19dbf1248134.pngVocê encontrou algo especial!

A maioria das mudanças de código necessárias para ativar a compatibilidade do Firebase já foi feita no projeto em que você está trabalhando. No entanto, para adicionar compatibilidade com plataformas móveis, você precisa seguir um processo semelhante ao que acabou de fazer para a Web:

  • Registre a plataforma desejada no projeto do Firebase.
  • Faça o download do arquivo de configuração específico da plataforma e adicione-o ao código.

No diretório de nível superior do seu app do Flutter, há subdiretórios chamados ios e android. Esses diretórios contêm os arquivos de configuração específicos da plataforma para iOS e Android, respectivamente.

Configurar o iOS

  1. No Console do Firebase, selecione Visão geral do projeto na barra de navegação à esquerda e clique no botão "iOS" abaixo da mensagem Comece adicionando o Firebase ao seu aplicativo.

Você verá a seguinte caixa de diálogo:

c42139f18fb9a2ee.png

  1. O valor importante que será fornecido é o Código do pacote do iOS. Você encontrará o ID do pacote seguindo as próximas três etapas.
  1. Na ferramenta de linha de comando, acesse o diretório de nível superior do seu app do Flutter.
  2. Execute o comando open ios/Runner.xcworkspace para abrir o Xcode.
  1. No Xcode, clique em Runner no nível superior no painel esquerdo para mostrar a guia General no painel direito, conforme a imagem abaixo. Copie o valor de Bundle Identifier.

9733e26be329f329.png

  1. Volte para a caixa de diálogo do Firebase, cole o Bundle Identifier copiado no campo Código do pacote do iOS e clique em Registrar app.
  1. Ainda no Firebase, siga as instruções para fazer o download do arquivo de configuração GoogleService-Info.plist.
  2. Volte para o Xcode. Observe que Runner também tem uma subpasta chamada Runner (mostrada na imagem anterior).
  3. Arraste o arquivo GoogleService-Info.plist que você acabou de salvar para a subpasta Runner.
  4. Na caixa de diálogo que aparece no Xcode, clique em Finish.
  5. Volte para o Console do Firebase. Na etapa de configuração, clique em Próxima, pule as etapas restantes e volte para página principal do Console do Firebase.

Você terminou de configurar seu app do Flutter para iOS.

Configurar para Android

  1. No Console do Firebase, selecione Visão geral do projeto na barra de navegação à esquerda e clique no botão Android abaixo da mensagem Comece adicionando o Firebase ao seu aplicativo.

Você verá a seguinte caixa de diálogo: 8254fc299e82f528.png

  1. O valor importante que será fornecido é o Nome do pacote do Android. Você encontrará o nome do pacote ao seguir estas duas etapas:
  1. No diretório do app do Flutter, abra o arquivo android/app/src/main/AndroidManifest.xml.
  2. No elemento manifest, encontre o valor da string do atributo package. Esse valor é o nome do pacote do Android, algo como com.yourcompany.yourproject. Copie esse valor.
  3. Na caixa de diálogo do Firebase, cole o nome de pacote copiado no campo Nome do pacote do Android.
  4. O Certificado de assinatura de depuração SHA-1 não é necessário para este codelab. Deixe-o em branco.
  5. Clique em Registrar app.
  6. Ainda no Firebase, siga as instruções para fazer o download do arquivo de configuração google-services.json.
  7. Acesse o diretório do app do Flutter e mova o arquivo google-services.json que você acabou de salvar para o diretório android/app.
  8. No Console do Firebase, pule as etapas restantes e volte à página principal.
  9. Toda a configuração do Gradle já foi verificada. Caso seu app ainda esteja em execução, feche e recrie-o para que o Gradle instale as dependências.

Você terminou de configurar seu app do Flutter para Android.

Você já pode começar a trabalhar no seu app. Primeiro, execute o app localmente. Agora você pode executá-lo em qualquer plataforma que tenha configurado e para a qual você tenha um dispositivo e emulador disponíveis.

Descubra quais dispositivos estão disponíveis com o seguinte comando:

flutter devices

Dependendo da disponibilidade, o resultado do comando anterior será parecido com este:

3 connected devices:

Android SDK built for x86 • emulator-5554 • android-x86    • Android 7.1.1 (API 25) (emulator)
Chrome                    • chrome        • web-javascript • Google Chrome 79.0.3945.130
Web Server                • web-server    • web-javascript • Flutter Tools

Continuaremos este codelab usando o dispositivo chrome.

  1. Execute o seguinte comando da CLI do Flutter:
flutter run -d chrome
  1. O Flutter é iniciado com a mensagem Building your application for the web… e abre automaticamente uma janela do Chrome com o app em execução.

Agora, você verá sua cópia do FriendlyEats, conectada ao seu projeto do Firebase.

O app se conectará automaticamente ao seu projeto do Firebase e fará um login silencioso como um usuário anônimo.

c45806a2ac9300d9.png

Nesta seção, você gravará alguns dados no Cloud Firestore para preencher a IU do app. Essa ação pode ser feita manualmente usando o Console do Firebase, mas você a fará no app para ver uma demonstração de gravação básica do Cloud Firestore.

Modelo de dados

Os dados do Firestore são divididos em coleções, documentos, campos e subcoleções. Cada restaurante é armazenado como um documento em uma coleção de nível superior chamada restaurants.

92f8dc2c769d2d6c.png

Mais tarde, você armazenará cada avaliação em uma subcoleção chamada ratings dentro de cada restaurant.

a00d9eb006ddd6c0.png

Adicionar restaurantes ao Firestore

O principal objeto de modelo no app é um restaurante. Escreva um código que adiciona um documento de restaurante à coleção restaurants.

  1. Abra o lib/src/model/data.dart
  2. Encontre a função addRestaurant.
  3. Substitua a função inteira por este código:

lib/src/model/data.dart

Future<void> addRestaurant(Restaurant restaurant) {
  final restaurants = FirebaseFirestore.instance.collection('restaurants');
  return restaurants.add({
    'avgRating': restaurant.avgRating,
    'category': restaurant.category,
    'city': restaurant.city,
    'name': restaurant.name,
    'numRatings': restaurant.numRatings,
    'photo': restaurant.photo,
    'price': restaurant.price,
  });
}

O código anterior adiciona um novo documento à coleção restaurants.

Para isso, primeiro você precisa de uma referência a uma coleção restaurants do Cloud Firestore e depois adding (adicionar) os dados.

Os dados do documento são provenientes de um objeto Restaurant, que precisa ser convertido em Map para o plug-in do Firestore.

Adicionar alguns restaurantes

  1. Recrie e atualize o app do Flutter pressionando Shift + R na janela de terminal que executa o app.
  2. Clique em ADD SOME.

O app gera automaticamente um conjunto aleatório de objetos restaurants e chama a função addRestaurant. No entanto, você não verá os dados no seu app da Web, porque ainda é necessário implementar a recuperação dos dados (próxima seção do codelab).

Ao navegar até Desenvolver > Banco de dados > Cloud Firestore no Console do Firebase, você verá novos documentos na coleção restaurants.

f06898b9d6dd4881.png

Parabéns! Você acabou de gravar dados no Cloud Firestore com um app da Web.

Na próxima seção, você aprenderá a recuperar dados do Cloud Firestore e exibi-los no seu app.

Nesta seção, você aprenderá a recuperar dados do Cloud Firestore e a exibi-los no seu app. As duas etapas principais são criar uma consulta e usar um listener no Stream de snapshots. Esse listener é notificado de todos os dados existentes que correspondem à consulta e recebe atualizações em tempo real.

Primeiro, gere a consulta que exibe a lista de restaurantes não filtrada padrão.

  1. Volte para o arquivo lib/src/model/data.dart.
  2. Encontre a função loadAllRestaurants.
  3. Substitua a função inteira por este código:

lib/src/model/data.dart

Stream<QuerySnapshot> loadAllRestaurants() {
  return FirebaseFirestore.instance
      .collection('restaurants')
      .orderBy('avgRating', descending: true)
      .limit(50)
      .snapshots();
}

O código anterior gera uma consulta que recupera até 50 restaurantes da coleção de nível superior chamada restaurants, ordenados pela nota média (atualmente, zero para todos).

Agora, é necessário transformar cada QuerySnapshot retornado do Stream em dados de Restaurant que você pode renderizar.

Para extrair informações de Restaurant de um QuerySnapshot da coleção restaurants, faça o seguinte:

  1. Volte para o arquivo lib/src/model/data.dart.
  2. Encontre a função getRestaurantsFromQuery.
  3. Substitua a função inteira por este código:

lib/src/model/data.dart

List<Restaurant> getRestaurantsFromQuery(QuerySnapshot snapshot) {
  return snapshot.docs.map((DocumentSnapshot doc) {
    return Restaurant.fromSnapshot(doc);
  }).toList();
}

O método getRestaurantsFromQuery é chamado sempre que há um novo QuerySnapshot da Query que você criou anteriormente. Os QuerySnapshots são os mecanismos que o Firestore usa para notificar seu app sobre mudanças em uma Query em tempo real.

Esse método simplesmente converte todos os documents contidos no snapshot em objetos Restaurant que podem ser usados em outros lugares no app do Flutter.

Agora que você implementou os dois métodos, recrie e recarregue o app. Verifique se os restaurantes que você viu anteriormente no Console do Firebase estão visíveis no app. Se você concluiu esta seção, isso significa que seu app está lendo e gravando dados junto ao Cloud Firestore.

Conforme a lista de restaurantes muda, esse listener é atualizado automaticamente. Você pode acessar o Console do Firebase e excluir manualmente um restaurante ou mudar o nome dele. As mudanças aparecerão no seu site imediatamente.

edd9adbafa5bd539.png

Até agora, você aprendeu a usar o onSnapshot para recuperar atualizações em tempo real. No entanto, nem sempre é isso que você quer fazer. Às vezes, faz sentido buscar os dados uma única vez.

Você precisa ter um método que carregue um restaurante específico do ID dele para quando os usuários clicarem em um restaurante específico no app.

  1. Volte para seu arquivo lib/src/model/data.dart.
  2. Encontre a função getRestaurant.
  3. Substitua a função inteira por este código:

lib/src/model/data.dart

Future<Restaurant> getRestaurant(String restaurantId) {
  return FirebaseFirestore.instance
      .collection('restaurants')
      .doc(restaurantId)
      .get()
      .then((DocumentSnapshot doc) => Restaurant.fromSnapshot(doc));
}

O código usa get() para recuperar um Future<DocumentSnapshot> contendo as informações do restaurante solicitado. Você só precisa direcioná-lo por then() até uma função que converterá o DocumentSnapshot em um objeto Restaurant quando ele estiver pronto.

Depois de implementar esse método, você poderá ver a página de detalhes de cada restaurante.

  1. Atualize o app pressionando Shift + R no terminal em que o flutter está em execução.
  2. Clique em um restaurante na lista. Você verá a página de detalhes do restaurante:

f8ca540dda5540a9.png

A seguir, você adicionará o código necessário para dar notas aos restaurantes usando transações.

Nesta seção, você adicionará um recurso para que os usuários avaliem os restaurantes. Até agora, todas as suas gravações foram atômicas e relativamente simples. Se alguma apresentar erro, você provavelmente solicitará que o usuário tente novamente ou o app repetirá a tentativa de forma automática.

O app terá muitos usuários querendo adicionar uma nota para um restaurante, então você precisa coordenar várias leituras e gravações. Primeiro, a avaliação precisa ser enviada e, depois, a count da nota e a average rating do restaurante precisam ser atualizadas. Se uma delas falhar, mas a outra não, você ficará em um estado inconsistente. Os dados de uma parte do banco de dados não correspondem aos da outra.

Felizmente, o Cloud Firestore oferece uma funcionalidade de transação que permite realizar várias leituras e gravações em uma única operação atômica, garantindo que os dados permaneçam consistentes.

  1. Volte para seu arquivo lib/src/model/data.dart.
  2. Encontre a função addReview.
  3. Substitua a função inteira por este código:

lib/src/model/data.dart

Future<void> addReview({String restaurantId, Review review}) {
  final restaurant =
      FirebaseFirestore.instance.collection('restaurants').doc(restaurantId);
  final newReview = restaurant.collection('ratings').doc();

  return FirebaseFirestore.instance.runTransaction((Transaction transaction) {
    return transaction
        .get(restaurant)
        .then((DocumentSnapshot doc) => Restaurant.fromSnapshot(doc))
        .then((Restaurant fresh) {
      final newRatings = fresh.numRatings + 1;
      final newAverage =
          ((fresh.numRatings * fresh.avgRating) + review.rating) / newRatings;

      transaction.update(restaurant, {
        'numRatings': newRatings,
        'avgRating': newAverage,
      });

      transaction.set(newReview, {
        'rating': review.rating,
        'text': review.text,
        'userName': review.userName,
        'timestamp': review.timestamp ?? FieldValue.serverTimestamp(),
        'userId': review.userId,
      });
    });
  });
}

A função anterior aciona uma transação que começa buscando uma versão fresh do Restaurant, representada por restaurantId.

Atualize os valores numéricos de avgRating e numRatings na referência do documento restaurant.

Ao mesmo tempo, você adicionará a nova review pela referência do documento newReview à subcoleção ratings do restaurante.

Para testar o código que você adicionou, faça o seguinte:

  1. Atualize o app pressionando Shift + R no terminal em que o flutter está em execução.
  2. Acesse qualquer página de detalhes do restaurante.
  3. Adicione algumas avaliações. Para isso, você pode:
  • clicar no botão ADD SOME na lista vazia, se não houver avaliações;
  • clicar no botão de ação flutuante + e digitar sua própria avaliação.

Atualmente, o app exibe uma lista de restaurantes, mas não há como o usuário filtrar de acordo com as necessidades dele. Nesta seção, você usará a consulta avançada do Cloud Firestore para ativar o uso de filtros.

Aqui está um exemplo de consulta simples para buscar todos os restaurantes de Dim Sum:

Query filteredCollection = FirebaseFirestore.instance
        .collection('restaurants')
        .where('category', isEqualTo: 'Dim Sum');

Ao usar o método where(), a consulta faz o download apenas dos membros da coleção cujos campos atendam às restrições que você definiu. Nesse caso, ele só faz o download de restaurantes em que a category é igual a Dim Sum.

Da mesma forma, é possível ordenar os dados retornados:

Query filteredAndSortedCollection = FirebaseFirestore.instance
        .collection('restaurants')
        .where('category', isEqualTo: 'Dim Sum')
        .orderBy('price', descending: true);

O método orderBy() faz com que a consulta ordene os restaurantes de Dim Sum pelo atributo price, do mais caro para o mais barato.

No app, o usuário pode encadear vários filtros para criar consultas específicas, como Pizza em São Paulo ou Frutos do Mar no Rio de Janeiro, ordenadas por popularidade.

Você gera um método para criar uma consulta que filtra os restaurantes com base em vários critérios selecionados pelos usuários.

  1. Volte para seu arquivo lib/src/model/data.dart.
  2. Encontre a função loadFilteredRestaurants.
  3. Substitua a função inteira por este código:

lib/src/model/data.dart

Stream<QuerySnapshot> loadFilteredRestaurants(Filter filter) {
  Query collection = FirebaseFirestore.instance.collection('restaurants');
  if (filter.category != null) {
    collection = collection.where('category', isEqualTo: filter.category);
  }
  if (filter.city != null) {
    collection = collection.where('city', isEqualTo: filter.city);
  }
  if (filter.price != null) {
    collection = collection.where('price', isEqualTo: filter.price);
  }
  return collection
      .orderBy(filter.sort ?? 'avgRating', descending: true)
      .limit(50)
      .snapshots();
}

O código anterior acrescenta vários filtros where e uma única cláusula orderBy para criar uma consulta composta com base na entrada do usuário. Agora, a consulta retorna apenas restaurantes que atendem aos requisitos do usuário.

Atualize o app no navegador pressionando Shift + R no terminal em que o flutter está em execução.

Agora, tente filtrar por preço, cidade e categoria. Durante os testes, você verá erros no Console JavaScript do navegador semelhantes a este:

The query requires an index. You can create it here: https://console.firebase.google.com/project/.../database/firestore/indexes?create_index=...

Esses erros ocorrem porque o Cloud Firestore requer índices para a maioria das consultas compostas. A exigência de índices em consultas mantém o Cloud Firestore rápido em escala.

Ao abrir o link pela mensagem de erro, a IU de criação do índice será aberta automaticamente no Console do Firebase com os parâmetros corretos preenchidos.

Na próxima seção, você gravará e implantará os índices necessários para este aplicativo na CLI do Firebase, de uma só vez.

Se você não quiser analisar cada caminho do app e acessar cada um dos links de criação de índice, é possível implantar vários índices de uma só vez usando a CLI do Firebase.

  1. Na raiz do projeto do app, localize o arquivo firestore.indexes.json.

Esse arquivo descreve todos os índices necessários para todas as combinações possíveis de filtros.

firestore.indexes.json

{
 "indexes": [
   {
     "collectionGroup": "restaurants",
     "queryScope": "COLLECTION",
     "fields": [
       { "fieldPath": "city", "order": "ASCENDING" },
       { "fieldPath": "avgRating", "order": "DESCENDING" }
     ]
   },

   ...

 ]
}
  1. Implante esses índices com o seguinte comando:
firebase deploy --only firestore:indexes

Após alguns minutos, os índices serão ativados e as mensagens de erro desaparecerão. Se você tentar usar os índices antes que eles estejam totalmente prontos, talvez ocorram erros parecidos com estes:

The query requires an index. That index is currently building and cannot be used yet. See its status here:
https://console.firebase.google.com/project/.../database/firestore/indexes?create_index=...

No início deste codelab, você definiu as regras de segurança do seu app para abrir completamente o banco de dados para qualquer leitura ou gravação. Em um aplicativo real, você definiria regras muito mais refinadas para impedir o acesso ou a modificação indesejados de dados.

  1. Na seção Desenvolver do Console do Firebase, clique em Banco de dados.
  2. Clique na guia Regras na seção do Cloud Firestore ou acesse o Console do Firebase diretamente.
  3. Substitua os padrões pelas seguintes regras e clique em Publicar.

firestore.rules

service cloud.firestore {
  match /databases/{database}/documents {
    // Restaurants:
    //   - Authenticated user can read
    //   - Authenticated user can create/update (for demo)
    //   - Validate updates
    //   - Deletes are not allowed
    match /restaurants/{restaurantId} {
      allow read, create: if request.auth != null;
      allow update: if request.auth != null
                    && request.resource.data.name == resource.data.name
      allow delete: if false;

      // Ratings:
      //   - Authenticated user can read
      //   - Authenticated user can create if userId matches
      //   - Deletes and updates are not allowed
      match /ratings/{ratingId} {
        allow read: if request.auth != null;
        allow create: if request.auth != null
                      && request.resource.data.userId == request.auth.uid;
        allow update, delete: if false;
      }
    }
  }
}

Essas regras restringem o acesso para garantir que os clientes façam apenas mudanças seguras. Por exemplo:

  • Atualizações no documento de um restaurante podem mudar apenas as notas, não o nome ou qualquer outro dado imutável.
  • As notas só poderão ser criadas se o ID do usuário for o do usuário conectado, o que evita o spoofing.

Em vez de usar o Console do Firebase, você pode usar a CLI do Firebase para implantar regras no seu projeto. O arquivo firestore.rules no seu diretório de trabalho já contém as regras anteriores. Para implantá-las no seu sistema de arquivos local, em vez de usar o Console do Firebase, execute o seguinte comando:

firebase deploy --only firestore:rules

flutter build web

Até o momento, você usou apenas versões de depuração do seu app do Flutter. Esses builds são um pouco mais lentos, porque contêm informações extras para facilitar a depuração.

Antes de implantar o app, é preciso criar uma versão de produção (prod). O Flutter permite criar para produção usando a ferramenta build:

flutter build web

Isso coloca todos os recursos criados para produção no diretório build/web do projeto.

Seu app está pronto para ser implantado no Firebase.

firebase deploy

Seu projeto está pré-configurado para a criação e implantação dos recursos que o Flutter gera. Veja o arquivo firebase.json na raiz do projeto.

Implante uma nova versão do app com o Firebase usando os seguintes comandos:

firebase init hosting
firebase deploy --only hosting

O processo anterior leva apenas alguns segundos. Ele apaga quaisquer builds anteriores (flutter clean), recria seu app (flutter build web) e implanta os recursos recém-criados (o conteúdo de build/web) no Firebase Hosting.

A mensagem de êxito contém o URL de hospedagem em que o app publicado está disponível na Internet.

Parabéns!

Neste codelab, você aprendeu a conectar seu app da Web do Flutter ao Firebase usando os plug-ins do Firebase Auth e do Firestore, fez leituras e gravações básicas e avançadas com o Cloud Firestore e protegeu o acesso aos dados com regras de segurança.

Você pode encontrar a solução completa na ramificação done (link em inglês) do repositório.

Para saber mais sobre o Dart e o Flutter, acesse os sites oficiais deles (em inglês):

Para saber mais sobre o Cloud Firestore, acesse estes recursos:

Este codelab é um bom ponto de partida para conhecer outros recursos do Firestore (e do Firebase). Se você quiser um desafio extra, tente:

  • usar gravações em lote no método addRestaurantsBatch para adicionar todos os restaurantes e avaliações em uma única solicitação. Assim, a IU do aplicativo será atualizada apenas uma vez;
  • modificar o widget RestaurantAppBar para que ele atualize a nota de um restaurante em tempo real conforme avaliações forem adicionadas pelos usuários;
  • ativar o firebase_auth com google_sign_in para recuperar o nome do usuário que está postando uma avaliação, já que todos os usuários são anônimos no momento.