Codelab ל-Cloud Firestore ל-Android

1. סקירה כללית

מטרות עסקיות

בקודלאב הזה תלמדו ליצור אפליקציה להמלצות על מסעדות ל-Android שמבוססת על Cloud Firestore. תלמדו איך:

  • קריאה וכתיבה של נתונים ב-Firestore מאפליקציית Android
  • האזנה לשינויים בנתונים של Firestore בזמן אמת
  • שימוש באימות ובכללי אבטחה של Firebase לאבטחת נתוני Firestore
  • כתיבת שאילתות מורכבות ב-Firestore

דרישות מוקדמות

לפני שמתחילים את סדנת הקוד, צריך לוודא שיש לכם:

  • Android Studio מגרסה Flamingo ואילך
  • אמולטור Android עם API מגרסה 19 ואילך
  • Node.js בגרסה 16 ואילך
  • Java בגרסה 17 ואילך

2. יצירת פרויקט Firebase

  1. נכנסים למסוף Firebase באמצעות חשבון Google.
  2. במסוף Firebase, לוחצים על Add project (הוספת פרויקט).
  3. כפי שמוצג בצילום המסך שבהמשך, מזינים שם לפרויקט ב-Firebase (לדוגמה, 'Friendly Eats') ולוחצים על Continue (המשך).

9d2f625aebcab6af.png

  1. יכול להיות שתתבקשו להפעיל את Google Analytics. לצורכי הקודלאב הזה, הבחירה שלכם לא חשובה.
  2. אחרי דקה בערך, פרויקט Firebase יהיה מוכן. לוחצים על המשך.

3. הגדרת פרויקט לדוגמה

מורידים את הקוד

מריצים את הפקודה הבאה כדי לשכפל את קוד הדוגמה של סדנת הקוד הזו. הפקודה הזו תיצור במחשב תיקייה בשם friendlyeats-android:

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

אם אין לכם את git במחשב, אתם יכולים גם להוריד את הקוד ישירות מ-GitHub.

הוספת הגדרות של Firebase

  1. במסוף Firebase, בוחרים באפשרות סקירה כללית של הפרויקט בתפריט הניווט הימני. לוחצים על הלחצן Android כדי לבחור את הפלטפורמה. כשמוצגת בקשה להזין שם חבילה, מזינים com.google.firebase.example.fireeats

73d151ed16016421.png

  1. לוחצים על רישום אפליקציה ופועלים לפי ההוראות כדי להוריד את הקובץ google-services.json ולהעביר אותו לתיקייה app/ של הקוד שהורדתם. לאחר מכן לוחצים על Next.

מייבאים את הפרויקט

פותחים את Android Studio. לוחצים על קובץ > חדש > ייבוא פרויקט ובוחרים בתיקייה friendlyeats-android.

4. הגדרת אמולטורים של Firebase

ב-codelab הזה תשתמשו ב-Firebase Emulator Suite כדי לדמות באופן מקומי את Cloud Firestore ושירותי Firebase אחרים. כך תוכלו ליצור סביבה מקומית לפיתוח מהירה, בטוחה וללא עלות, שבה תוכלו לפתח את האפליקציה.

התקנת ה-CLI של Firebase

קודם כול צריך להתקין את Firebase CLI. אם אתם משתמשים ב-macOS או ב-Linux, אתם יכולים להריץ את פקודת ה-cURL הבאה:

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

אם אתם משתמשים ב-Windows, כדאי לקרוא את הוראות ההתקנה כדי לקבל קובץ בינארי עצמאי או כדי להתקין דרך npm.

אחרי שתתקינו את ה-CLI, הפעלת firebase --version אמורה לדווח על גרסה 9.0.0 ואילך:

$ firebase --version
9.0.0

התחברות

מריצים את הפקודה firebase login כדי לקשר את CLI לחשבון Google. ייפתח חלון חדש בדפדפן כדי להשלים את תהליך ההתחברות. חשוב לבחור באותו חשבון שבו השתמשתם כשיצרתם את פרויקט Firebase מקודם.

בתיקייה friendlyeats-android, מריצים את הפקודה firebase use --add כדי לקשר את הפרויקט המקומי לפרויקט Firebase. פועלים לפי ההנחיות כדי לבחור את הפרויקט שיצרתם מקודם. אם מוצגת בקשה לבחור כינוי, מזינים default.

5. הפעלת האפליקציה

עכשיו הגיע הזמן להפעיל את ערכת האמולטורים של Firebase ואת אפליקציית FriendlyEats ל-Android בפעם הראשונה.

הפעלת האמולטורים

בטרמינל, מתוך הספרייה friendlyeats-android, מריצים את הפקודה firebase emulators:start כדי להפעיל את אמולרטורים של Firebase. היומנים אמורים להיראות כך:

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

עכשיו יש לכם סביבת פיתוח מקומית מלאה שפועלת במחשב. חשוב להשאיר את הפקודה הזו פועלת במהלך כל שאר הקודלאב, כי אפליקציית Android תצטרך להתחבר למהדמנים.

קישור האפליקציה לאמולטורים

פותחים את הקבצים util/FirestoreInitializer.kt ו-util/AuthInitializer.kt ב-Android Studio. הקבצים האלה מכילים את הלוגיקה לחיבור ערכות ה-SDK של Firebase למהדמנים המקומיים שפועלים במחשב, בזמן הפעלת האפליקציה.

ב-method‏ create() של הכיתה FirestoreInitializer, בודקים את קטע הקוד הזה:

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

אנחנו משתמשים ב-BuildConfig כדי לוודא שנתחבר לאמולטורים רק כשהאפליקציה שלנו פועלת במצב debug. כשאנחנו קוראים את האפליקציה במצב release, התנאי הזה יהיה שקר.

אפשר לראות שהשיטה useEmulator(host, port) משמשת לחיבור Firebase SDK למהדמ של Firestore המקומי. לאורך האפליקציה נשתמש ב-FirebaseUtil.getFirestore() כדי לגשת למכונה הזו של FirebaseFirestore, כדי שנוכל לוודא שאנחנו תמיד מתחברים לאמולטור של Firestore כשאנחנו מריצים במצב debug.

הפעלת האפליקציה

אם הוספתם את הקובץ google-services.json בצורה נכונה, עכשיו הפרויקט אמור להיערך. ב-Android Studio, לוחצים על Build (פיתוח) > Rebuild Project (פיתוח מחדש של הפרויקט) ומוודאים שאין שגיאות נוספות.

ב-Android Studio, מריצים את האפליקציה במהדמנת Android. קודם תוצג לכם מסך 'כניסה'. אפשר להשתמש בכל כתובת אימייל וסיסמה כדי להיכנס לאפליקציה. תהליך הכניסה הזה מתחבר למהדר של אימות Firebase, כך שלא מועברים פרטי כניסה אמיתיים.

עכשיו פותחים את ממשק המשתמש של המהדמנים. לשם כך, עוברים לכתובת http://localhost:4000 בדפדפן האינטרנט. לאחר מכן לוחצים על הכרטיסייה Authentication (אימות) ואמור להופיע החשבון שיצרתם:

Firebase Auth Emulator

אחרי שתשלימו את תהליך הכניסה, אמור להופיע מסך הבית של האפליקציה:

de06424023ffb4b9.png

בקרוב נוסיף נתונים כדי לאכלס את מסך הבית.

6. כתיבת נתונים ב-Firestore

בקטע הזה נכתוב נתונים מסוימים ב-Firestore כדי שנוכל לאכלס את מסך הבית הריק.

אובייקט המודל הראשי באפליקציה שלנו הוא מסעדה (ראו model/Restaurant.kt). הנתונים ב-Firestore מחולקים למסמכים, לקולקציות ולקולקציות משנה. נשמור כל מסעדה כמסמך באוסף ברמה העליונה שנקרא "restaurants". מידע נוסף על מודל הנתונים של Firestore זמין במסמכי העזרה.

למטרות הדגמה, נוסיף לאפליקציה פונקציונליות שתאפשר ליצור עשרה מסעדות אקראיות בלחיצה על הלחצן 'הוספת פריטים אקראיים' בתפריט הנפתח. פותחים את הקובץ MainFragment.kt ומחליפים את התוכן בשיטה onAddItemsClicked() בקוד הבא:

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

יש כמה דברים חשובים שצריך לזכור לגבי הקוד שלמעלה:

  • התחלנו בקבלת הפניה לאוסף "restaurants". האוספים נוצרים באופן משתמע כשמוסיפים מסמכים, כך שלא היה צורך ליצור את האוסף לפני כתיבת הנתונים.
  • אפשר ליצור מסמכים באמצעות כיתות נתונים של Kotlin, שבהן אנחנו משתמשים כדי ליצור כל מסמך מסוג מסעדה.
  • השיטה add() מוסיפה מסמך לקולקציה עם מזהה שנוצר באופן אוטומטי, כך שלא נדרשנו לציין מזהה ייחודי לכל מסעדה.

עכשיו מריצים שוב את האפליקציה ולוחצים על הלחצן 'הוספת פריטים אקראיים' בתפריט הנפתח (בפינה השמאלית העליונה) כדי להפעיל את הקוד שכתבתם:

95691e9b71ba55e3.png

עכשיו פותחים את ממשק המשתמש של המהדמנים. לשם כך, עוברים לכתובת http://localhost:4000 בדפדפן האינטרנט. לאחר מכן לוחצים על הכרטיסייה Firestore ואמורים להופיע הנתונים שהוספתם:

Firebase Auth Emulator

הנתונים האלה הם מקומיים במחשב שלכם בלבד. למעשה, הפרויקט האמיתי שלכם עדיין לא מכיל מסד נתונים של Firestore. כלומר, אפשר להתנסות בשינוי ובמחיקה של הנתונים האלה ללא חשש.

כל הכבוד, כתבתם נתונים ב-Firestore! בשלב הבא נסביר איך להציג את הנתונים האלה באפליקציה.

7. הצגת נתונים מ-Firestore

בשלב הזה תלמדו איך לאחזר נתונים מ-Firestore ולהציג אותם באפליקציה. השלב הראשון בקריאת נתונים מ-Firestore הוא ליצור Query. פותחים את הקובץ MainFragment.kt ומוסיפים את הקוד הבא לתחילת השיטה onViewCreated():

        // Firestore
        firestore = Firebase.firestore

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

עכשיו אנחנו רוצים להאזין לשאילתה, כדי שנקבל את כל המסמכים התואמים ונקבל התראות על עדכונים עתידיים בזמן אמת. המטרה הסופית שלנו היא לקשר את הנתונים האלה ל-RecyclerView, לכן אנחנו צריכים ליצור כיתה RecyclerView.Adapter כדי להאזין לנתונים.

פותחים את הכיתה FirestoreAdapter, שכבר הופעלה חלקית. קודם כול, נגרום למתאם להטמיע את EventListener ולהגדיר את הפונקציה onEvent כך שתוכל לקבל עדכונים לשאילתה ב-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()
    }
    
    // ...
}

בטעינה הראשונית, המאזין יקבל אירוע ADDED אחד לכל מסמך חדש. כאשר קבוצת התוצאות של השאילתה משתנה לאורך זמן, המאזין יקבל יותר אירועים שמכילים את השינויים. עכשיו נסיים את הטמעת המאזין. קודם מוסיפים שלוש שיטות חדשות: onDocumentAdded,‏ onDocumentModified ו-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)
    }

לאחר מכן, קוראים לשיטות החדשות האלה מ-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()
    }

לבסוף, מטמיעים את השיטה startListening() כדי לצרף את המאזין:

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

עכשיו האפליקציה מוגדרת באופן מלא לקריאת נתונים מ-Firestore. מריצים שוב את האפליקציה, ואמורים להופיע בה המסעדות שהוספתם בשלב הקודם:

9e45f40faefce5d0.png

עכשיו חוזרים לממשק המשתמש של המהדר בדפדפן ועורכים את אחד משמות המסעדות. השינוי אמור להופיע באפליקציה כמעט באופן מיידי.

8. מיון וסינון של נתונים

נכון לעכשיו, האפליקציה מציגה את המסעדות עם הדירוג הגבוה ביותר מתוך כל האוסף, אבל באפליקציה אמיתית של מסעדות, המשתמשים רוצים למיין ולסנן את הנתונים. לדוגמה, האפליקציה אמורה להיות מסוגלת להציג את האפשרויות 'מסעדות הדגים והמאכלי הים המובילות בפילדלפיה' או 'הפיצה הזולה ביותר'.

לחיצה על הסרגל הלבן בחלק העליון של האפליקציה תציג תיבת דו-שיח של מסננים. בקטע הזה נשתמש בשאילתות של Firestore כדי להפעיל את תיבת הדו-שיח הזו:

67898572a35672a5.png

נערוך את השיטה onFilter() של MainFragment.kt. השיטה הזו מקבלת אובייקט Filters שהוא אובייקט עזר שיצרנו כדי לתעד את הפלט של תיבת הדו-שיח של המסננים. נשנה את השיטה הזו כדי ליצור שאילתות מהמסננים:

    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
    }

בקטע הקוד שלמעלה אנחנו יוצרים אובייקט Query על ידי צירוף תנאי where ו-orderBy כדי להתאים למסננים הנתונים.

מריצים שוב את האפליקציה ובוחרים את המסנן הבא כדי להציג את המסעדות הפופולריות ביותר במחירים נמוכים:

7a67a8a400c80c50.png

עכשיו אמורה להופיע רשימה מסוננת של מסעדות שמכילה רק אפשרויות במחירים נמוכים:

a670188398c3c59.png

אם הגעתם עד כאן, סימן שיצרתם אפליקציה פונקציונלית לחלוטין להצגת המלצות למסעדות ב-Firestore. עכשיו אפשר למיין ולסנן מסעדות בזמן אמת. בחלקים הבאים נוסיף ביקורות למסעדות ונוסיף כללי אבטחה לאפליקציה.

9. ארגון נתונים באוספים משניים

בקטע הזה נוסיף דירוגים לאפליקציה כדי שהמשתמשים יוכלו לכתוב ביקורות על המסעדות האהובות עליהם (או הכי פחות אהובות).

אוספים ואוספים משניים

עד עכשיו שמרתם את כל נתוני המסעדות באוסף ברמה העליונה שנקרא 'מסעדות'. כשמשתמש נותן דירוג למסעדה, אנחנו רוצים להוסיף אובייקט Rating חדש למסעדות. במשימה הזו נשתמש באוסף משנה. אוסף משנה הוא אוסף שמצורף למסמך. לכן לכל מסמך מסעדה תהיה אוסף משנה של דירוגים שמכיל מסמכי דירוג. אוספים משניים עוזרים לארגן את הנתונים בלי להגדיל את נפח המסמכים או לדרוש שאילתות מורכבות.

כדי לגשת לאוסף משנה, צריך להפעיל את .collection() במסמך ההורה:

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

אפשר לגשת לאוסף משנה ולבצע בו שאילתות בדיוק כמו באוסף ברמה העליונה, ללא הגבלות גודל או שינויים בביצועים. כאן אפשר לקרוא מידע נוסף על מודל הנתונים של Firestore.

כתיבת נתונים בעסקה

כדי להוסיף Rating לאוסף המשנה המתאים, צריך רק להפעיל את .add(), אבל צריך גם לעדכן את הדירוג הממוצע ואת מספר הדירוגים של האובייקט Restaurant כך שישקפו את הנתונים החדשים. אם נשתמש בפעולות נפרדות כדי לבצע את שני השינויים האלה, יש כמה תנאי תחרות שעלולים לגרום לנתונים לא עדכניים או שגויים.

כדי לוודא שהדירוגים מתווספים בצורה תקינה, נשתמש בעסקה כדי להוסיף דירוגים למסעדה. העסקה הזו תבצע כמה פעולות:

  • קוראים את הדירוג הנוכחי של המסעדה ומחשבים את הדירוג החדש
  • מוסיפים את הדירוג לאוסף המשנה
  • עדכון הדירוג הממוצע ומספר הדירוגים של המסעדה

פותחים את RestaurantDetailFragment.kt ומטמיעים את הפונקציה 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
        }
    }

הפונקציה addRating() מחזירה Task שמייצג את העסקה כולה. בפונקציה onRating(), מאזינים מתווספים למשימה כדי להגיב לתוצאה של העסקה.

עכשיו מריצים שוב את האפליקציה ולוחצים על אחת מהמסעדות. המסך עם פרטי המסעדה אמור להופיע. לוחצים על הלחצן + כדי להתחיל להוסיף ביקורת. כדי להוסיף ביקורת, בוחרים מספר כוכבים ומזינים טקסט.

78fa16cdf8ef435a.png

לחיצה על Submit תפעיל את העסקה. כשהעסקה תושלם, הביקורת שלכם תוצג למטה ותופיע עדכון למספר הביקורות של המסעדה:

f9e670f40bd615b0.png

מזל טוב! עכשיו יש לכם אפליקציה לנייד עם ביקורות על מסעדות, שמבוססת על Cloud Firestore. אני שומע שהם פופולריים מאוד בימים האלה.

10. אבטחת הנתונים

עד כה לא התייחסנו לאבטחה של האפליקציה הזו. איך אנחנו יודעים שהמשתמשים יכולים לקרוא ולכתוב רק את הנתונים הנכונים שלהם? מסדי הנתונים של Firestore מאובטחים באמצעות קובץ תצורה שנקרא Security Rules.

פותחים את הקובץ firestore.rules ומחליפים את התוכן בקוד הבא:

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

הכללים האלה מגבילים את הגישה כדי לוודא שהלקוחות מבצעים רק שינויים בטוחים. לדוגמה, עדכונים במסמך של מסעדה יכולים לשנות רק את הדירוגים, ולא את השם או נתונים אחרים שלא ניתן לשנות. אפשר ליצור דירוגים רק אם מזהה המשתמש תואם למשתמש שמחובר לחשבון, וכך למנוע זיופים.

מידע נוסף על כללי אבטחה זמין במסמכי התיעוד.

11. סיכום

עכשיו יצרתם אפליקציה עם כל התכונות שמבוססת על Firestore. למדתם על התכונות החשובות ביותר של Firestore, כולל:

  • מסמכים ואוספים
  • קריאה וכתיבה של נתונים
  • מיון וסינון באמצעות שאילתות
  • אוספי משנה
  • עסקאות

מידע נוסף

כדי להמשיך ללמוד על Firestore, ריכזנו כאן כמה מקורות מידע שיכולים לעזור לכם להתחיל:

אפליקציית המסעדות בקודלאב הזה מבוססת על אפליקציית הדוגמה 'Friendly Eats'. אפשר לעיין בקוד המקור של האפליקציה הזו כאן.

אופציונלי: פריסה בסביבת הייצור

עד כה, באפליקציה הזו נעשה שימוש רק בחבילת הכלים של אמולטור Firebase. כדי ללמוד איך לפרוס את האפליקציה הזו בפרויקט Firebase אמיתי, ממשיכים לשלב הבא.

12. (אופציונלי) פריסת האפליקציה

עד כה האפליקציה הזו הייתה מקומית לחלוטין, וכל הנתונים נכללים ב-Firebase Emulator Suite. בקטע הזה תלמדו איך להגדיר את פרויקט Firebase כך שהאפליקציה הזו תפעל בסביבת הייצור.

אימות ב-Firebase

במסוף Firebase, עוברים לקטע Authentication ולוחצים על Get started. עוברים לכרטיסייה שיטת כניסה ובוחרים באפשרות אימייל/סיסמה בקטע ספקים מקומיים.

מפעילים את אמצעי הכניסה כתובת אימייל/סיסמה ולוחצים על שמירה.

sign-in-providers.png

Firestore

יצירת מסד נתונים

עוברים לקטע Firestore Database במסוף ולוחצים על Create Database:

  1. כשמוצגת בקשה לגבי כללי האבטחה, בוחרים להתחיל במצב ייצור. בקרוב נעדכן את הכללים האלה.
  2. בוחרים את המיקום של מסד הנתונים שבו רוצים להשתמש באפליקציה. חשוב לזכור שבחירת מיקום של מסד נתונים היא החלטה קבועה, וכדי לשנות אותה תצטרכו ליצור פרויקט חדש. מידע נוסף על בחירת מיקום לפרויקט זמין במסמכי העזרה.

פריסה של כללים

כדי לפרוס את כללי האבטחה שכתבתם מקודם, מריצים את הפקודה הבאה בספרייה של codelab:

$ firebase deploy --only firestore:rules

הפקודה הזו פורסת את התוכן של firestore.rules בפרויקט. כדי לוודא זאת, עוברים לכרטיסייה Rules במסוף.

פריסת מדדים

באפליקציית FriendlyEats יש מיון ומסננים מורכבים, שדורשים מספר מדדים מורכבים בהתאמה אישית. אפשר ליצור אותם באופן ידני במסוף Firebase, אבל קל יותר לכתוב את ההגדרות שלהם בקובץ firestore.indexes.json ולפרוס אותם באמצעות ה-CLI של Firebase.

אם פותחים את הקובץ firestore.indexes.json, רואים שכבר סיפקתם את האינדקסים הנדרשים:

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

כדי לפרוס את המדדים האלה, מריצים את הפקודה הבאה:

$ firebase deploy --only firestore:indexes

חשוב לזכור יצירת האינדקס לא מתבצעת באופן מיידי. אפשר לעקוב אחרי ההתקדמות במסוף Firebase.

הגדרת האפליקציה

בקבצים util/FirestoreInitializer.kt ו-util/AuthInitializer.kt הגדרנו את Firebase SDK להתחבר למהדמנים במצב ניפוי באגים:

    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
    }

אם רוצים לבדוק את האפליקציה עם פרויקט Firebase האמיתי, אפשר:

  1. יוצרים את האפליקציה במצב build לגרסת build ומריצים אותה במכשיר.
  2. מחליפים את BuildConfig.DEBUG באופן זמני ב-false ומפעילים את האפליקציה שוב.

שימו לב: יכול להיות שתצטרכו לצאת מהאפליקציה ולהיכנס שוב כדי להתחבר כראוי לסביבת הייצור.