Codelab per Android su Cloud Firestore

1. Panoramica

Obiettivi

In questo codelab creerai un'app di consigli sui ristoranti su Android basata su Cloud Firestore. Imparerai come:

  • Leggere e scrivere dati in Firestore da un'app per Android
  • Ascolta le modifiche ai dati di Firestore in tempo reale
  • Utilizzare Firebase Authentication e le regole di sicurezza per proteggere i dati di Firestore
  • Scrivere query Firestore complesse

Prerequisiti

Prima di iniziare questo codelab, assicurati di avere:

  • Android Studio Flamingo o versioni successive
  • Un emulatore Android con API 19 o versioni successive
  • Node.js versione 16 o successive
  • Java versione 17 o successive

2. Crea un progetto Firebase

  1. Accedi alla console Firebase con il tuo Account Google.
  2. Nella Console Firebase, fai clic su Aggiungi progetto.
  3. Come mostrato nello screenshot di seguito, inserisci un nome per il progetto Firebase (ad esempio "Friendly Eats") e fai clic su Continua.

9d2f625aebcab6af.png

  1. Ti potrebbe essere chiesto di attivare Google Analytics. Ai fini di questo codelab, la tua selezione non ha importanza.
  2. Dopo circa un minuto, il progetto Firebase sarà pronto. Fai clic su Continua.

3. Configura il progetto di esempio

Scarica il codice

Esegui il seguente comando per clonare il codice di esempio per questo codelab. Verrà creata una cartella denominata friendlyeats-android sul computer:

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

Se non hai git sul tuo computer, puoi anche scaricare il codice direttamente da GitHub.

Aggiungi la configurazione di Firebase

  1. Nella console Firebase, seleziona Panoramica del progetto nel menu di navigazione a sinistra. Fai clic sul pulsante Android per selezionare la piattaforma. Quando ti viene chiesto un nome del pacchetto, utilizza com.google.firebase.example.fireeats

73d151ed16016421.png

  1. Fai clic su Registra app e segui le istruzioni per scaricare il file google-services.json e spostarlo nella cartella app/ del codice che hai appena scaricato. Fai quindi clic su Avanti.

Importa il progetto

Apri Android Studio. Fai clic su File > Nuovo > Importa progetto e seleziona la cartella friendlyeats-android.

4. Configura gli emulatori Firebase

In questo codelab utilizzerai Firebase Emulator Suite per emulare localmente Cloud Firestore e altri servizi Firebase. In questo modo, avrai a disposizione un ambiente di sviluppo locale sicuro, veloce e senza costi per creare la tua app.

Installa l'interfaccia a riga di comando di Firebase

Innanzitutto devi installare l'interfaccia a riga di comando di Firebase. Se utilizzi macOS o Linux, puoi eseguire il seguente comando cURL:

curl -sL https://firebase.tools | bash

Se utilizzi Windows, leggi le istruzioni di installazione per ottenere un file binario autonomo o per installare il programma tramite npm.

Dopo aver installato la CLI, l'esecuzione di firebase --version dovrebbe segnalare una versione di 9.0.0 o successiva:

$ firebase --version
9.0.0

Accedi

Esegui firebase login per collegare la CLI al tuo Account Google. Si aprirà una nuova finestra del browser per completare la procedura di accesso. Assicurati di scegliere lo stesso account utilizzato in precedenza per creare il progetto Firebase.

Dalla cartella friendlyeats-android, esegui firebase use --add per collegare il progetto locale al progetto Firebase. Segui le istruzioni per selezionare il progetto creato in precedenza e, se ti viene chiesto di scegliere un alias, inserisci default.

5. Esegui l'app

Ora è il momento di eseguire Firebase Emulator Suite e l'app per Android FriendlyEats per la prima volta.

Esegui gli emulatori

Nel terminale, dalla directory friendlyeats-android, esegui firebase emulators:start per avviare gli emulatori Firebase. Dovresti vedere log come questo:

$ 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.

Ora hai un ambiente di sviluppo locale completo in esecuzione sulla tua macchina. Assicurati di lasciare in esecuzione questo comando per il resto del codelab, perché la tua app per Android dovrà connettersi agli emulatori.

Collega l'app agli emulatori

Apri i file util/FirestoreInitializer.kt e util/AuthInitializer.kt in Android Studio. Questi file contengono la logica per collegare gli SDK Firebase agli emulatori locali in esecuzione sulla tua macchina all'avvio dell'applicazione.

Nel metodo create() della classe FirestoreInitializer, esamina questo frammento di codice:

    // Use emulators only in debug builds
    if (BuildConfig.DEBUG) {
        firestore.useEmulator(FIRESTORE_EMULATOR_HOST, FIRESTORE_EMULATOR_PORT)
    }

Utilizziamo BuildConfig per assicurarci di connetterci agli emulatori solo quando la nostra app è in esecuzione in modalità debug. Quando compiliamo l'app in modalità release, questa condizione sarà falsa.

Possiamo vedere che utilizza il metodo useEmulator(host, port) per collegare l'SDK Firebase all'emulatore Firestore locale. Nell'app utilizzeremo FirebaseUtil.getFirestore() per accedere a questa istanza di FirebaseFirestore per assicurarci di connetterci sempre all'emulatore Firestore quando l'app viene eseguita in modalità debug.

Esegui l'app

Se hai aggiunto correttamente il file google-services.json, il progetto dovrebbe ora compilarsi. In Android Studio, fai clic su Build > Ricompila progetto e assicurati che non ci siano errori rimanenti.

In Android Studio, Esegui l'app sull'emulatore Android. All'inizio viene visualizzata una schermata "Accedi". Puoi utilizzare qualsiasi indirizzo email e password per accedere all'app. Questa procedura di accesso si connette all'emulatore di Firebase Authentication, pertanto non vengono trasmesse credenziali reali.

Ora apri l'interfaccia utente degli emulatori andando all'indirizzo http://localhost:4000 nel browser web. Quindi fai clic sulla scheda Autenticazione e dovresti vedere l'account appena creato:

Emulatore Firebase Auth

Una volta completata la procedura di accesso, dovresti vedere la schermata Home dell'app:

de06424023ffb4b9.png

A breve aggiungeremo alcuni dati per completare la schermata Home.

6. Scrivere dati in Firestore

In questa sezione scriveremo alcuni dati in Firestore per compilare la schermata Home attualmente vuota.

L'oggetto modello principale nella nostra app è un ristorante (vedi model/Restaurant.kt). I dati di Firestore sono suddivisi in documenti, raccolte e sottoraccolte. Archivieremo ogni ristorante come documento in una raccolta di primo livello denominata "restaurants". Per scoprire di più sul modello di dati di Firestore, leggi la sezione relativa a documenti e raccolte nella documentazione.

A scopo dimostrativo, aggiungeremo all'app la funzionalità per creare dieci ristoranti casuali quando facciamo clic sul pulsante "Aggiungi elementi casuali" nel menu extra. Apri il file MainFragment.kt e sostituisci i contenuti nel metodo onAddItemsClicked() con:

    private fun onAddItemsClicked() {
        val restaurantsRef = firestore.collection("restaurants")
        for (i in 0..9) {
            // Create random restaurant / ratings
            val randomRestaurant = RestaurantUtil.getRandom(requireContext())

            // Add restaurant
            restaurantsRef.add(randomRestaurant)
        }
    }

Ecco alcuni aspetti importanti da tenere presenti in merito al codice riportato sopra:

  • Abbiamo iniziato recuperando un riferimento alla raccolta "restaurants". Le raccolte vengono create implicitamente quando vengono aggiunti i documenti, quindi non è stato necessario creare la raccolta prima di scrivere i dati.
  • I documenti possono essere creati utilizzando le classi di dati Kotlin, che utilizziamo per creare ogni documento del ristorante.
  • Il metodo add() aggiunge un documento a una raccolta con un ID generato automaticamente, quindi non è stato necessario specificare un ID univoco per ogni ristorante.

Ora esegui di nuovo l'app e fai clic sul pulsante "Aggiungi elementi casuali" nel menu extra (nell'angolo in alto a destra) per richiamare il codice che hai appena scritto:

95691e9b71ba55e3.png

Ora apri l'interfaccia utente degli emulatori andando all'indirizzo http://localhost:4000 nel browser web. Quindi, fai clic sulla scheda Firestore e dovresti vedere i dati appena aggiunti:

Emulatore Firebase Auth

Questi dati sono completamente locali per la tua macchina. Infatti, il tuo progetto reale non contiene ancora un database Firestore. Ciò significa che puoi sperimentare la modifica e l'eliminazione di questi dati senza conseguenze.

Congratulazioni, hai appena scritto dati in Firestore. Nel passaggio successivo impareremo a visualizzare questi dati nell'app.

7. Visualizzare i dati di Firestore

In questo passaggio apprenderemo come recuperare i dati da Firestore e visualizzarli nella nostra app. Il primo passaggio per leggere i dati da Firestore è creare un Query. Apri il file MainFragment.kt e aggiungi il codice seguente all'inizio del metodo onViewCreated():

        // Firestore
        firestore = Firebase.firestore

        // Get the 50 highest rated restaurants
        query = firestore.collection("restaurants")
            .orderBy("avgRating", Query.Direction.DESCENDING)
            .limit(LIMIT.toLong())

Ora vogliamo ascoltare la query per ottenere tutti i documenti corrispondenti e ricevere notifiche di aggiornamenti futuri in tempo reale. Poiché il nostro obiettivo finale è associare questi dati a un RecyclerView, dobbiamo creare un'apposita classe per ascoltarli.RecyclerView.Adapter

Apri il corso FirestoreAdapter, che è già stato implementato parzialmente. Innanzitutto, facciamo in modo che l'adattatore implementi EventListener e definisca la funzione onEvent in modo che possa ricevere aggiornamenti a una query Firestore:

abstract class FirestoreAdapter<VH : RecyclerView.ViewHolder>(private var query: Query?) :
        RecyclerView.Adapter<VH>(),
        EventListener<QuerySnapshot> { // Add this implements
    
    // ...

    // Add this method
    override fun onEvent(documentSnapshots: QuerySnapshot?, e: FirebaseFirestoreException?) {
        
        // Handle errors
        if (e != null) {
            Log.w(TAG, "onEvent:error", e)
            return
        }

        // Dispatch the event
        if (documentSnapshots != null) {
            for (change in documentSnapshots.documentChanges) {
                // snapshot of the changed document
                when (change.type) {
                    DocumentChange.Type.ADDED -> {
                        // TODO: handle document added
                    }
                    DocumentChange.Type.MODIFIED -> {
                        // TODO: handle document changed
                    }
                    DocumentChange.Type.REMOVED -> {
                        // TODO: handle document removed
                    }
                }
            }
        }

        onDataChanged()
    }
    
    // ...
}

Al caricamento iniziale, l'ascoltatore riceverà un evento ADDED per ogni nuovo documento. Poiché il set di risultati della query cambia nel tempo, l'ascoltatore riceverà più eventi contenenti le modifiche. Ora completiamo l'implementazione dell'ascoltatore. Aggiungi prima tre nuovi metodi: onDocumentAdded, onDocumentModified e onDocumentRemoved:

    private fun onDocumentAdded(change: DocumentChange) {
        snapshots.add(change.newIndex, change.document)
        notifyItemInserted(change.newIndex)
    }

    private fun onDocumentModified(change: DocumentChange) {
        if (change.oldIndex == change.newIndex) {
            // Item changed but remained in same position
            snapshots[change.oldIndex] = change.document
            notifyItemChanged(change.oldIndex)
        } else {
            // Item changed and changed position
            snapshots.removeAt(change.oldIndex)
            snapshots.add(change.newIndex, change.document)
            notifyItemMoved(change.oldIndex, change.newIndex)
        }
    }

    private fun onDocumentRemoved(change: DocumentChange) {
        snapshots.removeAt(change.oldIndex)
        notifyItemRemoved(change.oldIndex)
    }

Quindi chiama questi nuovi metodi da onEvent:

    override fun onEvent(documentSnapshots: QuerySnapshot?, e: FirebaseFirestoreException?) {

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

        // Dispatch the event
        if (documentSnapshots != null) {
            for (change in documentSnapshots.documentChanges) {
                // snapshot of the changed document
                when (change.type) {
                    DocumentChange.Type.ADDED -> {
                        onDocumentAdded(change) // Add this line
                    }
                    DocumentChange.Type.MODIFIED -> {
                        onDocumentModified(change) // Add this line
                    }
                    DocumentChange.Type.REMOVED -> {
                        onDocumentRemoved(change) // Add this line
                    }
                }
            }
        }

        onDataChanged()
    }

Infine, implementa il metodo startListening() per collegare l'ascoltatore:

    fun startListening() {
        if (registration == null) {
            registration = query.addSnapshotListener(this)
        }
    }

Ora l'app è completamente configurata per leggere i dati da Firestore. Esegui di nuovo l'app e dovresti vedere i ristoranti che hai aggiunto nel passaggio precedente:

9e45f40faefce5d0.png

Ora torna all'interfaccia utente dell'emulatore nel browser e modifica uno dei nomi dei ristoranti. Dovresti vedere la modifica nell'app quasi istantaneamente.

8. Ordinare e filtrare i dati

Al momento l'app mostra i ristoranti con le valutazioni più alte nell'intera raccolta, ma in un'app di ristoranti reale l'utente vorrebbe ordinare e filtrare i dati. Ad esempio, l'app dovrebbe essere in grado di mostrare "I migliori ristoranti di pesce a Philadelphia" o "La pizza più economica".

Se fai clic sulla barra bianca nella parte superiore dell'app, viene visualizzata una finestra di dialogo dei filtri. In questa sezione utilizzeremo le query Firestore per far funzionare questa finestra di dialogo:

67898572a35672a5.png

Modifichiamo il metodo onFilter() di MainFragment.kt. Questo metodo accetta un oggetto Filters, ovvero un oggetto di supporto che abbiamo creato per acquisire l'output della finestra di dialogo dei filtri. Modificheremo questo metodo per creare una query dai filtri:

    override fun onFilter(filters: Filters) {
        // Construct query basic query
        var query: Query = firestore.collection("restaurants")

        // Category (equality filter)
        if (filters.hasCategory()) {
            query = query.whereEqualTo(Restaurant.FIELD_CATEGORY, filters.category)
        }

        // City (equality filter)
        if (filters.hasCity()) {
            query = query.whereEqualTo(Restaurant.FIELD_CITY, filters.city)
        }

        // Price (equality filter)
        if (filters.hasPrice()) {
            query = query.whereEqualTo(Restaurant.FIELD_PRICE, filters.price)
        }

        // Sort by (orderBy with direction)
        if (filters.hasSortBy()) {
            query = query.orderBy(filters.sortBy.toString(), filters.sortDirection)
        }

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

        // Update the query
        adapter.setQuery(query)

        // Set header
        binding.textCurrentSearch.text = HtmlCompat.fromHtml(
            filters.getSearchDescription(requireContext()),
            HtmlCompat.FROM_HTML_MODE_LEGACY
        )
        binding.textCurrentSortBy.text = filters.getOrderDescription(requireContext())

        // Save filters
        viewModel.filters = filters
    }

Nello snippet riportato sopra, viene creato un oggetto Query associando clausole where e orderBy in modo che corrispondano ai filtri specificati.

Esegui di nuovo l'app e seleziona il seguente filtro per visualizzare i ristoranti economici più apprezzati:

7a67a8a400c80c50.png

Dovresti visualizzare un elenco filtrato di ristoranti contenente solo opzioni a basso prezzo:

a670188398c3c59.png

Se hai raggiunto questo punto, hai creato un'app di visualizzazione dei consigli sui ristoranti completamente funzionante su Firestore. Ora puoi ordinare e filtrare i ristoranti in tempo reale. Nelle prossime sezioni aggiungeremo recensioni ai ristoranti e regole di sicurezza all'app.

9. Organizzare i dati nelle sottocollezioni

In questa sezione aggiungeremo le valutazioni all'app in modo che gli utenti possano recensire i loro ristoranti preferiti (o meno preferiti).

Raccolte e raccolte secondarie

Finora abbiamo archiviato tutti i dati dei ristoranti in una raccolta di primo livello denominata "ristoranti". Quando un utente valuta un ristorante, vogliamo aggiungere un nuovo oggetto Rating ai ristoranti. Per questa attività utilizzeremo una sottoraccolta. Puoi considerare una sottoraccolta come una raccolta collegata a un documento. Pertanto, ogni documento del ristorante avrà una sottoraccolta di valutazioni contenente documenti di valutazione. Le sottocollezioni consentono di organizzare i dati senza aumentare il volume dei documenti o richiedere query complesse.

Per accedere a una sottoraccolta, chiama .collection() nel documento principale:

val subRef = firestore.collection("restaurants")
        .document("abc123")
        .collection("ratings")

Puoi accedere a una sottoraccolta ed eseguire query come faresti con una raccolta di primo livello. Non ci sono limitazioni di dimensioni o modifiche alle prestazioni. Puoi scoprire di più sul modello di dati di Firestore qui.

Scrittura dei dati in una transazione

L'aggiunta di un Rating alla sottoraccolta corretta richiede solo la chiamata a .add(), ma dobbiamo anche aggiornare la valutazione media e il numero di valutazioni dell'oggetto Restaurant in modo che riflettano i nuovi dati. Se utilizziamo operazioni separate per apportare queste due modifiche, esistono una serie di condizioni di gara che potrebbero comportare dati obsoleti o errati.

Per assicurarci che le valutazioni vengano aggiunte correttamente, utilizzeremo una transazione per aggiungere le valutazioni a un ristorante. Questa transazione eseguirà alcune azioni:

  • Leggi la valutazione attuale del ristorante e calcola quella nuova
  • Aggiungi la valutazione alla sottoraccolta
  • Aggiornare la valutazione media e il numero di valutazioni del ristorante

Apri RestaurantDetailFragment.kt e implementa la funzione addRating:

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

        // In a transaction, add the new rating and update the aggregate totals
        return firestore.runTransaction { transaction ->
            val restaurant = transaction.get(restaurantRef).toObject<Restaurant>()
                ?: throw Exception("Restaurant not found at ${restaurantRef.path}")

            // Compute new number of ratings
            val newNumRatings = restaurant.numRatings + 1

            // Compute new average rating
            val oldRatingTotal = restaurant.avgRating * restaurant.numRatings
            val newAvgRating = (oldRatingTotal + rating.rating) / newNumRatings

            // Set new restaurant info
            restaurant.numRatings = newNumRatings
            restaurant.avgRating = newAvgRating

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

            null
        }
    }

La funzione addRating() restituisce un Task che rappresenta l'intera transazione. Nella funzione onRating(), gli ascoltatori vengono aggiunti all'attività per rispondere al risultato della transazione.

Ora Esegui di nuovo l'app e fai clic su uno dei ristoranti, per visualizzare la relativa schermata dei dettagli. Fai clic sul pulsante + per iniziare ad aggiungere una recensione. Aggiungi una recensione scegliendo un numero di stelle e inserendo del testo.

78fa16cdf8ef435a.png

Se fai clic su Invia, la transazione verrà avviata. Al termine della transazione, vedrai la tua recensione sotto e un aggiornamento del numero di recensioni del ristorante:

f9e670f40bd615b0.png

Congratulazioni! Ora hai un'app mobile di recensioni di ristoranti social e locale basata su Cloud Firestore. Ho sentito che sono molto popolari in questo periodo.

10. Proteggi i tuoi dati

Finora non abbiamo preso in considerazione la sicurezza di questa applicazione. Come facciamo a sapere che gli utenti possono leggere e scrivere solo i propri dati corretti? I database Firestore sono protetti da un file di configurazione denominato Regole di sicurezza.

Apri il file firestore.rules e sostituisci i contenuti con quanto segue:

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

Queste regole limitano l'accesso per garantire che i client apportino solo modifiche sicure. Ad esempio, gli aggiornamenti di un documento del ristorante possono modificare solo le valutazioni, non il nome o altri dati immutabili. Le valutazioni possono essere create solo se l'ID utente corrisponde all'utente che ha eseguito l'accesso, il che impedisce lo spoofing.

Per scoprire di più sulle regole di sicurezza, consulta la documentazione.

11. Conclusione

Ora hai creato un'app completa su Firestore. Hai appreso le funzionalità più importanti di Firestore, tra cui:

  • Documenti e raccolte
  • Lettura e scrittura dei dati
  • Ordinamento e filtri con le query
  • Sottoraccolte
  • Transazioni

Scopri di più

Per continuare a scoprire di più su Firestore, ecco alcuni buoni punti di partenza:

L'app per ristoranti in questo codelab si basa sull'applicazione di esempio "Friendly Eats". Puoi sfogliare il codice sorgente dell'app qui.

(Facoltativo) Esegui il deployment in produzione

Finora questa app ha utilizzato solo Firebase Emulator Suite. Se vuoi scoprire come eseguire il deployment di questa app in un progetto Firebase reale, vai al passaggio successivo.

12. (Facoltativo) Esegui il deployment dell'app

Finora questa app è stata interamente locale, tutti i dati sono contenuti in Firebase Emulator Suite. In questa sezione imparerai a configurare il progetto Firebase in modo che l'app funzioni in produzione.

Firebase Authentication

Nella console Firebase, vai alla sezione Autenticazione e fai clic su Inizia. Vai alla scheda Metodo di accesso e seleziona l'opzione Email/Password in Provider nativi.

Attiva il metodo di accesso Email/password e fai clic su Salva.

sign-in-providers.png

Firestore

Crea database

Vai alla sezione Database Firestore della console e fai clic su Crea database:

  1. Quando ti viene chiesto di scegliere le regole di sicurezza, seleziona l'opzione Modalità di produzione. A breve aggiorneremo queste regole.
  2. Scegli la posizione del database che vuoi utilizzare per la tua app. Tieni presente che la selezione di una posizione del database è una decisione definitiva e per modificarla dovrai creare un nuovo progetto. Per ulteriori informazioni sulla scelta della posizione di un progetto, consulta la documentazione.

Regole di deployment

Per eseguire il deployment delle regole di sicurezza che hai scritto in precedenza, esegui il seguente comando nella directory del codelab:

$ firebase deploy --only firestore:rules

In questo modo, i contenuti di firestore.rules verranno distribuiti nel tuo progetto. Puoi confermare questa operazione nella scheda Regole della console.

Esegui il deployment degli indici

L'app FriendlyEats ha un sistema di ordinamento e filtri complesso che richiede una serie di indici composti personalizzati. Possono essere creati manualmente nella console Firebase, ma è più semplice scrivere le relative definizioni nel file firestore.indexes.json e eseguirne il deployment utilizzando Firebase CLI.

Se apri il file firestore.indexes.json, vedrai che gli indici richiesti sono già stati forniti:

{
  "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": []
}

Per eseguire il deployment di questi indici, esegui il seguente comando:

$ firebase deploy --only firestore:indexes

Tieni presente che la creazione dell'indice non è istantanea, ma puoi monitorare l'avanzamento nella console Firebase.

Configura l'app

Nei file util/FirestoreInitializer.kt e util/AuthInitializer.kt abbiamo configurato l'SDK Firebase per connettersi agli emulatori in modalità di debug:

    override fun create(context: Context): FirebaseFirestore {
        val firestore = Firebase.firestore
        // Use emulators only in debug builds
        if (BuildConfig.DEBUG) {
            firestore.useEmulator(FIRESTORE_EMULATOR_HOST, FIRESTORE_EMULATOR_PORT)
        }
        return firestore
    }

Se vuoi testare la tua app con il tuo progetto Firebase reale, puoi:

  1. Compila l'app in modalità di rilascio ed eseguila su un dispositivo.
  2. Sostituisci temporaneamente BuildConfig.DEBUG con false ed esegui di nuovo l'app.

Tieni presente che potresti dover uscire dall'app e accedere di nuovo per connetterti correttamente alla produzione.