O Google tem o compromisso de promover a igualdade racial para as comunidades negras. Saiba como.

Cloud Firestore Android Codelab

Metas

Neste codelab, você criará um aplicativo de recomendação de restaurante no Android com o suporte do Cloud Firestore. Você vai aprender como:

  • Ler e gravar dados no Firestore de um aplicativo Android
  • Ouça as alterações nos dados do Firestore em tempo real
  • Use Firebase Authentication e regras de segurança para proteger os dados do Firestore
  • Escreva consultas complexas do Firestore

Pré-requisitos

Antes de iniciar este codelab, certifique-se de ter:

  • Android Studio 4.0 ou superior
  • Um emulador de Android
  • Node.js versão 10 ou superior
  • Java versão 8 ou superior
  1. Faça login no console do Firebase com sua conta do Google.
  2. No Firebase console , clique em Adicionar projeto .
  3. Conforme mostrado na captura de tela abaixo, insira um nome para seu projeto Firebase (por exemplo, "Comida amigável") e clique em Continuar .

9d2f625aebcab6af.png

  1. Pode ser solicitado que você ative o Google Analytics; para os fins deste codelab, sua seleção não importa.
  2. Depois de um minuto ou mais, seu projeto Firebase estará pronto. Clique em Continue .

Baixe o código

Execute o seguinte comando para clonar o código de amostra para este codelab. Isso criará uma pasta chamada friendlyeats-android em sua máquina:

$ git clone https://github.com/firebase/friendlyeats-android

Se você não tem git em sua máquina, também pode baixar o código diretamente do GitHub.

Importe o projeto para o Android Studio. Você provavelmente verá alguns erros de compilação ou talvez um aviso sobre a falta de um arquivo google-services.json . Vamos corrigir isso na próxima seção.

Adicionar configuração do Firebase

  1. No Firebase console , selecione Visão geral do projeto no painel de navegação esquerdo. Clique no botão Android para selecionar a plataforma. Quando for solicitado um nome de pacote, use com.google.firebase.example.fireeats

73d151ed16016421.png

  1. Clique em Registrar aplicativo e siga as instruções para baixar o arquivo google-services.json e mova-o para o app/ pasta do código de amostra. Em seguida, clique em Avançar .

Neste codelab, você usará o Firebase Emulator Suite para emular localmente o Cloud Firestore e outros serviços do Firebase. Isso fornece um ambiente de desenvolvimento local seguro, rápido e gratuito para construir seu aplicativo.

Instale a Firebase CLI

Primeiro, você precisa instalar a Firebase CLI . A maneira mais fácil de fazer isso é usar o npm :

npm install -g firebase-tools

Se você não tiver o npm ou se tiver um erro, leia as instruções de instalação para obter um binário autônomo para sua plataforma.

Depois de instalar a CLI, execute firebase --version para relatar uma versão 9.0.0 ou superior:

$ firebase --version
9.0.0

Conecte-se

Execute firebase login para conectar a CLI à sua conta do Google. Isso abrirá uma nova janela do navegador para concluir o processo de login. Certifique-se de escolher a mesma conta que você usou ao criar seu projeto Firebase anteriormente.

Na pasta friendlyeats-android execute firebase use --add para conectar seu projeto local ao projeto Firebase. Siga os prompts para selecionar o projeto que você criou anteriormente e, se for solicitado a escolher um alias, insira default .

Agora é hora de executar o Firebase Emulator Suite e o app FriendlyEats Android pela primeira vez.

Execute os emuladores

No seu terminal, a partir do diretório friendlyeats-android , execute os firebase emulators:start do Firebase firebase emulators:start para inicializar os emuladores do Firebase. Você deve ver registros como este:

$ firebase emulators:start
i  emulators: Starting emulators: auth, firestore
i  firestore: Firestore Emulator logging to firestore-debug.log
i  ui: Emulator UI logging to ui-debug.log

┌─────────────────────────────────────────────────────────────┐
│ ✔  All emulators ready! It is now safe to connect your app. │
│ i  View Emulator UI at http://localhost:4000                │
└─────────────────────────────────────────────────────────────┘

┌────────────────┬────────────────┬─────────────────────────────────┐
│ Emulator       │ Host:Port      │ View in Emulator UI             │
├────────────────┼────────────────┼─────────────────────────────────┤
│ Authentication │ localhost:9099 │ http://localhost:4000/auth      │
├────────────────┼────────────────┼─────────────────────────────────┤
│ Firestore      │ localhost:8080 │ http://localhost:4000/firestore │
└────────────────┴────────────────┴─────────────────────────────────┘
  Emulator Hub running at localhost:4400
  Other reserved ports: 4500

Issues? Report them at https://github.com/firebase/firebase-tools/issues and attach the *-debug.log files.

Agora você tem um ambiente de desenvolvimento local completo em execução em sua máquina! Certifique-se de deixar este comando em execução pelo resto do codelab, seu aplicativo Android precisará se conectar aos emuladores.

Conecte o aplicativo aos emuladores

Abra o arquivo FirebaseUtil.java no Android Studio. Este arquivo contém a lógica para conectar os SDKs do Firebase aos emuladores locais em execução na sua máquina.

Na parte superior do arquivo, examine esta linha:

    /** Use emulators only in debug builds **/
    private static final boolean sUseEmulators = BuildConfig.DEBUG;

Estamos usando BuildConfig para garantir que só nos conectemos aos emuladores quando nosso aplicativo estiver sendo executado no modo de debug . Quando compilamos o aplicativo no modo de release , essa condição será falsa.

Agora, dê uma olhada no método getFirestore() :

    public static FirebaseFirestore getFirestore() {
        if (FIRESTORE == null) {
            FIRESTORE = FirebaseFirestore.getInstance();

            // Connect to the Cloud Firestore emulator when appropriate. The host '10.0.2.2' is a
            // special IP address to let the Android emulator connect to 'localhost'.
            if (sUseEmulators) {
                FIRESTORE.useEmulator("10.0.2.2", 8080);
            }
        }

        return FIRESTORE;
    }

Podemos ver que ele está usando o método useEmulator(host, port) para conectar o Firebase SDK ao emulador local do Firestore. Em todo o aplicativo, usaremos FirebaseUtil.getFirestore() para acessar esta instância do FirebaseFirestore para termos certeza de que estamos sempre nos conectando ao emulador Firestore ao executar no modo de debug .

Execute o aplicativo

Se você adicionou o arquivo google-services.json corretamente, o projeto agora deve ser compilado. No Android Studio, clique em Build > Rebuild Project e certifique-se de que não haja erros restantes.

No Android Studio, execute o aplicativo em seu emulador Android. Primeiramente, você verá uma tela de "Login". Você pode usar qualquer e-mail e senha para fazer login no aplicativo. Este processo de login está se conectando ao emulador do Firebase Authentication, portanto, nenhuma credencial real está sendo transmitida.

Agora abra a IU dos emuladores navegando para http: // localhost: 4000 em seu navegador. Em seguida, clique na guia Autenticação e você verá a conta que acabou de criar:

Emulador de autenticação do Firebase

Depois de concluir o processo de login, você deverá ver a tela inicial do aplicativo:

de06424023ffb4b9.png

Em breve adicionaremos alguns dados para preencher a tela inicial.

Nesta seção, escreveremos alguns dados no Firestore para que possamos preencher a tela inicial vazia.

O objeto de modelo principal em nosso aplicativo é um restaurante (consulte model/Restaurant.java ). Os dados do Firestore são divididos em documentos, coleções e subcoleções. Armazenaremos cada restaurante como um documento em uma coleção de nível superior chamada "restaurants" . Para saber mais sobre o modelo de dados Firestore, leia sobre documentos e coleções na documentação .

Para fins de demonstração, adicionaremos funcionalidade no aplicativo para criar dez restaurantes aleatórios quando clicarmos no botão "Adicionar itens aleatórios" no menu flutuante. Abra o arquivo MainActivity.java e preencha o método onAddItemsClicked() :

    private void onAddItemsClicked() {
        // Get a reference to the restaurants collection
        CollectionReference restaurants = mFirestore.collection("restaurants");

        for (int i = 0; i < 10; i++) {
            // Get a random Restaurant POJO
            Restaurant restaurant = RestaurantUtil.getRandom(this);

            // Add a new document to the restaurants collection
            restaurants.add(restaurant);
        }
    }

Existem algumas coisas importantes a serem observadas sobre o código acima:

  • Começamos por obter uma referência à coleção "restaurants" . As coleções são criadas implicitamente quando os documentos são adicionados, portanto, não há necessidade de criar a coleção antes de gravar os dados.
  • Os documentos podem ser criados usando POJOs, que usamos para criar cada documento de restaurante.
  • O método add() adiciona um documento a uma coleção com um ID gerado automaticamente, portanto, não precisamos especificar um ID exclusivo para cada restaurante.

Agora execute o aplicativo novamente e clique no botão "Adicionar itens aleatórios" no menu flutuante para invocar o código que você acabou de escrever:

95691e9b71ba55e3.png

Agora abra a IU do emuladores navegando para http: // localhost: 4000 em seu navegador. Em seguida, clique na guia Firestore e você verá os dados que acabou de adicionar:

Emulador de autenticação do Firebase

Esses dados são 100% locais para sua máquina. Na verdade, seu projeto real nem contém um banco de dados Firestore ainda! Isso significa que é seguro experimentar modificar e excluir esses dados sem consequências.

Parabéns, você acabou de gravar dados no Firestore! Na próxima etapa, aprenderemos como exibir esses dados no aplicativo.

Nesta etapa, aprenderemos como recuperar dados do Firestore e exibi-los em nosso aplicativo. A primeira etapa para ler dados do Firestore é criar uma Query . Modifique o método onCreate() :

        mFirestore = FirebaseUtil.getFirestore();

        // Get the 50 highest rated restaurants
        mQuery = mFirestore.collection("restaurants")
                .orderBy("avgRating", Query.Direction.DESCENDING)
                .limit(LIMIT);

Agora queremos ouvir a consulta, para que recebamos todos os documentos correspondentes e sejamos notificados de futuras atualizações em tempo real. Como nosso objetivo final é vincular esses dados a um RecyclerView , precisamos criar uma classe RecyclerView.Adapter para ouvir os dados.

Abra a classe FirestoreAdapter , que já foi parcialmente implementada. Primeiro, vamos fazer o adaptador implementar EventListener e definir a função onEvent para que possa receber atualizações para uma consulta do Firestore:

public abstract class FirestoreAdapter<VH extends RecyclerView.ViewHolder>
        extends RecyclerView.Adapter<VH>
        implements EventListener<QuerySnapshot> { // Add this "implements"

    // ...

    // Add this method
    @Override
    public void onEvent(QuerySnapshot documentSnapshots,
                        FirebaseFirestoreException e) {

        // Handle errors
        if (e != null) {
            Log.w(TAG, "onEvent:error", e);
            return;
        }

        // Dispatch the event
        for (DocumentChange change : documentSnapshots.getDocumentChanges()) {
            // Snapshot of the changed document
            DocumentSnapshot snapshot = change.getDocument();

            switch (change.getType()) {
                case ADDED:
                    // TODO: handle document added
                    break;
                case MODIFIED:
                    // TODO: handle document modified
                    break;
                case REMOVED:
                    // TODO: handle document removed
                    break;
            }
        }

        onDataChanged();
    }

  // ...
}

No carregamento inicial, o ouvinte receberá um evento ADDED para cada novo documento. Como o conjunto de resultados da consulta muda com o tempo, o ouvinte receberá mais eventos contendo as mudanças. Agora vamos terminar de implementar o ouvinte. Primeiro, adicione três novos métodos: onDocumentAdded , onDocumentModified e onDocumentRemoved :

    protected void onDocumentAdded(DocumentChange change) {
        mSnapshots.add(change.getNewIndex(), change.getDocument());
        notifyItemInserted(change.getNewIndex());
    }

    protected void onDocumentModified(DocumentChange change) {
        if (change.getOldIndex() == change.getNewIndex()) {
            // Item changed but remained in same position
            mSnapshots.set(change.getOldIndex(), change.getDocument());
            notifyItemChanged(change.getOldIndex());
        } else {
            // Item changed and changed position
            mSnapshots.remove(change.getOldIndex());
            mSnapshots.add(change.getNewIndex(), change.getDocument());
            notifyItemMoved(change.getOldIndex(), change.getNewIndex());
        }
    }

    protected void onDocumentRemoved(DocumentChange change) {
        mSnapshots.remove(change.getOldIndex());
        notifyItemRemoved(change.getOldIndex());
    }

Em seguida, chame esses novos métodos de onEvent :

    @Override
    public void onEvent(QuerySnapshot documentSnapshots,
                        FirebaseFirestoreException e) {

        // ...

        // Dispatch the event
        for (DocumentChange change : documentSnapshots.getDocumentChanges()) {
            // Snapshot of the changed document
            DocumentSnapshot snapshot = change.getDocument();

            switch (change.getType()) {
                case ADDED:
                    onDocumentAdded(change); // Add this line
                    break;
                case MODIFIED:
                    onDocumentModified(change); // Add this line
                    break;
                case REMOVED:
                    onDocumentRemoved(change); // Add this line
                    break;
            }
        }

        onDataChanged();
    }

Por fim, implemente o método startListening() para anexar o ouvinte:

    public void startListening() {
        if (mQuery != null && mRegistration == null) {
            mRegistration = mQuery.addSnapshotListener(this);
        }
    }

Agora o aplicativo está totalmente configurado para ler dados do Firestore. Execute o aplicativo novamente e você verá os restaurantes que adicionou na etapa anterior:

9e45f40faefce5d0.png

Agora volte para a IU do emulador em seu navegador e edite um dos nomes de restaurante. Você deve ver a mudança no aplicativo quase instantaneamente!

O aplicativo atualmente exibe os restaurantes mais bem avaliados em toda a coleção, mas em um aplicativo de restaurante real o usuário deseja classificar e filtrar os dados. Por exemplo, o aplicativo deve ser capaz de mostrar "Melhores restaurantes de frutos do mar na Filadélfia" ou "Pizza menos cara".

Clicar na barra branca na parte superior do aplicativo abre uma caixa de diálogo de filtros. Nesta seção, usaremos consultas do Firestore para fazer esta caixa de diálogo funcionar:

67898572a35672a5.png

Vamos editar o método onFilter() de MainActivity.java . Este método aceita um objeto Filters que é um objeto auxiliar que criamos para capturar a saída da caixa de diálogo de filtros. Vamos mudar este método para construir uma consulta a partir dos filtros:

    @Override
    public void onFilter(Filters filters) {
        // Construct query basic query
        Query query = mFirestore.collection("restaurants");

        // Category (equality filter)
        if (filters.hasCategory()) {
            query = query.whereEqualTo("category", filters.getCategory());
        }

        // City (equality filter)
        if (filters.hasCity()) {
            query = query.whereEqualTo("city", filters.getCity());
        }

        // Price (equality filter)
        if (filters.hasPrice()) {
            query = query.whereEqualTo("price", filters.getPrice());
        }

        // Sort by (orderBy with direction)
        if (filters.hasSortBy()) {
            query = query.orderBy(filters.getSortBy(), filters.getSortDirection());
        }

        // Limit items
        query = query.limit(LIMIT);

        // Update the query
        mQuery = query;
        mAdapter.setQuery(query);

        // Set header
        mCurrentSearchView.setText(Html.fromHtml(filters.getSearchDescription(this)));
        mCurrentSortByView.setText(filters.getOrderDescription(this));

        // Save filters
        mViewModel.setFilters(filters);
    }

No trecho acima, construir uma Query objeto anexando where e orderBy cláusulas para combinar os filtros dadas.

Execute o aplicativo novamente e selecione o seguinte filtro para mostrar os restaurantes de baixo preço mais populares:

7a67a8a400c80c50.png

Agora você deve ver uma lista filtrada de restaurantes contendo apenas opções de preço baixo:

a670188398c3c59.png

Se você chegou até aqui, agora você construiu um aplicativo de visualização de recomendações de restaurantes totalmente funcional no Firestore! Agora você pode classificar e filtrar restaurantes em tempo real. Nas próximas seções, postaremos comentários e segurança para o aplicativo.

Nesta seção, adicionaremos classificações ao aplicativo para que os usuários possam avaliar seus restaurantes favoritos (ou menos favoritos).

Coleções e subcoleções

Até agora, armazenamos todos os dados do restaurante em uma coleção de nível superior chamada "restaurantes". Quando um usuário avalia um restaurante, queremos adicionar um novo objeto de Rating aos restaurantes. Para esta tarefa, usaremos uma subcoleção. Você pode pensar em uma subcoleção como uma coleção anexada a um documento. Portanto, cada documento de restaurante terá uma subcoleção de classificações cheia de documentos de classificação. As subcoleções ajudam a organizar os dados sem sobrecarregar nossos documentos ou exigir consultas complexas.

Para acessar uma subcoleção, chame .collection() no documento pai:

CollectionReference subRef = mFirestore.collection("restaurants")
        .document("abc123")
        .collection("ratings");

Você pode acessar e consultar uma subcoleção da mesma forma que uma coleção de nível superior, não há limitações de tamanho ou alterações de desempenho. Você pode ler mais sobre o modelo de dados Firestore aqui .

Gravando dados em uma transação

Adicionar uma Rating à subcoleção adequada requer apenas a chamada de .add() , mas também precisamos atualizar a classificação média do objeto Restaurant e o número de classificações para refletir os novos dados. Se usarmos operações separadas para fazer essas duas alterações, há uma série de condições de corrida que podem resultar em dados obsoletos ou incorretos.

Para garantir que as classificações sejam adicionadas corretamente, usaremos uma transação para adicionar classificações a um restaurante. Esta transação irá realizar algumas ações:

  • Leia a classificação atual do restaurante e calcule a nova
  • Adicione a classificação à subcoleção
  • Atualize a avaliação média do restaurante e o número de avaliações

Abra RestaurantDetailActivity.java e implemente a função addRating :

    private Task<Void> addRating(final DocumentReference restaurantRef,
                                 final Rating rating) {
        // Create reference for new rating, for use inside the transaction
        final DocumentReference ratingRef = restaurantRef.collection("ratings")
                .document();

        // In a transaction, add the new rating and update the aggregate totals
        return mFirestore.runTransaction(new Transaction.Function<Void>() {
            @Override
            public Void apply(Transaction transaction)
                    throws FirebaseFirestoreException {

                Restaurant restaurant = transaction.get(restaurantRef)
                        .toObject(Restaurant.class);

                // Compute new number of ratings
                int newNumRatings = restaurant.getNumRatings() + 1;

                // Compute new average rating
                double oldRatingTotal = restaurant.getAvgRating() *
                        restaurant.getNumRatings();
                double newAvgRating = (oldRatingTotal + rating.getRating()) /
                        newNumRatings;

                // Set new restaurant info
                restaurant.setNumRatings(newNumRatings);
                restaurant.setAvgRating(newAvgRating);

                // Commit to Firestore
                transaction.set(restaurantRef, restaurant);
                transaction.set(ratingRef, rating);

                return null;
            }
        });
    }

A função addRating() retorna uma Task representa toda a transação. Na função onRating() , os ouvintes são adicionados à tarefa para responder ao resultado da transação.

Agora execute o aplicativo novamente e clique em um dos restaurantes, que deve abrir a tela de detalhes do restaurante. Clique no botão + para começar a adicionar um comentário. Adicione uma avaliação escolhendo algumas estrelas e inserindo algum texto.

78fa16cdf8ef435a.png

Clicar em Enviar iniciará a transação. Quando a transação for concluída, você verá sua avaliação exibida abaixo e uma atualização na contagem de avaliações do restaurante:

f9e670f40bd615b0.png

Parabéns! Agora você tem um aplicativo de avaliação de restaurantes social, local e móvel desenvolvido no Cloud Firestore. Ouvi dizer que esses são muito populares atualmente.

Até agora, não consideramos a segurança deste aplicativo. Como sabemos que os usuários só podem ler e gravar os próprios dados corretos? As bases de dados do Firestore são protegidas por um arquivo de configuração chamado Regras de segurança .

Abra o arquivo firestore.rules , você verá o seguinte:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      //
      // WARNING: These rules are insecure! We will replace them with
      // more secure rules later in the codelab
      //
      allow read, write: if request.auth != null;
    }
  }
}

Vamos alterar essas regras para evitar acessos ou alterações indesejadas aos dados, abra o arquivo firestore.rules e substitua o conteúdo pelo seguinte:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    // Determine if the value of the field "key" is the same
    // before and after the request.
    function isUnchanged(key) {
      return (key in resource.data)
        && (key in request.resource.data)
        && (resource.data[key] == request.resource.data[key]);
    }

    // Restaurants
    match /restaurants/{restaurantId} {
      // Any signed-in user can read
      allow read: if request.auth != null;

      // Any signed-in user can create
      // WARNING: this rule is for demo purposes only!
      allow create: if request.auth != null;

      // Updates are allowed if no fields are added and name is unchanged
      allow update: if request.auth != null
                    && (request.resource.data.keys() == resource.data.keys())
                    && isUnchanged("name");

      // Deletes are not allowed.
      // Note: this is the default, there is no need to explicitly state this.
      allow delete: if false;

      // Ratings
      match /ratings/{ratingId} {
        // Any signed-in user can read
        allow read: if request.auth != null;

        // Any signed-in user can create if their uid matches the document
        allow create: if request.auth != null
                      && request.resource.data.userId == request.auth.uid;

        // Deletes and updates are not allowed (default)
        allow update, delete: if false;
      }
    }
  }
}

Essas regras restringem o acesso para garantir que os clientes apenas façam alterações seguras. Por exemplo, as atualizações de um documento de restaurante só podem alterar as classificações, não o nome ou quaisquer outros dados imutáveis. As classificações só podem ser criadas se o ID do usuário corresponder ao usuário conectado, o que evita spoofing.

Para ler mais sobre as regras de segurança, visite a documentação .

Agora você criou um aplicativo completo no Firestore. Você aprendeu sobre os recursos mais importantes do Firestore, incluindo:

  • Documentos e coleções
  • Ler e escrever dados
  • Classificação e filtragem com consultas
  • Subcoleções
  • Transações

Saber mais

Para continuar aprendendo sobre o Firestore, aqui estão alguns bons lugares para começar:

O aplicativo de restaurante neste codelab foi baseado no aplicativo de exemplo "Friendly Eats". Você pode navegar pelo código-fonte desse aplicativo aqui .

Opcional: implantar na produção

Até agora, este aplicativo usou apenas o Firebase Emulator Suite. Se você quiser aprender como implantar este aplicativo em um projeto Firebase real, continue na próxima etapa.

Até agora, este aplicativo tem sido totalmente local, todos os dados estão contidos no Firebase Emulator Suite. Nesta seção, você aprenderá como configurar seu projeto Firebase para que este aplicativo funcione em produção.

Firebase Authentication

No console do Firebase, vá para a seção Autenticação e navegue até a guia Provedores de login .

Ative o método de login de e-mail:

334ef7f6ff4da4ce.png

Firestore

Criar banco de dados

Navegue até a seção Firestore do console e clique em Criar banco de dados :

  1. Quando questionado sobre as regras de segurança, escolha iniciar no modo bloqueado , atualizaremos essas regras em breve.
  2. Escolha o local do banco de dados que deseja usar para seu aplicativo. Observe que a seleção de um local de banco de dados é uma decisão permanente e para alterá-lo você terá que criar um novo projeto. Para obter mais informações sobre como escolher um local para o projeto, consulte a documentação .

Regras de implantação

Para implantar as regras de segurança que você escreveu anteriormente, execute o seguinte comando no diretório codelab:

$ firebase deploy --only firestore:rules

Isso implantará o conteúdo de firestore.rules em seu projeto, que você pode confirmar navegando até a guia Regras no console.

Implantar índices

O aplicativo FriendlyEats tem classificação e filtragem complexas que requerem uma série de índices compostos personalizados. Eles podem ser criados manualmente no console do Firebase, mas é mais simples escrever suas definições no arquivo firestore.indexes.json e implantá-los usando a Firebase CLI.

Se você abrir o arquivo firestore.indexes.json , verá que os índices necessários já foram fornecidos:

{
  "indexes": [
    {
      "collectionId": "restaurants",
      "queryScope": "COLLECTION",
      "fields": [
        { "fieldPath": "city", "mode": "ASCENDING" },
        { "fieldPath": "avgRating", "mode": "DESCENDING" }
      ]
    },
    {
      "collectionId": "restaurants",
      "queryScope": "COLLECTION",
      "fields": [
        { "fieldPath": "category", "mode": "ASCENDING" },
        { "fieldPath": "avgRating", "mode": "DESCENDING" }
      ]
    },
    {
      "collectionId": "restaurants",
      "queryScope": "COLLECTION",
      "fields": [
        { "fieldPath": "price", "mode": "ASCENDING" },
        { "fieldPath": "avgRating", "mode": "DESCENDING" }
      ]
    },
    {
      "collectionId": "restaurants",
      "queryScope": "COLLECTION",
      "fields": [
        { "fieldPath": "city", "mode": "ASCENDING" },
        { "fieldPath": "numRatings", "mode": "DESCENDING" }
      ]
    },
    {
      "collectionId": "restaurants",
      "queryScope": "COLLECTION",
      "fields": [
        { "fieldPath": "category", "mode": "ASCENDING" },
        { "fieldPath": "numRatings", "mode": "DESCENDING" }
      ]
    },
    {
      "collectionId": "restaurants",
      "queryScope": "COLLECTION",
      "fields": [
        { "fieldPath": "price", "mode": "ASCENDING" },
        { "fieldPath": "numRatings", "mode": "DESCENDING" }
      ]
    },
    {
      "collectionId": "restaurants",
      "queryScope": "COLLECTION",
      "fields": [
        { "fieldPath": "city", "mode": "ASCENDING" },
        { "fieldPath": "price", "mode": "ASCENDING" }
      ]
    },
    {
      "collectionId": "restaurants",
      "fields": [
        { "fieldPath": "category", "mode": "ASCENDING" },
        { "fieldPath": "price", "mode": "ASCENDING" }
      ]
    }
  ],
  "fieldOverrides": []
}

Para implantar esses índices, execute o seguinte comando:

$ firebase deploy --only firestore:indexes

Observe que a criação do índice não é instantânea, você pode monitorar o progresso no console do Firebase.

Configure o aplicativo

Na classe FirebaseUtil , configuramos o Firebase SDK para se conectar aos emuladores no modo de depuração:

public class FirebaseUtil {

    /** Use emulators only in debug builds **/
    private static final boolean sUseEmulators = BuildConfig.DEBUG;

    // ...
}

Se quiser testar seu aplicativo com o projeto real do Firebase, você pode:

  1. Crie o aplicativo no modo de lançamento e execute-o em um dispositivo.
  2. Altere temporariamente sUseEmulators para false e execute o aplicativo novamente.

Observe que pode ser necessário sair do aplicativo e entrar novamente para conectar-se corretamente à produção.