Cloud Firestore-Android-Codelab

1. Übersicht

Ziele

In diesem Codelab erstellen Sie eine Android-App für Restaurantempfehlungen, die auf Cloud Firestore basiert. Nach Abschluss können Sie:

  • Daten aus einer Android-App in Firestore lesen und schreiben
  • Änderungen an Firestore-Daten in Echtzeit überwachen
  • Firebase Authentication und Sicherheitsregeln zum Schutz von Firestore-Daten verwenden
  • Komplexe Firestore-Abfragen schreiben

Vorbereitung

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

  • Android Studio Flamingo oder höher
  • Einen Android-Emulator mit API 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 in der Firebase Console auf Projekt hinzufügen.
  3. Geben Sie wie im Screenshot unten einen Namen für Ihr Firebase-Projekt ein (z. B. „Friendly Eats“) und klicken Sie auf Weiter.

9d2f625aebcab6af.png

  1. Möglicherweise werden Sie aufgefordert, Google Analytics zu aktivieren. Für dieses Codelab spielt Ihre Auswahl keine Rolle.
  2. Nach etwa einer Minute ist Ihr Firebase-Projekt einsatzbereit. Klicken Sie 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. Geben Sie bei der Aufforderung nach einem Paketnamen com.google.firebase.example.fireeats ein.

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 (Datei > Neu > Projekt importieren) und wählen Sie den Ordner friendlyeats-android aus.

4. Firebase Emulators 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 Erstellung Ihrer App.

Firebase CLI installieren

Sie müssen zuerst 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, lesen Sie die Installationsanleitung, um ein eigenständiges Binärprogramm zu erhalten oder die Installation über npm durchzuführen.

Nachdem Sie die Befehlszeile installiert haben, sollte beim Ausführen von firebase --version die Version 9.0.0 oder höher angezeigt werden:

$ firebase --version
9.0.0

Anmelden

Führen Sie firebase login aus, um die Befehlszeile mit Ihrem Google-Konto zu verbinden. Dadurch wird ein neues Browserfenster geöffnet, um den Anmeldevorgang abzuschließen. Wählen Sie dasselbe Konto aus, das Sie beim Erstellen Ihres Firebase-Projekts verwendet haben.

Führen Sie im Ordner friendlyeats-android 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 Android-App von FriendlyEats zum ersten Mal auszuführen.

Emulatoren ausführen

Führen Sie im Terminal im Verzeichnis friendlyeats-android den Befehl firebase emulators:start aus, um die Firebase-Emulatoren zu starten. Sie sollten Protokolle wie diese sehen:

$ 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, mit der die Firebase SDKs beim Start der Anwendung mit den lokalen Emulatoren auf Ihrem Computer verbunden werden.

Sehen Sie sich in der create()-Methode 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 dafür zu sorgen, dass nur eine Verbindung zu den Emulatoren hergestellt wird, 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 können wir sicher sein, dass immer eine Verbindung zum Firestore-Emulator hergestellt wird, wenn die App im debug-Modus ausgeführt wird.

Anwendung ausführen

Wenn Sie die google-services.json-Datei 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 keine Fehler mehr 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 dieser Anmeldung wird eine Verbindung zum Firebase Authentication-Emulator hergestellt, sodass keine echten Anmeldedaten übertragen werden.

Öffnen Sie jetzt die Benutzeroberfläche der Emulatoren, indem Sie in Ihrem Webbrowser http://localhost:4000 aufrufen. Klicken Sie dann auf den Tab Authentifizierung. Das Konto, das Sie gerade erstellt haben, sollte angezeigt werden:

Firebase Auth-Emulator

Nach der Anmeldung sollte der Startbildschirm der App angezeigt werden:

de06424023ffb4b9.png

Bald werden wir dem Startbildschirm einige Daten hinzufügen.

6. Daten in Firestore schreiben

In diesem Abschnitt schreiben wir einige Daten in Firestore, damit wir den derzeit leeren Startbildschirm befü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 Dreipunkt-Menü auf die Schaltfläche „Zufällige Elemente hinzufügen“ klicken. Öffnen Sie die Datei MainFragment.kt und ersetzen Sie den Inhalt in 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)
        }
    }

Im Code oben gibt es einige wichtige Dinge zu beachten:

  • Zuerst haben wir eine Referenz auf die Sammlung "restaurants" abgerufen. Sammlungen werden implizit erstellt, wenn Dokumente hinzugefügt werden. Daher musste die Sammlung nicht vor dem Schreiben von Daten erstellt werden.
  • Dokumente können mit Kotlin-Datenklassen erstellt werden, mit denen wir jedes Restaurantdokument erstellen.
  • Mit der Methode add() wird einer Sammlung ein Dokument mit einer automatisch generierten ID hinzugefügt. Daher mussten wir für jedes Restaurant keine eindeutige ID angeben.

Führen Sie die App jetzt noch einmal aus und klicken Sie im Dreipunkt-Menü oben rechts auf die Schaltfläche „Zufällige Elemente hinzufügen“, um den Code auszuführen, den Sie gerade geschrieben haben:

95691e9b71ba55e3.png

Öffnen Sie jetzt die Benutzeroberfläche der Emulatoren, indem Sie in Ihrem Webbrowser http://localhost:4000 aufrufen. Klicken Sie dann auf den Tab Firestore. Die Daten, die Sie gerade hinzugefügt haben, sollten dort angezeigt werden:

Firebase Auth-Emulator

Diese Daten befinden sich zu 100% lokal auf Ihrem Computer. Tatsächlich enthält Ihr echtes Projekt noch keine Firestore-Datenbank. Sie können diese Daten also ohne Konsequenzen ändern und löschen.

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 Sie, wie Sie Daten aus Firestore abrufen und in Ihrer 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 an den 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 beobachten, damit wir alle übereinstimmenden Dokumente erhalten und in Echtzeit über zukünftige Aktualisierungen informiert werden. Da wir diese Daten letztendlich an eine RecyclerView binden möchten, müssen wir eine RecyclerView.Adapter-Klasse erstellen, um die Daten zu überwachen.

Öffnen Sie die Klasse FirestoreAdapter, die bereits teilweise implementiert wurde. Implementieren wir zuerst EventListener im Adapter und definieren die Funktion onEvent so, dass sie Aktualisierungen für eine 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 die Ergebnismenge der Abfrage im Laufe der Zeit ändert, erhält der Listener weitere Ereignisse mit den Änderungen. Jetzt können wir den Listener fertig implementieren. 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 diese neuen Methoden dann ü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()
    }

Implementiere abschließend die Methode startListening(), um den Listener anzuhängen:

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

Jetzt ist die App vollständig für das Lesen von Daten aus Firestore konfiguriert. Führen Sie die App noch einmal aus. Sie sollten die im vorherigen Schritt hinzugefügten Restaurants sehen:

9e45f40faefce5d0.png

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

8. Daten sortieren und filtern

Derzeit werden in der App die bestbewerteten Restaurants der gesamten Sammlung angezeigt. In einer echten Restaurant-App möchte der Nutzer jedoch die Daten sortieren und filtern können. Die App sollte beispielsweise „Top-Meeresfrüchterestaurants in Philadelphia“ oder „Die günstigste Pizza“ anzeigen können.

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

67898572a35672a5.png

Bearbeiten wir die onFilter()-Methode von MainFragment.kt. Diese Methode akzeptiert ein Filters-Objekt, ein Hilfsobjekt, das wir zum Erfassen der Ausgabe des Dialogfelds „Filter“ erstellt haben. 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.

Starten Sie die App noch einmal und wählen Sie den folgenden Filter aus, um die beliebtesten preiswerten Restaurants zu sehen:

7a67a8a400c80c50.png

Sie sollten jetzt eine gefilterte Liste von Restaurants sehen, die nur preisgünstige Optionen enthalten:

a670188398c3c59.png

Wenn Sie es bis hierher geschafft haben, haben Sie jetzt eine voll funktionsfähige App für 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 Lieblings- (oder unbeliebtesten) Restaurants 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 Untersammlung. Eine untergeordnete Sammlung ist eine Sammlung, die an ein Dokument angehängt ist. Jedes Restaurantdokument hat also eine untergeordnete Sammlung mit Bewertungsdokumenten. Untersammlungen helfen, Daten zu organisieren, ohne die Dokumente zu vergrößern oder komplexe Abfragen zu erfordern.

Wenn Sie auf eine untergeordnete Sammlung zugreifen möchten, rufen Sie .collection() im übergeordneten Dokument auf:

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

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

Daten in einer Transaktion schreiben

Um der entsprechenden Untersammlung eine Rating hinzuzufügen, müssen wir nur .add() aufrufen. Außerdem müssen wir die durchschnittliche Bewertung und die Anzahl der Bewertungen des Restaurant-Objekts aktualisieren, um die neuen Daten widerzuspiegeln. Wenn wir diese beiden Änderungen mit separaten Vorgängen vornehmen, kann es zu einer Reihe von Race-Bedingungen kommen, die zu veralteten oder falschen Daten führen können.

Damit Bewertungen korrekt 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 der Untersammlung hinzufügen
  • Durchschnittliche Bewertung und Anzahl der Bewertungen des Restaurants aktualisieren

Ö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 einen Task zurück, der die gesamte Transaktion darstellt. In der onRating()-Funktion werden der Aufgabe Listener hinzugefügt, um auf das Ergebnis der Transaktion zu reagieren.

Führen Sie die App jetzt noch einmal aus und klicken Sie auf eines der Restaurants. Daraufhin sollte der Detailbildschirm des Restaurants angezeigt 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 Text eingeben.

78fa16cdf8ef435a.png

Wenn du auf Senden klickst, wird die Transaktion gestartet. Sobald die Transaktion abgeschlossen ist, wird Ihre Rezension unten angezeigt und die Anzahl der Rezensionen für das Restaurant wird aktualisiert:

f9e670f40bd615b0.png

Glückwunsch! Sie haben jetzt eine soziale, lokale, mobile App für Restaurantbewertungen, 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 Kunden 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 User-ID mit dem angemeldeten Nutzer übereinstimmt. So wird Spoofing verhindert.

Weitere Informationen zu Sicherheitsregeln finden Sie in der Dokumentation.

11. Fazit

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

  • Dokumente und Sammlungen
  • Daten lesen und schreiben
  • Mit Abfragen sortieren und filtern
  • Untergeordnete Sammlungen
  • Transaktionen

Weitere Informationen

Hier finden Sie weitere Informationen zu Firestore:

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

Optional: Für Produktion bereitstellen

Bisher wurde für diese 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 und alle Daten befinden sich in der Firebase Emulator Suite. In diesem Abschnitt erfahren Sie, wie Sie Ihr Firebase-Projekt so konfigurieren, dass diese App in der Produktion funktioniert.

Firebase Authentication

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

Aktiviere die Anmeldemethode E-Mail-Adresse/Passwort und klicke auf Speichern.

sign-in-providers.png

Firestore

Datenbank erstellen

Klicken Sie in der Console im Bereich Firestore-Datenbank auf Datenbank erstellen:

  1. Wenn Sie bei der Frage zu Sicherheitsregeln den Produktionsmodus auswählen, aktualisieren wir diese Regeln bald.
  2. Wählen Sie den Speicherort der Datenbank aus, den Sie für Ihre App verwenden möchten. Die Auswahl eines Speicherorts für die Datenbank ist dauerhaft. Wenn Sie ihn ändern möchten, müssen Sie ein neues Projekt erstellen. Weitere Informationen zur Auswahl eines Projektstandorts finden Sie in der Dokumentation.

Bereitstellungsregeln

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

$ firebase deploy --only firestore:rules

Dadurch werden die Inhalte von firestore.rules in Ihrem Projekt bereitgestellt. Sie können dies in der Konsole auf dem Tab Regeln prüfen.

Indexe bereitstellen

Die FriendlyEats-App bietet eine komplexe Sortierung und Filterung, für die eine Reihe benutzerdefinierter zusammengesetzter Indexe erforderlich ist. Sie können sie manuell in der Firebase Console erstellen. Es ist jedoch einfacher, ihre Definitionen in der 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 bereitgestellt 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 des Index dauert einige Zeit. Sie können den Fortschritt in der Firebase Console verfolgen.

App konfigurieren

In den Dateien util/FirestoreInitializer.kt und util/AuthInitializer.kt haben wir das Firebase SDK so konfiguriert, dass es im Debug-Modus 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 wieder anmelden, um eine ordnungsgemäße Verbindung zur Produktionsversion herzustellen.