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. Du wirst lernen wie:

  • Lesen und schreiben Sie Daten aus einer Android-App in Firestore
  • Hören Sie Änderungen in Firestore-Daten in Echtzeit
  • Verwenden Sie die Firebase-Authentifizierung und Sicherheitsregeln, um Firestore-Daten zu schützen
  • Schreiben Sie komplexe Firestore-Abfragen

Voraussetzungen

Bevor Sie mit diesem Codelab beginnen, stellen Sie sicher, dass Sie über Folgendes verfügen:

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

2. Erstellen Sie ein Firebase-Projekt

  1. Melden Sie sich mit Ihrem Google-Konto bei der Firebase-Konsole an.
  2. Klicken Sie in der Firebase-Konsole auf Projekt hinzufügen .
  3. Geben Sie, wie im Screenshot unten gezeigt, 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 die Zwecke dieses Codelabs spielt Ihre Auswahl keine Rolle.
  2. Nach etwa einer Minute ist Ihr Firebase-Projekt fertig. Klicken Sie auf Weiter .

3. Richten Sie das Beispielprojekt ein

Laden Sie den Code herunter

Führen Sie den folgenden Befehl aus, um den Beispielcode für dieses Codelab zu klonen. Dadurch wird auf Ihrem Computer ein Ordner namens 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-Konsole im linken Navigationsbereich Projektübersicht aus. Klicken Sie auf die Android- Schaltfläche, um die Plattform auszuwählen. Wenn Sie zur Eingabe eines Paketnamens aufgefordert werden, verwenden Sie com.google.firebase.example.fireeats

73d151ed16016421.png

  1. Klicken Sie auf „App registrieren“ und folgen Sie den Anweisungen, um die Datei google-services.json herunterzuladen und in den Ordner app/ des Codes zu verschieben, den Sie gerade heruntergeladen haben. Klicken Sie dann auf Weiter .

Importieren Sie das Projekt

Öffnen Sie Android Studio. Klicken Sie auf Datei > Neu > Projekt importieren und wählen Sie den Ordner „Friendlyeats-Android“ aus.

4. Richten Sie die Firebase-Emulatoren ein

In diesem Codelab verwenden Sie die Firebase Emulator Suite , um Cloud Firestore und andere Firebase-Dienste lokal zu emulieren. Dies bietet eine sichere, schnelle und kostenlose lokale Entwicklungsumgebung zum Erstellen Ihrer App.

Installieren Sie die Firebase-CLI

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, lesen Sie die Installationsanweisungen , um eine eigenständige Binärdatei zu erhalten oder über npm zu installieren.

Sobald Sie die CLI installiert haben, sollte beim Ausführen von firebase --version eine Version von 9.0.0 oder höher gemeldet werden:

$ firebase --version
9.0.0

Anmeldung

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

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

5. Führen Sie die App aus

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

Führen Sie die Emulatoren aus

Führen Sie in Ihrem Terminal aus dem Verzeichnis friendlyeats-android den Befehl firebase emulators:start 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 verfügen nun über eine vollständige lokale Entwicklungsumgebung, die auf Ihrem Computer läuft! Stellen Sie sicher, dass dieser Befehl für den Rest des Codelabs ausgeführt wird. Ihre Android-App muss eine Verbindung zu den Emulatoren herstellen.

Verbinden Sie die App mit den Emulatoren

Ö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 zu verbinden, die auf Ihrem Computer ausgeführt werden.

Untersuchen Sie in der Methode create() der Klasse FirestoreInitializer diesen Code:

    // 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 können sehen, dass es die Methode useEmulator(host, port) verwendet, 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, sodass wir sicher sind, dass wir bei der Ausführung im debug Modus immer eine Verbindung zum Firestore-Emulator herstellen.

Führen Sie die App aus

Wenn Sie die Datei google-services.json ordnungsgemäß hinzugefügt haben, sollte das Projekt jetzt kompiliert werden. Klicken Sie in Android Studio auf Erstellen > Projekt neu erstellen und stellen Sie sicher, dass keine Fehler mehr vorhanden sind.

Führen Sie in Android Studio die App auf Ihrem Android-Emulator aus. Zunächst wird Ihnen ein „Anmelden“-Bildschirm angezeigt. Sie können eine beliebige E-Mail-Adresse und ein beliebiges Passwort verwenden, um sich bei der App anzumelden. Bei diesem Anmeldevorgang wird eine Verbindung zum Firebase-Authentifizierungsemulator hergestellt, sodass keine echten Anmeldeinformationen übertragen werden.

Öffnen Sie nun die Benutzeroberfläche der Emulatoren, indem Sie in Ihrem Webbrowser zu http://localhost:4000 navigieren. Klicken Sie dann auf die Registerkarte „Authentifizierung“ und Sie sollten das Konto sehen, das Sie gerade erstellt haben:

Firebase-Auth-Emulator

Sobald Sie den Anmeldevorgang abgeschlossen 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 sind in Dokumente, Sammlungen und Untersammlungen unterteilt. Wir speichern jedes Restaurant als Dokument in einer Sammlung auf oberster Ebene namens "restaurants" . Um mehr über das Firestore-Datenmodell zu erfahren, lesen Sie die Informationen zu Dokumenten und Sammlungen in der Dokumentation .

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

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

Es gibt ein paar wichtige Dinge, die Sie zum obigen Code beachten sollten:

  • Wir begannen mit einem Verweis auf die Sammlung "restaurants" . Sammlungen werden implizit erstellt, wenn Dokumente hinzugefügt werden, sodass es nicht erforderlich war, die Sammlung vor dem Schreiben von Daten zu erstellen.
  • Dokumente können mithilfe von Kotlin-Datenklassen erstellt werden, die wir zum Erstellen jedes Restaurantdokuments verwenden.
  • Die Methode add() fügt ein Dokument mit einer automatisch generierten ID zu einer Sammlung hinzu, sodass wir nicht für jedes Restaurant eine eindeutige ID angeben mussten.

Führen Sie nun die App erneut aus und klicken Sie im Überlaufmenü (oben rechts) auf die Schaltfläche „Zufällige Elemente hinzufügen“, um den Code aufzurufen, den Sie gerade geschrieben haben:

95691e9b71ba55e3.png

Öffnen Sie nun die Benutzeroberfläche der Emulatoren, indem Sie in Ihrem Webbrowser zu http://localhost:4000 navigieren. Klicken Sie dann auf die Registerkarte „Firestore“ und Sie sollten die Daten sehen, die Sie gerade hinzugefügt haben:

Firebase-Auth-Emulator

Diese Daten befinden sich zu 100 % lokal auf Ihrem Computer. Tatsächlich enthält Ihr echtes Projekt noch nicht einmal eine Firestore-Datenbank! Dies bedeutet, dass Sie problemlos mit der Änderung und Löschung dieser Daten experimentieren können, ohne dass dies Konsequenzen hat.

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

7. Daten aus Firestore anzeigen

In diesem Schritt lernen 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 onViewCreated() Methode hinzu:

        // Firestore
        firestore = Firebase.firestore

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

Jetzt wollen wir uns die Abfrage anhören, damit wir alle passenden Dokumente erhalten und in Echtzeit über zukünftige Updates informiert werden. Da unser letztendliches Ziel darin besteht, diese Daten an eine RecyclerView zu binden, müssen wir eine RecyclerView.Adapter Klasse erstellen, um die Daten abzuhören.

Öffnen Sie die FirestoreAdapter Klasse, die bereits teilweise implementiert wurde. Lassen Sie uns zunächst dafür sorgen, dass der Adapter EventListener implementiert und die onEvent Funktion definiert, damit er 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 ein ADDED Ereignis für jedes neue Dokument. Da sich die Ergebnismenge der Abfrage im Laufe der Zeit ändert, erhält der Listener mehr Ereignisse, die die Änderungen enthalten. Lassen Sie uns nun die Implementierung des Listeners abschließen. Fügen Sie zunächst 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 von 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 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 zum Lesen von Daten aus Firestore konfiguriert. Führen Sie die App erneut aus und Sie sollten die Restaurants sehen, die Sie im vorherigen Schritt hinzugefügt haben:

9e45f40faefce5d0.png

Gehen Sie nun in Ihrem Browser zurück zur Emulator-Benutzeroberfläche und bearbeiten Sie einen der Restaurantnamen. Sie sollten fast sofort sehen, wie sich die Änderung in der App ändert!

8. Daten sortieren und filtern

Die App zeigt derzeit die am besten bewerteten Restaurants in der gesamten Sammlung an, aber in einer echten Restaurant-App würde der Benutzer die Daten sortieren und filtern wollen. Beispielsweise sollte die App in der Lage sein, „Top-Fischrestaurants in Philadelphia“ oder „Günstigste Pizza“ anzuzeigen.

Wenn Sie oben in der App auf die weiße Leiste klicken, wird ein Filterdialog angezeigt. In diesem Abschnitt verwenden wir Firestore-Abfragen, damit dieser Dialog funktioniert:

67898572a35672a5.png

Bearbeiten wir die onFilter() Methode von MainFragment.kt . Diese Methode akzeptiert ein Filters Objekt, bei dem es sich um ein Hilfsobjekt handelt, das wir erstellt haben, um die Ausgabe des Filterdialogs zu erfassen. Wir werden diese Methode ändern, 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 obigen Snippet erstellen wir ein Query Objekt, indem wir where und orderBy Klauseln anhängen, um den angegebenen Filtern zu entsprechen.

Führen Sie die App erneut aus und wählen Sie den folgenden Filter aus, um die beliebtesten Niedrigpreisrestaurants anzuzeigen:

7a67a8a400c80c50.png

Sie sollten nun 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 zum Anzeigen von Restaurantempfehlungen auf Firestore erstellt! Sie können Restaurants jetzt in Echtzeit sortieren und filtern. In den nächsten Abschnitten werden wir Bewertungen zu den Restaurants hinzufügen und Sicherheitsregeln zur App hinzufügen.

9. Organisieren Sie Daten in Untersammlungen

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

Sammlungen und Untersammlungen

Bisher haben wir alle Restaurantdaten in einer Sammlung auf oberster Ebene namens „Restaurants“ gespeichert. Wenn ein Benutzer ein Restaurant bewertet, möchten wir den Restaurants ein neues Rating hinzufügen. Für diese Aufgabe verwenden wir eine Untersammlung. Sie können sich eine Untersammlung als eine Sammlung vorstellen, die an ein Dokument angehängt ist. Daher verfügt jedes Restaurantdokument über eine Bewertungsuntersammlung voller Bewertungsdokumente. Untersammlungen helfen dabei, Daten zu organisieren, ohne unsere Dokumente aufzublähen oder komplexe Abfragen zu erfordern.

Um auf eine Untersammlung zuzugreifen, rufen Sie .collection() für das übergeordnete Dokument auf:

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

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

Daten in eine Transaktion schreiben

Das Hinzufügen einer Rating zur richtigen Untersammlung erfordert nur den Aufruf von .add() , aber wir müssen auch die durchschnittliche Bewertung und die Anzahl der Bewertungen des Restaurant Objekts aktualisieren, um die neuen Daten widerzuspiegeln. Wenn wir separate Vorgänge verwenden, um diese beiden Änderungen vorzunehmen, gibt es eine Reihe von Rennbedingungen, die zu veralteten oder falschen Daten führen können.

Um sicherzustellen, dass Bewertungen ordnungsgemäß hinzugefügt werden, verwenden wir eine Transaktion, um Bewertungen zu einem Restaurant hinzuzufügen. Diese Transaktion führt einige Aktionen aus:

  • Lesen Sie die aktuelle Bewertung des Restaurants und berechnen Sie die neue
  • Fügen Sie die Bewertung zur Untersammlung hinzu
  • Aktualisieren Sie die durchschnittliche Bewertung und die 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 eine Task zurück, die die gesamte Transaktion darstellt. In der Funktion onRating() werden der Aufgabe Listener hinzugefügt, die auf das Ergebnis der Transaktion reagieren.

Führen Sie nun die App erneut aus und klicken Sie auf eines der Restaurants. Daraufhin sollte der Bildschirm mit den Restaurantdetails angezeigt werden. Klicken Sie auf die Schaltfläche „+“ , um mit dem Hinzufügen einer Bewertung zu beginnen. Fügen Sie eine Bewertung hinzu, indem Sie eine Reihe von Sternen auswählen und Text eingeben.

78fa16cdf8ef435a.png

Wenn Sie auf „Senden“ klicken, wird die Transaktion gestartet. Wenn die Transaktion abgeschlossen ist, wird Ihre Bewertung unten angezeigt und die Anzahl der Bewertungen des Restaurants wird aktualisiert:

f9e670f40bd615b0.png

Glückwunsch! Sie verfügen jetzt über eine soziale, lokale, mobile Restaurantbewertungs-App, die auf Cloud Firestore basiert. Ich habe gehört, dass diese heutzutage sehr beliebt sind.

10. Sichern Sie Ihre Daten

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

Öffnen Sie die Datei firestore.rules . Sie sollten Folgendes sehen:

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

Ändern wir diese Regeln, um unerwünschte Datenzugriffe oder -änderungen zu verhindern. Ö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 beschränken den Zugriff, um sicherzustellen, dass Clients nur sichere Änderungen vornehmen. Beispielsweise können Aktualisierungen eines Restaurantdokuments nur die Bewertungen ändern, nicht jedoch den Namen oder andere unveränderliche Daten. Bewertungen können nur erstellt werden, wenn die Benutzer-ID mit dem angemeldeten Benutzer übereinstimmt, was Spoofing verhindert.

Weitere Informationen zu Sicherheitsregeln finden Sie in der Dokumentation .

11. Fazit

Sie haben jetzt eine App mit vollem Funktionsumfang auf Basis von Firestore erstellt. Sie haben die wichtigsten Firestore-Funktionen kennengelernt, darunter:

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

Erfahren Sie mehr

Um weiterhin mehr über Firestore zu erfahren, finden Sie hier einige gute Einstiegsmöglichkeiten:

Die Restaurant-App in diesem Codelab basierte auf der Beispielanwendung „Friendly Eats“. Sie können den Quellcode für diese App hier durchsuchen.

Optional: Bereitstellung in der Produktion

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

12. (Optional) Stellen Sie Ihre App bereit

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-Authentifizierung

Gehen Sie in der Firebase-Konsole zum Abschnitt „Authentifizierung“ und klicken Sie auf „Erste Schritte“ . Navigieren Sie zur Registerkarte „Anmeldemethode“ und wählen Sie die Option „E-Mail/Passwort“ unter „Native Provider“ aus.

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

sign-in-providers.png

Feuerladen

Datenbank erstellen

Navigieren Sie zum Abschnitt „Firestore-Datenbank“ der Konsole und klicken Sie auf „Datenbank erstellen“ :

  1. Wenn Sie zu den Sicherheitsregeln aufgefordert werden, wählen Sie „Start im Produktionsmodus“ aus. Wir werden diese Regeln bald aktualisieren.
  2. Wählen Sie den Datenbankspeicherort aus, den Sie für Ihre App verwenden möchten. Beachten Sie, dass die Auswahl eines Datenbankspeicherorts eine dauerhafte Entscheidung ist und Sie zum Ändern ein neues Projekt erstellen müssen. Weitere Informationen zur Auswahl eines Projektstandorts finden Sie in der Dokumentation .

Regeln bereitstellen

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

$ firebase deploy --only firestore:rules

Dadurch wird der Inhalt von firestore.rules in Ihrem Projekt bereitgestellt. Sie können dies bestätigen, indem Sie in der Konsole zur Registerkarte „Regeln“ navigieren.

Stellen Sie Indizes bereit

Die FriendlyEats-App verfügt über komplexe Sortier- und Filterfunktionen, die eine Reihe benutzerdefinierter zusammengesetzter Indizes erfordern. Diese können manuell in der Firebase-Konsole erstellt werden. Es ist jedoch einfacher, ihre Definitionen in die Datei firestore.indexes.json zu schreiben und sie mithilfe der Firebase-CLI bereitzustellen.

Wenn Sie die Datei firestore.indexes.json öffnen, werden Sie feststellen, dass die erforderlichen Indizes 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": []
}

Um diese Indizes bereitzustellen, führen Sie den folgenden Befehl aus:

$ firebase deploy --only firestore:indexes

Beachten Sie, dass die Indexerstellung nicht sofort erfolgt. Sie können den Fortschritt in der Firebase-Konsole überwachen.

Konfigurieren Sie die App

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, können Sie entweder:

  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 erneut aus.

Beachten Sie, dass Sie sich möglicherweise von der App abmelden und erneut anmelden müssen, um eine ordnungsgemäße Verbindung zur Produktion herzustellen.