Ćwiczenia z programowania w Cloud Firestore na Androida

1. Omówienie

Cele

Z tego ćwiczenia w Codelabs dowiesz się, jak stworzyć aplikację z rekomendacjami restauracji na Androida korzystającą z Cloud Firestore. Zapoznasz się z tymi zagadnieniami:

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

Wymagania wstępne

Zanim rozpoczniesz to ćwiczenia w programie, sprawdź, czy masz:

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

2. Tworzenie projektu Firebase

  1. Zaloguj się w konsoli Firebase, korzystając ze swojego konta Google.
  2. W konsoli Firebase kliknij Dodaj projekt.
  3. Jak pokazano na zrzucie ekranu poniżej, wpisz nazwę projektu Firebase (na przykład „Przyjazne jedzenie”), a potem kliknij Dalej.

9d2f625aebcab6af.png

  1. Możemy poprosić Cię o włączenie Google Analytics, ponieważ na potrzeby tego ćwiczenia z programowania Twój wybór nie ma 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 na potrzeby tego ćwiczenia z programowania, uruchom następujące polecenie. Spowoduje to utworzenie na Twoim komputerze folderu o nazwie friendlyeats-android:

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

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

Dodaj konfigurację Firebase

  1. W konsoli Firebase w panelu nawigacyjnym po lewej stronie kliknij Przegląd projektu. Kliknij przycisk Android, aby wybrać platformę. Gdy pojawi się prośba o podanie nazwy pakietu, użyj 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/ pobranego kodu. Następnie kliknij Dalej.

Importowanie projektu

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

4. Konfigurowanie emulatorów Firebase

W ramach tego ćwiczenia w programie użyjesz Pakietu emulatorów Firebase do lokalnej emulacji Cloud Firestore i innych usług Firebase. Zapewnia to bezpieczne, szybkie i bezpłatne lokalne środowisko programistyczne do tworzenia aplikacji.

Instalowanie interfejsu wiersza poleceń Firebase

Najpierw musisz zainstalować interfejs wiersza poleceń Firebase. Jeśli używasz systemu macOS lub Linux, możesz uruchomić następujące polecenie cURL:

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

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

Po zainstalowaniu interfejsu wiersza poleceń uruchomienie interfejsu firebase --version powinno zgłosić 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 będzie można dokończyć proces logowania. Pamiętaj, aby wybrać to samo konto co podczas tworzenia projektu Firebase.

W folderze friendlyeats-android uruchom polecenie firebase use --add, aby połączyć swój 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. Uruchom aplikację

Teraz możesz po raz pierwszy uruchomić Pakiet emulatorów Firebase i aplikację FriendsEats na Androida.

Uruchamianie emulatorów

Aby uruchomić emulatory Firebase, w terminalu z katalogu friendlyeats-android uruchom polecenie firebase emulators:start. Logi powinny wyglądać tak:

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

Masz teraz na swoim komputerze kompletne lokalne środowisko programistyczne. Pamiętaj, aby zostawić to polecenie uruchomione w trakcie pozostałej części ćwiczeń z programowania. Aplikacja na Androida będzie musiała łączyć się z emulatorami.

Połącz aplikację z emulatorami

Otwórz pliki util/FirestoreInitializer.kt i util/AuthInitializer.kt w Android Studio. Pliki te zawierają funkcje umożliwiające połączenie pakietów SDK Firebase z lokalnymi emulatorami uruchomionymi na Twoim komputerze po uruchomieniu aplikacji.

Korzystając z metody 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życie operatora BuildConfig pozwala nam łączyć się z emulatorami tylko wtedy, gdy aplikacja działa w trybie debug. Gdy skompilujemy aplikację w trybie release, ten warunek ma wartość Fałsz.

Widzimy, że używa metody useEmulator(host, port) do łączenia pakietu SDK Firebase z lokalnym emulatorem Firestore. W całej aplikacji będziemy używać usługi FirebaseUtil.getFirestore() do uzyskiwania dostępu do tej instancji FirebaseFirestore, więc mamy pewność, że zawsze łączymy się z emulatorem Firestore, gdy działasz w trybie debug.

Uruchom aplikację

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

Uruchom aplikację w Android Studio w emulatorze Androida. Na początku zobaczysz przycisk „Zaloguj się”. ekranu. Do logowania 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, wchodząc na http://localhost:4000 w przeglądarce. Następnie kliknij kartę Authentication (Uwierzytelnianie). Zobaczysz wtedy konto, które właśnie zostało utworzone:

Emulator uwierzytelniania Firebase

Gdy się zalogujesz, powinien wyświetlić się ekran główny aplikacji:

de06424023ffb4b9.png

Wkrótce dodamy dane, które pojawią się na ekranie głównym.

6. Zapisywanie danych w Firestore

W tej sekcji zapiszemy niektóre dane w Firestore, abyśmy mogli zapełnić pusty ekran główny, który aktualnie jest pusty.

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ę zapiszemy jako dokument w kolekcji najwyższego poziomu o nazwie "restaurants". Aby dowiedzieć się więcej o modelu danych Firestore, przeczytaj o dokumentach i kolekcjach w dokumentacji.

W celach demonstracyjnych dodamy do aplikacji funkcję tworzenia 10 losowych restauracji po kliknięciu opcji „Dodaj losowe elementy”. w rozszerzonym menu. Otwórz plik MainFragment.kt i zastąp zawartość metody onAddItemsClicked() następującym:

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

Należy pamiętać o kilku ważnych kwestiach związanych z powyższym kodem:

  • Zaczęliśmy od odniesienia do kolekcji "restaurants". Kolekcje są tworzone domyślnie podczas dodawania dokumentów, więc nie trzeba było tworzyć kolekcji przed zapisem danych.
  • Dokumenty można tworzyć za pomocą klas danych Kotlin, które służą do tworzenia dokumentów dotyczących restauracji.
  • Metoda add() dodaje dokument do kolekcji z automatycznie wygenerowanym identyfikatorem, więc nie musieliśmy określać unikalnego identyfikatora dla każdej restauracji.

Teraz uruchom aplikację jeszcze raz i kliknij „Dodaj losowe elementy”. w rozszerzonym menu (w prawym górnym rogu), aby wywołać napisany przed chwilą kod:

95691e9b71ba55e3.png

Teraz otwórz interfejs emulatorów, wchodząc na http://localhost:4000 w przeglądarce. Następnie kliknij kartę Firestore (Firestore). Zobaczysz dodane przed chwilą dane:

Emulator uwierzytelniania Firebase

Te dane są w całości dostępne lokalnie na Twoim komputerze. W rzeczywistości Twój projekt nie zawiera jeszcze bazy danych Firestore. Oznacza to, że można bezpiecznie eksperymentować z modyfikowaniem i usuwaniem tych danych bez konsekwencji.

Gratulacje, dane zostały właśnie zapisane w Firestore. W następnym kroku dowiemy się, jak wyświetlać te dane w aplikacji.

7. Wyświetl dane z Firestore

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

        // Firestore
        firestore = Firebase.firestore

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

Teraz chcemy wysłuchać zapytania, aby otrzymać wszystkie pasujące dokumenty i być powiadamiane w czasie rzeczywistym o przyszłych aktualizacjach. Naszym ostatecznym celem jest powiązanie tych danych z elementem RecyclerView, dlatego musimy utworzyć klasę RecyclerView.Adapter, aby nasłuchiwać danych.

Otwórz klasę FirestoreAdapter, która została już częściowo zaimplementowana. Najpierw zadbamy o wdrożenie adaptera EventListener i zdefiniowanie funkcji onEvent, tak aby mogła 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()
    }
    
    // ...
}

Przy pierwszym wczytaniu detektor otrzyma jedno zdarzenie ADDED dla każdego nowego dokumentu. W miarę jak zestaw wynikowy zapytania zmienia się w miarę upływu czasu, detektor otrzyma więcej zdarzeń zawierających te zmiany. Teraz dokończmy wdrażanie detektora. Najpierw dodaj 3 nowe metody: onDocumentAdded, onDocumentModified i 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)
    }

Następnie wywołaj te nowe metody z 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 podłączyć odbiornik:

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

Aplikacja jest teraz w pełni skonfigurowana i może odczytywać dane z Firestore. Ponownie uruchom aplikację. Powinny pokazać się restauracje dodane w poprzednim kroku:

9e45f40faefce5d0.png

Wróć do interfejsu emulatora w przeglądarce i zmień nazwę jednej z restauracji. Zmiana powinna być widoczna w aplikacji niemal natychmiast.

8. Sortowanie i filtrowanie danych

Aplikacja wyświetla obecnie najwyżej oceniane restauracje z całej kolekcji, ale w prawdziwej aplikacji restauracyjnej użytkownik chciałby sortować i filtrować dane. Na przykład aplikacja powinna mieć możliwość wyświetlania komunikatu „Najlepsze restauracje z owocami morza w Filadelfii”. lub „Najmniej kosztowna pizza”.

Kliknięcie białego paska u góry aplikacji powoduje wyświetlenie okna filtrów. W tej sekcji będziemy używać zapytań Firestore, aby to okno działało:

67898572a35672a5.png

Edytujmy metodę onFilter() metody MainFragment.kt. Ta metoda akceptuje obiekt Filters, który jest obiektem pomocniczym, który utworzyliśmy do przechwytywania danych wyjściowych z okna filtrów. Zmienimy tę metodę, aby utworzyć 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
    }

We fragmencie kodu powyżej budujemy obiekt Query, dołączając klauzule where i orderBy pasujące do podanych filtrów.

Uruchom aplikację jeszcze raz i wybierz ten filtr, aby wyświetlić najpopularniejsze restauracje w niskich cenach:

7a67a8a400c80c50.png

Powinna się pojawić przefiltrowana lista restauracji zawierających tylko dania o niskiej cenie:

A670188398c3C59.png

Jeśli idzie Ci tak daleko, stworzyliśmy w pełni działającą aplikację do wyświetlania rekomendacji restauracji w Firestore. Możesz teraz sortować i filtrować restauracje w czasie rzeczywistym. W następnych 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, dzięki którym użytkownicy będą mogli recenzować ulubione (lub najmniej ulubione) restauracje.

Kolekcje i podkolekcje

Do tej pory wszystkie dane restauracji zapisaliśmy w kolekcji najwyższego poziomu o nazwie „restauracje”. Gdy użytkownik ocenia restaurację, chcemy dodać do niej nowy obiekt Rating. W tym zadaniu użyjemy kolekcji podrzędnej. Możesz ją sobie wyobrazić jako kolekcję dołączoną do dokumentu. Każdy dokument dotyczący restauracji ma więc podkolekcję zawierającą takie dokumenty. Podkolekcje pomagają uporządkować dane bez nadmiaru dokumentów i wymagania skomplikowanych zapytań.

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

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

Możesz uzyskiwać dostęp do kolekcji podrzędnej i wysyłać do niej zapytania, tak jak w przypadku kolekcji najwyższego poziomu – nie ma żadnych ograniczeń rozmiaru ani zmian skuteczności. Więcej informacji o modelu danych Firestore znajdziesz tutaj.

Zapisywanie danych w transakcji

Dodanie elementu Rating do odpowiedniej kolekcji podrzędnej wymaga jedynie wywołania metody .add(), ale musimy też zaktualizować średnią ocenę i liczbę ocen obiektu Restaurant, aby uwzględnić nowe dane. Jeśli wprowadzimy te 2 zmiany w osobnych operacjach, może dojść do sytuacji, w której mogą wystąpić nieaktualne lub nieprawidłowe dane.

Aby oceny były dodawane prawidłowo, użyjemy transakcji, aby dodać oceny do restauracji. Ta transakcja wykona kilka działań:

  • Przeczytaj aktualną ocenę restauracji i oblicz nową
  • Dodaj ocenę do kolekcji podrzędnej
  • Zaktualizuj średnią ocenę restauracji i liczbę ocen

Otwórz plik RestaurantDetailFragment.kt i zaimplementuj 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 odpowiadają na wynik transakcji.

Teraz ponownie uruchom aplikację i kliknij jedną z restauracji. Powinna się wyświetlić ekran z informacjami o restauracji. Kliknij przycisk +, aby rozpocząć dodawanie opinii. Aby dodać opinię, wybierz liczbę gwiazdek i wpisz tekst.

78fa16cdf8ef435a.png

Kliknięcie Prześlij spowoduje uruchomienie transakcji. Gdy transakcja zostanie zakończona, poniżej wyświetli się Twoja recenzja, a liczba opinii o restauracji zostanie zaktualizowana:

f9e670f40bd615b0.png

Gratulacje! Masz teraz opracowaną w Cloud Firestore aplikację mobilną z recenzjami restauracji na żywo. Podobno są dziś bardzo popularne.

10. Zabezpieczanie danych

Jak dotąd nie uwzględnialiśmy bezpieczeństwa tej aplikacji. Skąd wiadomo, że użytkownicy mogą odczytywać i zapisywać tylko własne dane? Bazy danych Firestore są zabezpieczane w pliku konfiguracji o nazwie Security Rules (Reguły zabezpieczeń).

Otwórz plik firestore.rules. Powinien pojawić się ten plik:

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

Zmień te reguły, aby uniknąć niechcianego dostępu do danych lub zmian. Otwórz plik firestore.rules i zastąp treść tymi:

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 mieć pewność, że klienci wprowadzają tylko bezpieczne zmiany. Na przykład aktualizacja dokumentu dotyczącego restauracji może spowodować tylko zmianę ocen, a nie nazwy ani innych stałych danych. Oceny można tworzyć tylko wtedy, gdy identyfikator użytkownika jest zgodny z zalogowanym użytkownikiem. Zapobiega to podszywaniu się.

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

11. Podsumowanie

Udało Ci się utworzyć w pełni funkcjonalną aplikację oprócz Firestore. Znasz już najważniejsze funkcje Firestore, w tym:

  • Dokumenty i kolekcje
  • Odczyt i zapis danych
  • Sortowanie i filtrowanie za pomocą zapytań
  • Podkolekcje
  • Transakcje

Więcej informacji

Jeśli chcesz nadal dowiedzieć się więcej o Firestore, na początek zapoznaj się z tymi miejscami:

Aplikacja do obsługi restauracji w ramach tego ćwiczenia z programowania powstała w stylu „Przyjazne jedzenie” z przykładową aplikacją. Kod źródłowy tej aplikacji możesz przejrzeć tutaj.

Opcjonalnie: wdróż w środowisku produkcyjnym

Jak dotąd ta aplikacja używała tylko Pakietu emulatorów Firebase. Jeśli chcesz się dowiedzieć, jak wdrożyć tę aplikację w prawdziwym projekcie Firebase, przejdź do następnego kroku.

12. (Opcjonalnie) Wdrażanie aplikacji

Jak dotąd aplikacja była całkowicie lokalna, a wszystkie dane znajdują się w Pakiecie emulatorów Firebase. Z tej sekcji dowiesz się, jak skonfigurować projekt Firebase, aby aplikacja działała w wersji produkcyjnej.

Uwierzytelnianie Firebase

W konsoli Firebase przejdź do sekcji Uwierzytelnianie i kliknij Rozpocznij. Otwórz kartę Metoda logowania i w sekcji Dostawcy reklam natywnych wybierz opcję E-mail/hasło.

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

dostawcy-logowania.png

Firestore

Utwórz bazę danych

Przejdź w konsoli do sekcji Baza danych Firestore i kliknij Utwórz bazę danych:

  1. Gdy pojawi się prośba o uruchomienie reguł zabezpieczeń w trybie produkcyjnym, wkrótce je zaktualizujemy.
  2. Wybierz lokalizację bazy danych, której chcesz używać dla aplikacji. Pamiętaj, że wybór lokalizacji bazy danych jest trwałą decyzją i żeby ją zmienić, musisz utworzyć nowy projekt. Więcej informacji o wybieraniu lokalizacji projektu znajdziesz w dokumentacji.

Wdrażanie reguł

Aby wdrożyć zapisane wcześniej reguły zabezpieczeń, uruchom to polecenie w katalogu ćwiczeń z programowania:

$ firebase deploy --only firestore:rules

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

Wdrażanie indeksów

Aplikacja FriendsEats wymaga skomplikowanego sortowania i filtrowania, co wymaga użycia wielu niestandardowych indeksów złożonych. Można je utworzyć ręcznie w konsoli Firebase, ale prościej jest zapisać ich definicje w pliku firestore.indexes.json i wdrażać je za pomocą interfejsu wiersza poleceń Firebase.

Jeśli otworzysz plik firestore.indexes.json, zobaczysz, że wymagane indeksy zostały już podane:

{
  "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 natychmiastowe. Postęp możesz monitorować w konsoli Firebase.

Konfigurowanie aplikacji

W plikach util/FirestoreInitializer.kt i util/AuthInitializer.kt skonfigurowaliśmy pakiet SDK Firebase w taki sposób, 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ę przy użyciu rzeczywistego projektu Firebase, możesz:

  1. Utwórz aplikację w trybie wydania i uruchom ją na urządzeniu.
  2. Tymczasowo zastąp BuildConfig.DEBUG elementem false i uruchom aplikację ponownie.

Pamiętaj, że aby prawidłowo połączyć się ze ścieżką produkcyjną, może być konieczne wylogowanie się z aplikacji i ponowne zalogowanie się.