Cloud Firestore-Android-Codelab

1. Übersicht

Ziele

In diesem Codelab erstellen Sie eine Restaurantempfehlungs-App für Android, die von Cloud Firestore unterstützt wird. Nach Abschluss können Sie:

  • Daten aus einer Android-App in Firestore lesen und schreiben
  • Änderungen an Firestore-Daten in Echtzeit überwachen
  • Firestore-Daten mit Firebase Authentication und Sicherheitsregeln schützen
  • Komplexe Firestore-Abfragen schreiben

Vorbereitung

Bevor Sie mit diesem Codelab beginnen, müssen Sie Folgendes haben:

  • Android Studio Flamingo oder höher
  • Ein Android-Emulator mit API‑Level 19 oder höher
  • Node.js-Version 16 oder höher
  • Java-Version 17 oder höher

2. Firebase-Projekt erstellen

  1. Melden Sie sich mit Ihrem Google-Konto in der Firebase Console an.
  2. Klicken Sie auf die Schaltfläche, um ein neues Projekt zu erstellen, und geben Sie dann einen Projektnamen ein (z. B. FriendlyEats).
  3. Klicken Sie auf Weiter.
  4. Lesen und akzeptieren Sie bei Aufforderung die Firebase-Nutzungsbedingungen und klicken Sie dann auf Weiter.
  5. (Optional) Aktivieren Sie die KI-Unterstützung in der Firebase Console (als „Gemini in Firebase“ bezeichnet).
  6. Für dieses Codelab benötigen Sie kein Google Analytics. Deaktivieren Sie daher die Google Analytics-Option.
  7. Klicken Sie auf Projekt erstellen, warten Sie, bis Ihr Projekt bereitgestellt wurde, und klicken Sie dann auf Weiter.

3. Beispielprojekt einrichten

Code herunterladen

Führen Sie den folgenden Befehl aus, um den Beispielcode für dieses Codelab zu klonen. Dadurch wird auf Ihrem Computer ein Ordner mit dem Namen friendlyeats-android erstellt:

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

Wenn Sie Git nicht auf Ihrem Computer haben, können Sie den Code auch direkt von GitHub herunterladen.

Firebase-Konfiguration hinzufügen

  1. Wählen Sie in der Firebase Console im linken Navigationsbereich Projektübersicht aus. Klicken Sie auf die Schaltfläche Android, um die Plattform auszuwählen. Wenn Sie nach einem Paketnamen gefragt werden, verwenden Sie com.google.firebase.example.fireeats.

73d151ed16016421.png

  1. Klicken Sie auf App registrieren und folgen Sie der Anleitung, um die Datei google-services.json herunterzuladen und in den Ordner app/ des gerade heruntergeladenen Codes zu verschieben. Klicken Sie anschließend auf Weiter.

Projekt importieren

Öffnen Sie Android Studio. Klicken Sie auf File > New > Import Project und wählen Sie den Ordner friendlyeats-android aus.

4. Firebase-Emulatoren einrichten

In diesem Codelab verwenden Sie die Firebase Emulator Suite, um Cloud Firestore und andere Firebase-Dienste lokal zu emulieren. So erhalten Sie eine sichere, schnelle und kostenlose lokale Entwicklungsumgebung für die Entwicklung Ihrer App.

Firebase CLI installieren

Zuerst müssen Sie die Firebase CLI installieren. Wenn Sie macOS oder Linux verwenden, können Sie den folgenden cURL-Befehl ausführen:

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

Wenn Sie Windows verwenden, finden Sie hier eine Installationsanleitung, um ein eigenständiges Binärprogramm zu erhalten oder die Installation über npm vorzunehmen.

Nach der Installation der CLI sollte bei der Ausführung von firebase --version eine Version von 9.0.0 oder höher angezeigt werden:

$ firebase --version
9.0.0

Anmelden

Führen Sie firebase login aus, um die CLI mit Ihrem Google-Konto zu verbinden. Dadurch wird ein neues Browserfenster geöffnet, in dem Sie sich anmelden können. Wählen Sie dasselbe Konto aus, das Sie zuvor beim Erstellen Ihres Firebase-Projekts verwendet haben.

Führen Sie im Ordner friendlyeats-android den Befehl firebase use --add aus, um Ihr lokales Projekt mit Ihrem Firebase-Projekt zu verbinden. Folgen Sie der Anleitung, um das zuvor erstellte Projekt auszuwählen. Wenn Sie aufgefordert werden, einen Alias auszuwählen, geben Sie default ein.

5. Anwendung ausführen

Jetzt ist es an der Zeit, die Firebase Emulator Suite und die FriendlyEats-Android-App zum ersten Mal auszuführen.

Emulatoren ausführen

Führen Sie in Ihrem Terminal im Verzeichnis friendlyeats-android den Befehl firebase emulators:start aus, um die Firebase-Emulatoren zu starten. Die Logs sollten so aussehen:

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

Sie haben jetzt eine vollständige lokale Entwicklungsumgebung auf Ihrem Computer. Lassen Sie diesen Befehl für den Rest des Codelabs laufen, da Ihre Android-App eine Verbindung zu den Emulatoren herstellen muss.

App mit den Emulatoren verbinden

Öffnen Sie die Dateien util/FirestoreInitializer.kt und util/AuthInitializer.kt in Android Studio. Diese Dateien enthalten die Logik, um die Firebase SDKs beim Start der Anwendung mit den lokalen Emulatoren auf Ihrem Computer zu verbinden.

Sehen Sie sich in der Methode create() der Klasse FirestoreInitializer diesen Code an:

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

Wir verwenden BuildConfig, um sicherzustellen, dass wir nur dann eine Verbindung zu den Emulatoren herstellen, wenn unsere App im debug-Modus ausgeführt wird. Wenn wir die App im release-Modus kompilieren, ist diese Bedingung falsch.

Wir sehen, dass die useEmulator(host, port)-Methode verwendet wird, um das Firebase SDK mit dem lokalen Firestore-Emulator zu verbinden. In der gesamten App verwenden wir FirebaseUtil.getFirestore(), um auf diese Instanz von FirebaseFirestore zuzugreifen. So stellen wir sicher, dass wir im debug-Modus immer eine Verbindung zum Firestore-Emulator herstellen.

Anwendung ausführen

Wenn Sie die Datei google-services.json richtig hinzugefügt haben, sollte das Projekt jetzt kompiliert werden. Klicken Sie in Android Studio auf Build > Rebuild Project (Erstellen > Projekt neu erstellen) und prüfen Sie, ob noch Fehler vorhanden sind.

Führen Sie die App in Android Studio auf Ihrem Android-Emulator aus. Zuerst wird der Bildschirm „Anmelden“ angezeigt. Sie können sich mit einer beliebigen E-Mail-Adresse und einem beliebigen Passwort in der App anmelden. Bei diesem Anmeldevorgang wird eine Verbindung zum Firebase Authentication-Emulator hergestellt, sodass keine echten Anmeldedaten übertragen werden.

Öffnen Sie jetzt die Emulators-Benutzeroberfläche, indem Sie in Ihrem Webbrowser zu http://localhost:4000 gehen. Klicken Sie dann auf den Tab Authentifizierung. Das gerade erstellte Konto sollte angezeigt werden:

Firebase Auth-Emulator

Nachdem Sie sich angemeldet haben, sollte der Startbildschirm der App angezeigt werden:

de06424023ffb4b9.png

Bald werden wir einige Daten hinzufügen, um den Startbildschirm zu füllen.

6. Daten in Firestore schreiben

In diesem Abschnitt schreiben wir einige Daten in Firestore, damit wir den derzeit leeren Startbildschirm füllen können.

Das Hauptmodellobjekt in unserer App ist ein Restaurant (siehe model/Restaurant.kt). Firestore-Daten werden in Dokumente, Sammlungen und untergeordnete Sammlungen aufgeteilt. Wir speichern jedes Restaurant als Dokument in einer Sammlung auf oberster Ebene namens "restaurants". Weitere Informationen zum Firestore-Datenmodell finden Sie in den Dokumenten und Sammlungen der Dokumentation.

Zu Demonstrationszwecken fügen wir der App eine Funktion hinzu, mit der zehn zufällige Restaurants erstellt werden, wenn wir im Überlaufmenü auf die Schaltfläche „Zufällige Elemente hinzufügen“ klicken. Öffnen Sie die Datei MainFragment.kt und ersetzen Sie den Inhalt der Methode onAddItemsClicked() durch Folgendes:

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

Hier sind einige wichtige Punkte zum obigen Code:

  • Zuerst haben wir einen Verweis auf die "restaurants"-Sammlung abgerufen. Sammlungen werden implizit erstellt, wenn Dokumente hinzugefügt werden. Daher war es nicht erforderlich, die Sammlung vor dem Schreiben von Daten zu erstellen.
  • Dokumente können mit Kotlin-Datenklassen erstellt werden, die wir zum Erstellen der einzelnen Restaurantdokumente verwenden.
  • Mit der Methode add() wird einer Sammlung ein Dokument mit einer automatisch generierten ID hinzugefügt. Daher mussten wir keine eindeutige ID für jedes Restaurant angeben.

Führen Sie die App jetzt noch einmal aus und klicken Sie im Überlaufmenü (oben rechts) auf die Schaltfläche „Add Random Items“ (Zufällige Elemente hinzufügen), um den gerade geschriebenen Code aufzurufen:

95691e9b71ba55e3.png

Öffnen Sie jetzt die Emulators-Benutzeroberfläche, indem Sie in Ihrem Webbrowser zu http://localhost:4000 gehen. Klicken Sie dann auf den Tab Firestore. Dort sollten Sie die gerade hinzugefügten Daten sehen:

Firebase Auth-Emulator

Diese Daten sind zu 100% lokal auf Ihrem Computer gespeichert. Ihr echtes Projekt enthält noch nicht einmal eine Firestore-Datenbank. Sie können diese Daten also gefahrlos ändern und löschen.

Glückwunsch! Sie haben gerade Daten in Firestore geschrieben. Im nächsten Schritt erfahren Sie, wie Sie diese Daten in der App anzeigen.

7. Daten aus Firestore anzeigen

In diesem Schritt erfahren wir, wie wir Daten aus Firestore abrufen und in unserer App anzeigen. Der erste Schritt zum Lesen von Daten aus Firestore besteht darin, eine Query zu erstellen. Öffnen Sie die Datei MainFragment.kt und fügen Sie den folgenden Code am Anfang der Methode onViewCreated() ein:

        // Firestore
        firestore = Firebase.firestore

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

Jetzt möchten wir die Abfrage überwachen, damit wir alle übereinstimmenden Dokumente erhalten und über zukünftige Aktualisierungen in Echtzeit benachrichtigt werden. Da wir diese Daten letztendlich an ein RecyclerView binden möchten, müssen wir eine RecyclerView.Adapter-Klasse erstellen, um die Daten zu erfassen.

Öffnen Sie die Klasse FirestoreAdapter, die bereits teilweise implementiert wurde. Zuerst muss der Adapter EventListener implementieren. Außerdem muss die Funktion onEvent so definiert werden, dass sie Aktualisierungen einer Firestore-Abfrage empfangen kann:

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()
    }
    
    // ...
}

Beim ersten Laden erhält der Listener für jedes neue Dokument ein ADDED-Ereignis. Wenn sich das Ergebnis-Set der Abfrage im Laufe der Zeit ändert, empfängt der Listener weitere Ereignisse mit den Änderungen. Jetzt implementieren wir den Listener fertig. Fügen Sie zuerst drei neue Methoden hinzu: onDocumentAdded, onDocumentModified und 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)
    }

Rufen Sie dann diese neuen Methoden über onEvent auf:

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

Implementieren Sie schließlich die Methode startListening(), um den Listener anzuhängen:

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

Die App ist jetzt vollständig konfiguriert, um Daten aus Firestore zu lesen. Führen Sie die App noch einmal aus. Die Restaurants, die Sie im vorherigen Schritt hinzugefügt haben, sollten angezeigt werden:

9e45f40faefce5d0.png

Kehren Sie nun zur Emulator-Benutzeroberfläche in Ihrem Browser zurück und bearbeiten Sie einen der Restaurantnamen. Die Änderung sollte in der App fast sofort sichtbar sein.

8. Daten sortieren und filtern

In der App werden derzeit die am besten bewerteten Restaurants in der gesamten Sammlung angezeigt. In einer echten Restaurant-App möchte der Nutzer die Daten jedoch sortieren und filtern. Die App sollte beispielsweise „Top-Fischrestaurants in Philadelphia“ oder „Günstigste Pizza“ anzeigen können.

Wenn Sie oben in der App auf die weiße Leiste klicken, wird ein Filterdialogfeld aufgerufen. In diesem Abschnitt verwenden wir Firestore-Abfragen, um diesen Dialog zu ermöglichen:

67898572a35672a5.png

Bearbeiten wir die Methode onFilter() von MainFragment.kt. Diese Methode akzeptiert ein Filters-Objekt. Das ist ein Hilfsobjekt, das wir erstellt haben, um die Ausgabe des Filterdialogfelds zu erfassen. Wir ändern diese Methode, um eine Abfrage aus den Filtern zu erstellen:

    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
    }

Im Snippet oben erstellen wir ein Query-Objekt, indem wir where- und orderBy-Klauseln anhängen, die den angegebenen Filtern entsprechen.

Führen Sie die App noch einmal aus und wählen Sie den folgenden Filter aus, um die beliebtesten Restaurants mit niedrigen Preisen aufzurufen:

7a67a8a400c80c50.png

Sie sollten jetzt eine gefilterte Liste von Restaurants sehen, die nur Optionen mit niedrigen Preisen enthält:

a670188398c3c59.png

Wenn Sie bis hierher gekommen sind, haben Sie jetzt eine voll funktionsfähige App zum Anzeigen von Restaurantempfehlungen in Firestore erstellt. Sie können Restaurants jetzt in Echtzeit sortieren und filtern. In den nächsten Abschnitten fügen wir den Restaurants Rezensionen und der App Sicherheitsregeln hinzu.

9. Daten in untergeordneten Sammlungen organisieren

In diesem Abschnitt fügen wir der App Bewertungen hinzu, damit Nutzer ihre Lieblingsrestaurants (oder die Restaurants, die sie am wenigsten mögen) bewerten können.

Sammlungen und Untersammlungen

Bisher haben wir alle Restaurantdaten in einer Sammlung auf oberster Ebene namens „restaurants“ gespeichert. Wenn ein Nutzer ein Restaurant bewertet, möchten wir den Restaurants ein neues Rating-Objekt hinzufügen. Für diese Aufgabe verwenden wir eine untergeordnete Sammlung. Eine untergeordnete Sammlung ist eine Sammlung, die an ein Dokument angehängt ist. Jedes Restaurantdokument hat also eine untergeordnete Sammlung „Bewertungen“ mit Bewertungsdokumenten. Mit untergeordneten Sammlungen lassen sich Daten organisieren, ohne dass Dokumente aufgebläht werden oder komplexe Abfragen erforderlich sind.

Rufen Sie .collection() für das übergeordnete Dokument auf, um auf eine untergeordnete Sammlung zuzugreifen:

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

Sie können auf eine Unterkollektion zugreifen und sie abfragen, genau wie bei einer Sammlung der obersten Ebene. Es gibt keine Größenbeschränkungen oder Leistungsänderungen. Weitere Informationen zum Firestore-Datenmodell

Daten in eine Transaktion schreiben

Wenn Sie ein Rating der richtigen Unterkollektion hinzufügen möchten, müssen Sie nur .add() aufrufen. Wir müssen aber auch die durchschnittliche Bewertung und die Anzahl der Bewertungen des Restaurant-Objekts aktualisieren, damit die neuen Daten berücksichtigt werden. Wenn wir separate Vorgänge verwenden, um diese beiden Änderungen vorzunehmen, kann es zu einer Reihe von Race-Bedingungen kommen, die zu veralteten oder falschen Daten führen.

Damit Bewertungen richtig hinzugefügt werden, verwenden wir eine Transaktion, um einem Restaurant Bewertungen hinzuzufügen. Bei dieser Transaktion werden einige Aktionen ausgeführt:

  • Aktuelle Bewertung des Restaurants lesen und neue Bewertung berechnen
  • Bewertung zur Untergruppe hinzufügen
  • Aktualisieren der durchschnittlichen Bewertung und der Anzahl der Bewertungen des Restaurants

Öffnen Sie RestaurantDetailFragment.kt und implementieren Sie die Funktion 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
        }
    }

Die Funktion addRating() gibt ein Task zurück, das die gesamte Transaktion darstellt. In der Funktion onRating() werden Listener für die Aufgabe hinzugefügt, um auf das Ergebnis der Transaktion zu reagieren.

Führen Sie die App nun noch einmal aus und klicken Sie auf eines der Restaurants. Dadurch sollte der Detailbildschirm des Restaurants aufgerufen werden. Klicken Sie auf die Schaltfläche +, um eine Rezension hinzuzufügen. Fügen Sie eine Rezension hinzu, indem Sie eine Anzahl von Sternen auswählen und etwas Text eingeben.

78fa16cdf8ef435a.png

Wenn Sie auf Senden klicken, wird die Transaktion gestartet. Wenn die Transaktion abgeschlossen ist, wird Ihre Rezension unten angezeigt und die Anzahl der Rezensionen des Restaurants wird aktualisiert:

f9e670f40bd615b0.png

Glückwunsch! Sie haben jetzt eine soziale, lokale, mobile Restaurantrezensions-App, die auf Cloud Firestore basiert. Ich habe gehört, dass sie heutzutage sehr beliebt sind.

10. Daten schützen

Bisher haben wir die Sicherheit dieser Anwendung nicht berücksichtigt. Woher wissen wir, dass Nutzer nur die richtigen eigenen Daten lesen und schreiben können? Firestore-Datenbanken werden durch eine Konfigurationsdatei namens Sicherheitsregeln geschützt.

Öffnen Sie die Datei firestore.rules und ersetzen Sie den Inhalt durch Folgendes:

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

Diese Regeln schränken den Zugriff ein, damit Clients nur sichere Änderungen vornehmen. Bei Aktualisierungen eines Restaurantdokuments können beispielsweise nur die Bewertungen geändert werden, nicht der Name oder andere unveränderliche Daten. Bewertungen können nur erstellt werden, wenn die Nutzer-ID mit dem angemeldeten Nutzer übereinstimmt. Dadurch wird Spoofing verhindert.

Weitere Informationen zu Sicherheitsregeln finden Sie in der Dokumentation.

11. Fazit

Sie haben jetzt eine voll funktionsfähige App auf Grundlage von Firestore erstellt. Sie haben die wichtigsten Firestore-Funktionen kennengelernt, darunter:

  • Dokumente und Sammlungen
  • Daten lesen und schreiben
  • Sortieren und Filtern mit Abfragen
  • Untergeordnete Sammlungen
  • Transaktionen

Weitere Informationen

Hier sind einige gute Ausgangspunkte, um mehr über Firestore zu erfahren:

Die Restaurant-App in diesem Codelab basiert auf der Beispielanwendung „Friendly Eats“. Den Quellcode für diese App finden Sie hier.

Optional: Für die Produktion bereitstellen

Bisher wurde in dieser App nur die Firebase Emulator Suite verwendet. Wenn Sie wissen möchten, wie Sie diese App in einem echten Firebase-Projekt bereitstellen, fahren Sie mit dem nächsten Schritt fort.

12. (Optional) Anwendung bereitstellen

Bisher war diese App vollständig lokal. Alle Daten sind in der Firebase Emulator Suite enthalten. In diesem Abschnitt erfahren Sie, wie Sie Ihr Firebase-Projekt so konfigurieren, dass diese App in der Produktion funktioniert.

Firebase Authentication

Rufen Sie in der Firebase Console den Abschnitt Authentifizierung auf und klicken Sie auf Jetzt starten. Rufen Sie den Tab Anmeldemethode auf und wählen Sie unter Native Anbieter die Option E-Mail/Passwort aus.

Aktivieren Sie die Anmeldemethode E-Mail-Adresse/Passwort und klicken Sie auf Speichern.

sign-in-providers.png

Firestore

Datenbank erstellen

Rufen Sie in der Console den Abschnitt Firestore-Datenbank auf und klicken Sie auf Datenbank erstellen:

  1. Wenn Sie nach Sicherheitsregeln gefragt werden, wählen Sie Produktionsmodus aus. Wir aktualisieren diese Regeln in Kürze.
  2. Wählen Sie den Datenbankstandort aus, den Sie für Ihre App verwenden möchten. Die Auswahl eines Datenbankstandorts ist eine dauerhafte Entscheidung. Wenn Sie den Standort ändern möchten, müssen Sie ein neues Projekt erstellen. Weitere Informationen zur Auswahl eines Projektstandorts finden Sie in der Dokumentation.

Regeln bereitstellen

Führen Sie den folgenden Befehl im Codelab-Verzeichnis aus, um die zuvor geschriebenen Sicherheitsregeln bereitzustellen:

$ firebase deploy --only firestore:rules

Dadurch werden die Inhalte von firestore.rules in Ihrem Projekt bereitgestellt. Sie können dies bestätigen, indem Sie in der Konsole den Tab Regeln aufrufen.

Indexe bereitstellen

Die FriendlyEats-App hat komplexe Sortier- und Filterfunktionen, für die eine Reihe benutzerdefinierter zusammengesetzter Indexe erforderlich sind. Sie können manuell in der Firebase Console erstellt werden, es ist jedoch einfacher, ihre Definitionen in die Datei firestore.indexes.json zu schreiben und sie mit der Firebase CLI bereitzustellen.

Wenn Sie die Datei firestore.indexes.json öffnen, sehen Sie, dass die erforderlichen Indexe bereits angegeben wurden:

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

Führen Sie den folgenden Befehl aus, um diese Indexe bereitzustellen:

$ firebase deploy --only firestore:indexes

Das Erstellen von Indexen erfolgt nicht sofort. Sie können den Fortschritt in der Firebase Console verfolgen.

Anwendung konfigurieren

In den Dateien util/FirestoreInitializer.kt und util/AuthInitializer.kt haben wir das Firebase SDK so konfiguriert, dass es im Debugmodus eine Verbindung zu den Emulatoren herstellt:

    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
    }

Wenn Sie Ihre App mit Ihrem echten Firebase-Projekt testen möchten, haben Sie folgende Möglichkeiten:

  1. Erstellen Sie die App im Release-Modus und führen Sie sie auf einem Gerät aus.
  2. Ersetzen Sie BuildConfig.DEBUG vorübergehend durch false und führen Sie die App noch einmal aus.

Möglicherweise müssen Sie sich von der App abmelden und noch einmal anmelden, um eine Verbindung zur Produktion herzustellen.