Cloud Firestore Android Codelab

1. Visão Geral

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 a partir 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
  • Versão Node.js 10 ou superior
  • Java versão 8 ou superior

2. Crie um projeto Firebase

  1. Entre no console de Firebase com a sua conta Google.
  2. Na consola Firebase , clique em Adicionar projeto.
  3. Como mostrado na captura de tela abaixo, digite um nome para o seu projeto Firebase (por exemplo, "amigável Come"), 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 Continuar.

3. Configure o projeto de amostra

Baixe o código

Execute o seguinte comando para clonar o código de amostra para este codelab. Isto irá 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, você também pode baixar o código diretamente do GitHub.

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

Adicionar configuração do Firebase

  1. Na consola Firebase , selecione Visão geral do projeto na navegação à esquerda. Clique no botão Android para selecionar a plataforma. Quando solicitado a fornecer um nome de pacote de uso com.google.firebase.example.fireeats

73d151ed16016421.png

  1. Clique Register App e siga as instruções para baixar o google-services.json arquivo e movê-lo para o app/ pasta do código de exemplo. Em seguida, clique em Avançar.

4. Configure os emuladores do Firebase

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

Instale a Firebase CLI

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

npm install -g firebase-tools

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

Uma vez que você instalou o CLI, correndo firebase --version deve reportar uma versão 9.0.0 ou superior:

$ firebase --version
9.0.0

Conecte-se

Execute firebase login para conectar o CLI para a sua conta 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.

De dentro do friendlyeats-android pasta de execução firebase use --add para conectar seu projeto local ao seu projeto Firebase. Siga as instruções para selecionar o projeto que você criou anteriormente e se solicitado a escolher um alias de entrar default .

5. Execute o aplicativo

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

Execute os emuladores

Em seu terminal de dentro do friendlyeats-android diretório prazo firebase emulators:start para iniciar o Firebase emuladores. 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 se certificar de que só se conectar aos emuladores quando nosso aplicativo está sendo executado em debug modo. Quando compilar o aplicativo em release modo esta condição será falso.

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

    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 useEmulator(host, port) método para conectar o Firebase SDK para o emulador locais Firestore. Durante todo o aplicativo, vamos utilizar FirebaseUtil.getFirestore() para acessar a instância de FirebaseFirestore assim temos a certeza de que estamos sempre a conexão com o emulador Firestore quando rodando em debug modo.

Execute o aplicativo

Se você adicionou o google-services.json arquivo corretamente, o projeto agora deve compilar. Em Android Estúdio clique em Construir> Reconstruir Projeto e garantir que não há erros restantes.

Em Android Estúdio Execute o aplicativo no 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 o UI Emuladores navegando até http: // localhost: 4000 no seu navegador web. Em seguida, clique na guia Autenticação e você deve ver a conta que você 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.

6. Grave dados no Firestore

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

O principal objeto de modelo em nosso aplicativo é um restaurante (ver model/Restaurant.java ). Os dados do Firestore são divididos em documentos, coleções e subcoleções. Nós iremos guardar cada restaurante como um documento em uma coleção de nível superior chamada "restaurants" . Para saber mais sobre o modelo de dados Firestore, ler sobre documentos e coleções em 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 onAddItemsClicked() método:

    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 para o "restaurants" coleção. 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 add() método adiciona um documento a uma coleção com um ID gerado automaticamente, por isso, não precisa especificar um ID único 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 o UI Emuladores navegando até http: // localhost: 4000 no seu navegador web. Em seguida, clique na guia Firestore e você deve ver os dados que você 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.

7. Exibir dados do Firestore

Nesta etapa, aprenderemos como recuperar dados do Firestore e exibi-los em nosso aplicativo. O primeiro passo para a leitura de dados a partir Firestore é criar uma Query . Modificar o onCreate() Método:

        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. Porque o nosso objetivo final é ligar esses dados para um RecyclerView , precisamos criar um RecyclerView.Adapter classe para ouvir os dados.

Abra o FirestoreAdapter classe, que foi parcialmente já implementadas. Primeiro, vamos fazer o adaptador implementar EventListener e definir o onEvent função para que ele possa receber atualizações para uma consulta 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 vai receber uma ADDED evento 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 em 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, chamar 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();
    }

Finalmente implementar o startListening() método 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ê deve ver os restaurantes que você 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!

8. Classifique e filtre os dados

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

Do Vamos editar onFilter() método de MainActivity.java . Este método aceita um Filters objeto que é um objeto auxiliar que criamos para capturar a saída da caixa de diálogo 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 mais populares restaurantes de baixo preço:

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.

9. Organize os dados em subcoleções

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 taxas de um restaurante que queremos adicionar um novo Rating objeto para os 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 um subcoleção, chamada .collection() no documento parent:

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

Adicionando uma Rating do subcoleção adequada requer apenas chamando .add() , mas também precisamos atualizar o Restaurant classificação média de objeto e 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

Abrir RestaurantDetailActivity.java e implementar o addRating função:

    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;
            }
        });
    }

O addRating() função retorna um Task que representa toda a transação. No onRating() ouvintes de função são adicionados à tarefa de responder ao resultado da transação.

Agora, execute o aplicativo novamente e clique em um dos restaurantes, que devem abrir a tela de restaurante detalhes. 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

Bater Apresentar vai começar 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.

10. Proteja seus dados

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? Firestore datbases são garantidos por um arquivo de configuração chamado regras de segurança .

Abra o firestore.rules arquivo, você deve 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 estas regras para evitar que dados indesejados acesss ou alterações, abra o firestore.rules arquivo e substituir o conteúdo com o 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 avaliaçõ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 Regras de Segurança, visite a documentação .

11. Conclusã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 procurar o código fonte para esse 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.

12. (Opcional) Implante seu aplicativo

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 Firebase consle vá para a seção de autenticação e navegue até o Sign-in guia Provedores .

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 solicitado sobre Regras de Segurança optar por iniciar no modo bloqueado, vamos atualizar essas regras em breve.
  2. Escolha o local do banco de dados que deseja usar para seu aplicativo. Note-se que a seleção de uma localização de banco de dados é uma decisão permanente e para mudá-lo você terá que criar um novo projeto. Para mais informações sobre a escolha de um local do 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

Isto irá implantar o conteúdo de firestore.rules ao 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. Estes podem ser criados com a mão no console Firebase mas é mais simples para escrever suas definições na firestore.indexes.json arquivo e implantá-los usando o Firebase CLI.

Se você abrir o firestore.indexes.json arquivo, você 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

No FirebaseUtil classe que configurou o Firebase SDK para se conectar à emuladores quando em 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. Temporariamente mudar sUseEmulators a false e executar o aplicativo novamente.

Note que você pode precisar Sair do aplicativo e faça login novamente, a fim de conectar corretamente à produção.