Ćwiczenia z programowania w Cloud Firestore na Androida

1. Omówienie

Cele

W tym ćwiczeniu z programowania utworzysz aplikację na Androida z rekomendacjami restauracji, która korzysta z Cloud Firestore. Zapoznasz się z tymi zagadnieniami:

  • Odczytywanie i zapisywanie danych w Firestore z aplikacji na Androida
  • Słuchaj zmian w danych Firestore w czasie rzeczywistym
  • Zabezpieczanie danych Firestore za pomocą uwierzytelniania i reguł zabezpieczeń Firebase
  • Pisanie złożonych zapytań Firestore

Wymagania wstępne

Zanim zaczniesz korzystać z tego Codelab, upewnij się, że masz:

  • Android Studio w wersji Flamingo lub nowszej
  • emulatora Androida z interfejsem API w wersji 19 lub nowszej,
  • Node.js w wersji 16 lub nowszej.
  • Java w wersji 17 lub nowszej.

2. Tworzenie projektu Firebase

  1. Zaloguj się w konsoli Firebase za pomocą konta Google.
  2. W konsoli Firebase kliknij Dodaj projekt.
  3. Jak widać na zrzucie ekranu poniżej, wpisz nazwę projektu Firebase (np. „Friendly Eats”), a potem kliknij Dalej.

9d2f625aebcab6af.png

  1. Możesz otrzymać prośbę o włączenie Google Analytics, ale w ramach tego ćwiczenia nie ma to znaczenia.
  2. Po około minucie Twój projekt Firebase będzie gotowy. Kliknij Dalej.

3. Konfigurowanie przykładowego projektu

Pobieranie kodu

Aby skopiować przykładowy kod do tego ćwiczenia, uruchom to polecenie. Spowoduje to utworzenie na komputerze folderu o nazwie friendlyeats-android:

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

Jeśli nie masz git na komputerze, możesz też pobrać kod bezpośrednio z GitHuba.

Dodawanie konfiguracji Firebase

  1. W konsoli Firebase kliknij Przegląd projektu w menu nawigacyjnym po lewej stronie. Kliknij przycisk Android, aby wybrać platformę. Gdy pojawi się prośba o podanie nazwy pakietu, wpisz com.google.firebase.example.fireeats.

73d151ed16016421.png

  1. Kliknij Zarejestruj aplikację i postępuj zgodnie z instrukcjami, aby pobrać plik google-services.json i przenieść go do folderu app/, w którym znajduje się pobrany przez Ciebie kod. Następnie kliknij Dalej.

Importowanie projektu

Otwórz Android Studio. Kliknij Plik > Nowy > Importuj projekt i wybierz folder friendlyeats-android.

4. Konfigurowanie emulatorów Firebase

W tym laboratorium kodu użyjesz pakietu emulatorów Firebase, aby emulować lokalnie Cloud Firestore i inne usługi Firebase. Dzięki temu możesz tworzyć aplikacje w bezpiecznym, szybkim i bezpłatnym środowisku lokalnym.

Instalowanie wiersza poleceń Firebase

Najpierw musisz zainstalować wiersz poleceń Firebase. Jeśli używasz systemu macOS lub Linux, możesz uruchomić to polecenie cURL:

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

Jeśli używasz systemu Windows, przeczytaj instrukcje instalacji, aby pobrać samodzielny plik binarny lub zainstalować program za pomocą npm.

Po zainstalowaniu interfejsu wiersza poleceń uruchomienie firebase --version powinno wygenerować wersję 9.0.0 lub nowszą:

$ firebase --version
9.0.0

Zaloguj się

Uruchom firebase login, aby połączyć interfejs wiersza poleceń z kontem Google. Otworzy się nowe okno przeglądarki, w którym możesz dokończyć proces logowania. Pamiętaj, aby wybrać to samo konto, którego użyto wcześniej podczas tworzenia projektu Firebase.

W folderze friendlyeats-android uruchom firebase use --add, aby połączyć projekt lokalny z projektem Firebase. Postępuj zgodnie z instrukcjami, aby wybrać utworzony wcześniej projekt. Jeśli pojawi się prośba o wybranie aliasu, wpisz default.

5. Uruchamianie aplikacji

Teraz czas na pierwsze uruchomienie pakietu emulatorów Firebase i aplikacji FriendlyEats na Androida.

Uruchamianie emulatorów

Aby uruchomić emulatory Firebase, w terminalu w katalogu friendlyeats-android uruchom firebase emulators:start. Powinny pojawić się takie dzienniki:

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

Na komputerze masz teraz pełne lokalne środowisko programistyczne. Podczas dalszej pracy z tym ćwiczeniem nie zamykaj tego polecenia, ponieważ Twoja aplikacja na Androida musi połączyć się z emulatorami.

Łączenie aplikacji z emulatorami

Otwórz pliki util/FirestoreInitializer.ktutil/AuthInitializer.kt w Android Studio. Te pliki zawierają logikę, która łączy pakiety SDK Firebase z lokalnymi emulatorami uruchomionymi na Twoim komputerze po uruchomieniu aplikacji.

W metodzie create() klasy FirestoreInitializer sprawdź ten fragment kodu:

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

Używamy BuildConfig, aby mieć pewność, że łączymy się z emulatorami tylko wtedy, gdy aplikacja działa w trybie debug. Gdy skompilujemy aplikację w trybie release, to warunek będzie fałszywy.

Widzimy, że do połączenia pakietu Firebase SDK z lokalnym emulatorem Firestore używana jest metoda useEmulator(host, port). W całej aplikacji używamy funkcji FirebaseUtil.getFirestore(), aby uzyskać dostęp do tej instancji usługi FirebaseFirestore. Dzięki temu mamy pewność, że w trybie debug zawsze łączymy się z emulatorem Firestore.

Uruchamianie aplikacji

Jeśli plik google-services.json został dodany prawidłowo, projekt powinien się skompilować. W Android Studio kliknij Kompiluj > Ponownie skompiluj projekt i upewnij się, że nie ma żadnych błędów.

W Android Studio uruchom aplikację w emulatorze Androida. Najpierw zobaczysz ekran logowania. Do zalogowania się w aplikacji możesz użyć dowolnego adresu e-mail i hasła. Ten proces logowania łączy się z emulatorem Uwierzytelniania Firebase, więc nie są przesyłane żadne prawdziwe dane logowania.

Teraz otwórz interfejs emulatorów, wpisując w przeglądarce adres http://localhost:4000. Następnie kliknij kartę Uwierzytelnianie. Powinno się wyświetlić utworzone konto:

Emulator Uwierzytelniania Firebase

Po zakończeniu procesu logowania powinien wyświetlić się ekran główny aplikacji:

de06424023ffb4b9.png

Wkrótce dodamy dane, aby wypełnić ekran główny.

6. Zapisywanie danych w Firestore

W tej sekcji zapiszemy w Firestore kilka danych, aby wypełnić pusty ekran główny.

Głównym obiektem modelu w naszej aplikacji jest restauracja (patrz model/Restaurant.kt). Dane Firestore są podzielone na dokumenty, kolekcje i podkolekcje. Każdą restaurację będziemy przechowywać jako dokument w kolekcji najwyższego poziomu o nazwie "restaurants". Więcej informacji o modelu danych Firestore znajdziesz w dokumentacji dotyczącej dokumentów i kolekcji.

W ramach demonstracji dodamy w aplikacji funkcję, która po kliknięciu przycisku „Dodaj losowe elementy” w menu przepełnienia spowoduje utworzenie 10 losowych restauracji. Otwórz plik MainFragment.kt i zastąp zawartość metody onAddItemsClicked() tym fragmentem kodu:

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

W przypadku tego kodu należy zwrócić uwagę na kilka ważnych kwestii:

  • Najpierw uzyskaliśmy odwołanie do kolekcji "restaurants". Kolekcje są tworzone domyślnie podczas dodawania dokumentów, więc nie trzeba było tworzyć kolekcji przed zapisaniem danych.
  • Dokumenty można tworzyć za pomocą klas danych Kotlin, których używamy do tworzenia każdego dokumentu restauracji.
  • Metoda add() dodaje dokument do kolekcji z automatycznie wygenerowanym identyfikatorem, więc nie musieliśmy podawać unikalnego identyfikatora dla każdej restauracji.

Uruchom ponownie aplikację i w menu rozszerzonym (w prawym górnym rogu) kliknij przycisk „Dodaj losowe elementy”, aby wywołać kod, który właśnie napisałeś:

95691e9b71ba55e3.png

Teraz otwórz interfejs emulatorów, wpisując w przeglądarce adres http://localhost:4000. Następnie kliknij kartę Firestore. Powinny się tam wyświetlić właśnie dodane dane:

Emulator Uwierzytelniania Firebase

Te dane są w 100% lokalne na Twoim komputerze. W Twoim prawdziwym projekcie nie ma jeszcze bazy danych Firestore. Oznacza to, że możesz bezpiecznie eksperymentować z modyfikowaniem i usuwaniem tych danych bez żadnych konsekwencji.

Gratulacje! Właśnie zapisaliśmy dane do Firestore. W następnym kroku dowiesz się, jak wyświetlać te dane w aplikacji.

7. Wyświetlanie danych z Firestore

W tym kroku dowiesz się, jak pobierać dane z Firestore i wyświetlać je w aplikacji. Pierwszym krokiem do odczytania danych z Firestore jest utworzenie Query. Otwórz plik MainFragment.kt i na początku metody onViewCreated() dodaj ten kod:

        // Firestore
        firestore = Firebase.firestore

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

Teraz chcemy przesłuchać zapytanie, aby uzyskać wszystkie pasujące dokumenty i otrzymywać w czasie rzeczywistym powiadomienia o przyszłych aktualizacjach. Naszym ostatecznym celem jest powiązanie tych danych z elementem RecyclerView, więc musimy utworzyć klasę RecyclerView.Adapter, która będzie je odbierać.

Otwórz klasę FirestoreAdapter, która została już częściowo zaimplementowana. Najpierw sprawmy, aby adapter zaimplementował interfejs EventListener i zdefiniował funkcję onEvent, tak aby mógł otrzymywać aktualizacje zapytania Firestore:

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

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

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

        onDataChanged()
    }
    
    // ...
}

Podczas początkowego wczytywania detektory otrzymują po jednym zdarzeniu ADDED dla każdego nowego dokumentu. Gdy z czasem zmienia się zbiór wyników zapytania, detektor będzie otrzymywać więcej zdarzeń zawierających te zmiany. Teraz dokończ implementację listenera. Najpierw dodaj 3 nowe metody: onDocumentAdded, onDocumentModifiedonDocumentRemoved:

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

Następnie wywołaj te nowe metody z poziomu onEvent:

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

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

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

        onDataChanged()
    }

Na koniec zaimplementuj metodę startListening(), aby dołączyć obiekt do odbiorcy:

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

Aplikacja jest teraz w pełni skonfigurowana do odczytu danych z Firestore. Uruchom aplikację ponownie. Powinny pojawić się restauracje dodane w poprzednim kroku:

9e45f40faefce5d0.png

Wróć do interfejsu emulatora w przeglądarce i zmodyfikuj nazwę jednej z restauracji. Zmiana powinna nastąpić w aplikacji niemal natychmiast.

8. sortować i filtrować dane,

Aplikacja wyświetla obecnie restauracje z najlepszymi ocenami w całej kolekcji, ale w rzeczywistej aplikacji użytkownik chciałby sortować i filtrować dane. Aplikacja powinna np. wyświetlać „Najlepsze restauracje z owocami morza w Filadelfii” lub „Najtańsza pizza”.

Kliknięcie białego paska u góry aplikacji powoduje wyświetlenie okna filtrów. W tej sekcji użyjemy zapytań Firestore, aby ten dialog działał:

67898572a35672a5.png

Zmieńmy metodę onFilter() w klasie MainFragment.kt. Ta metoda przyjmuje obiekt Filters, który jest obiektem pomocniczym utworzonym przez nas w celu przechwycenia danych wyjściowych z okna dialogowego filtrów. Zmienimy tę metodę, aby tworzyć zapytanie na podstawie filtrów:

    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
    }

W kodzie źródłowym powyżej tworzymy obiekt Query, dołączając klauzule where i orderBy, aby dopasować je do podanych filtrów.

Uruchom aplikację ponownie i wybierz filtr, aby wyświetlić najpopularniejsze restauracje w niskiej cenie:

7a67a8a400c80c50.png

Powinna wyświetlić się lista restauracji zawierająca tylko opcje z niskimi cenami:

a670188398c3c59.png

Jeśli dotarłeś/-aś do tego miejsca, masz już w Firestore w pełni działającą aplikację do wyświetlania rekomendacji restauracji. Teraz możesz sortować i filtrować restauracje w czasie rzeczywistym. W kolejnych sekcjach dodamy opinie o restauracjach i reguły bezpieczeństwa do aplikacji.

9. Porządkowanie danych w podkolekcjach

W tej sekcji dodamy do aplikacji oceny, aby użytkownicy mogli oceniać swoje ulubione (lub najmniej ulubione) restauracje.

Kolekcje i podkolekcje

Do tej pory wszystkie dane o restauracjach były przechowywane w kolekcji najwyższego poziomu o nazwie „restauracje”. Gdy użytkownik oceni restaurację, chcemy dodać do niej nowy obiekt Rating. W tym zadaniu użyjemy podkolekcji. Kolekcję podrzędną można traktować jako kolekcję dołączoną do dokumentu. Każdy dokument restauracji będzie zawierał podkolekcją ocen z dokumentami oceny. Podzbiory ułatwiają porządkowanie danych bez nadmiernego rozrastania się dokumentów czy konieczności tworzenia skomplikowanych zapytań.

Aby uzyskać dostęp do podkolekcji, wywołaj funkcję .collection() w dokumencie nadrzędnym:

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

Możesz uzyskać dostęp do podkolekcji i wysłać zapytanie do niej tak samo jak do kolekcji najwyższego poziomu. Nie ma żadnych ograniczeń rozmiaru ani zmian w skuteczności. Więcej informacji o modelu danych Firestore znajdziesz tutaj.

Zapisywanie danych w transakcji

Dodanie Rating do odpowiedniej podzbiory wymaga tylko wywołania .add(), ale musimy też zaktualizować średnią ocen i liczbę ocen obiektu Restaurant, aby odzwierciedlić nowe dane. Jeśli użyjemy oddzielnych operacji do wprowadzenia tych 2 zmian, może to spowodować wystąpienie kilku warunków wyścigu, które mogą skutkować nieaktualnymi lub nieprawidłowymi danymi.

Aby mieć pewność, że oceny są dodawane prawidłowo, użyjemy transakcji do dodania ocen restauracji. Ta transakcja wykona kilka działań:

  • Sprawdź aktualną ocenę restauracji i oblicz nową.
  • Dodawanie oceny do podkolekcji
  • Zaktualizuj średnią ocenę i liczbę ocen restauracji

Otwórz RestaurantDetailFragment.kt i wdróż funkcję 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
        }
    }

Funkcja addRating() zwraca wartość Task reprezentującą całą transakcję. Do zadania dodawane są detektory funkcji onRating(), które reagują na wynik transakcji.

Ponownie uruchom aplikację i kliknij jedną z restauracji, aby wyświetlić ekran z informacjami o niej. Kliknij przycisk +, aby rozpocząć dodawanie opinii. Dodaj opinię, wybierając liczbę gwiazdek i wpisując tekst.

78fa16cdf8ef435a.png

Kliknięcie Prześlij rozpocznie transakcję. Po zakończeniu transakcji Twoja opinia pojawi się poniżej, a liczba opinii o restauracji zostanie zaktualizowana:

f9e670f40bd615b0.png

Gratulacje! Masz teraz aplikację mobilną z opiniami o restauracjach, która wykorzystuje Cloud Firestore. Podobno są teraz bardzo popularne.

10. Bezpieczeństwo danych

Do tej pory nie zbadaliśmy bezpieczeństwa tej aplikacji. Skąd wiemy, że użytkownicy mogą tylko odczytywać i zapisywać prawidłowe dane? Bazy danych Firestore są chronione przez plik konfiguracji o nazwie Reguły zabezpieczeń.

Otwórz plik firestore.rules i zastąp jego zawartość tym kodem:

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

Te reguły ograniczają dostęp, aby zapewnić, że klienci wprowadzają tylko bezpieczne zmiany. Na przykład aktualizacje dokumentu restauracji mogą dotyczyć tylko ocen, a nie nazwy ani innych danych, których nie można zmienić. Oceny mogą być tworzone tylko wtedy, gdy identyfikator użytkownika zgadza się z zalogowanym użytkownikiem, co zapobiega podszywanie się pod inną osobę.

Więcej informacji o regułach zabezpieczeń znajdziesz w dokumentacji.

11. Podsumowanie

Utworzyłeś/utworzyłaś w Firestore w pełni funkcjonalną aplikację. Poznasz najważniejsze funkcje Firestore, w tym:

  • Dokumenty i kolekcje
  • Odczytywanie i zapisywanie danych
  • Sortowanie i filtrowanie za pomocą zapytań
  • Podkolekcje
  • Transakcje

Więcej informacji

Aby dowiedzieć się więcej o Firestore, zapoznaj się z tymi materiałami:

Aplikacja restauracji w tym samouczku została oparta na przykładowej aplikacji „Friendly Eats”. Kod źródłowy tej aplikacji znajdziesz tutaj.

Opcjonalnie: wdróż w gałęzi produkcyjnej

Do tej pory aplikacja korzystała tylko z pakietu emulatorów Firebase. Jeśli chcesz dowiedzieć się, jak wdrożyć tę aplikację do prawdziwego projektu Firebase, przejdź do następnego kroku.

12. (Opcjonalnie) Wdróż aplikację

Dotychczas ta aplikacja była całkowicie lokalna, a wszystkie dane były przechowywane w Pakiecie emulatorów Firebase. W tej sekcji dowiesz się, jak skonfigurować projekt Firebase, aby aplikacja działała w produkcji.

Uwierzytelnianie Firebase

W konsoli Firebase otwórz sekcję Uwierzytelnianie i kliknij Rozpocznij. Otwórz kartę Metoda logowania i w sekcji Natywni dostawcy wybierz opcję E-mail/hasło.

Włącz metodę logowania Adres e-mail/hasło i kliknij Zapisz.

sign-in-providers.png

Firestore

Utwórz bazę danych

Otwórz w konsoli sekcję Baza danych Firestore i kliknij Utwórz bazę danych:

  1. Gdy pojawi się pytanie o reguły bezpieczeństwa, wybierz Tryb produkcyjny. Wkrótce zaktualizujemy te reguły.
  2. Wybierz lokalizację bazy danych, której chcesz używać w aplikacji. Pamiętaj, że wybranie lokalizacji bazy danych jest trwałą decyzją, a aby ją zmienić, musisz utworzyć nowy projekt. Więcej informacji o wybieraniu lokalizacji projektu znajdziesz w dokumentacji.

Reguły wdrażania

Aby wdrożyć wcześniej napisane reguły bezpieczeństwa, uruchom w katalogu codelab to polecenie:

$ firebase deploy --only firestore:rules

Spowoduje to wdrożenie zawartości pliku firestore.rules do projektu. Możesz to sprawdzić na karcie Reguły w konsoli.

Wdrażanie indeksów

Aplikacja FriendlyEats ma złożone sortowanie i filtrowanie, które wymaga wielu niestandardowych indeksów złożonych. Można je tworzyć ręcznie w konsoli Firebase, ale łatwiej jest wpisać ich definicje w pliku firestore.indexes.json i wdrażać je za pomocą interfejsu wiersza poleceń Firebase.

Po otwarciu pliku firestore.indexes.json zobaczysz, że wymagane indeksy zostały już przesłane:

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

Aby wdrożyć te indeksy, uruchom to polecenie:

$ firebase deploy --only firestore:indexes

Pamiętaj, że tworzenie indeksu nie jest procesem natychmiastowym. Możesz jednak śledzić jego postępy w konsoli Firebase.

Konfigurowanie aplikacji

W plikach util/FirestoreInitializer.ktutil/AuthInitializer.kt skonfigurowaliśmy pakiet SDK Firebase tak, aby łączył się z emulatorami w trybie debugowania:

    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
    }

Jeśli chcesz przetestować aplikację w prawdziwym projekcie Firebase, możesz:

  1. Kompiluj aplikację w trybie wersji i uruchamiaj ją na urządzeniu.
  2. Tymczasowo zastąp BuildConfig.DEBUG wartością false i ponownie uruchom aplikację.

Pamiętaj, że aby prawidłowo połączyć się z wersją produkcyjną, musisz wylogować się z aplikacji i ponownie się zalogować.