Cloud Firestore Android कोडलैब (कोड बनाना सीखना)

1. खास जानकारी

लक्ष्य

इस कोडलैब में, आपको Android पर रेस्टोरेंट के सुझाव देने वाला ऐप्लिकेशन बनाना होगा. यह ऐप्लिकेशन, Cloud Firestore का इस्तेमाल करेगा. आपको इनके बारे में जानकारी मिलेगी:

  • Android ऐप्लिकेशन से Firestore में डेटा पढ़ना और उसमें डेटा डालना
  • Firestore डेटा में होने वाले बदलावों को रीयल टाइम में सुनना
  • Firestore डेटा को सुरक्षित रखने के लिए, Firebase Authentication और सुरक्षा से जुड़े नियमों का इस्तेमाल करना
  • Firestore की जटिल क्वेरी लिखना

ज़रूरी शर्तें

इस कोडलैब को शुरू करने से पहले, पक्का करें कि आपके पास ये चीज़ें हों:

  • Android Studio Flamingo या इसके बाद का वर्शन
  • एपीआई 19 या उसके बाद के वर्शन वाला Android एमुलेटर
  • Node.js का 16 या इसके बाद का वर्शन
  • Java का 17 या इसके बाद का वर्शन

2. Firebase प्रोजेक्ट बनाना

  1. अपने Google खाते से Firebase कंसोल में साइन इन करें.
  2. Firebase कंसोल में, प्रोजेक्ट जोड़ें पर क्लिक करें.
  3. नीचे दिए गए स्क्रीनशॉट में दिखाए गए तरीके से, अपने Firebase प्रोजेक्ट के लिए कोई नाम डालें. उदाहरण के लिए, "Friendly Eats". इसके बाद, जारी रखें पर क्लिक करें.

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/ फ़ोल्डर में ले जाएं. इसके बाद, आगे बढ़ें पर क्लिक करें.

प्रोजेक्ट इंपोर्ट करना

Android Studio खोलें. फ़ाइल > नया > प्रोजेक्ट इंपोर्ट करें पर क्लिक करें. इसके बाद, friendlyeats-android फ़ोल्डर चुनें.

4. Firebase एमुलेटर सेट अप करना

इस कोडलैब में, आपको Cloud Firestore और Firebase की अन्य सेवाओं को स्थानीय तौर पर एमुलेट करने के लिए, Firebase Emulator Suite का इस्तेमाल करना होगा. इससे, ऐप्लिकेशन बनाने के लिए, स्थानीय डेवलपमेंट का ऐसा सुरक्षित, तेज़, और बिना किसी शुल्क वाला प्लैटफ़ॉर्म मिलता है.

Firebase CLI इंस्टॉल करना

सबसे पहले, आपको Firebase CLI इंस्टॉल करना होगा. macOS या Linux का इस्तेमाल करने वाले लोग, cURL का यह कमांड चला सकते हैं:

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

अगर Windows का इस्तेमाल किया जा रहा है, तो स्टैंडअलोन बाइनरी पाने या npm के ज़रिए इंस्टॉल करने के लिए, इंस्टॉल करने के निर्देश पढ़ें.

सीएलआई इंस्टॉल करने के बाद, firebase --version को चलाने पर, 9.0.0 या उसके बाद के वर्शन की जानकारी मिलनी चाहिए:

$ firebase --version
9.0.0

प्रवेश करें

सीएलआई को अपने Google खाते से कनेक्ट करने के लिए, firebase login चलाएं. लॉगिन की प्रोसेस पूरी करने के लिए, एक नई ब्राउज़र विंडो खुलेगी. पक्का करें कि आपने वही खाता चुना है जिसका इस्तेमाल करके, आपने पहले अपना Firebase प्रोजेक्ट बनाया था.

अपने स्थानीय प्रोजेक्ट को Firebase प्रोजेक्ट से कनेक्ट करने के लिए, friendlyeats-android फ़ोल्डर में जाकर firebase use --add चलाएं. पहले से बनाए गए प्रोजेक्ट को चुनने के लिए, निर्देशों का पालन करें. अगर आपसे कोई दूसरा नाम चुनने के लिए कहा जाए, तो default डालें.

5. ऐप्लिकेशन चलाना

अब Firebase Emulator Suite और FriendlyEats Android ऐप्लिकेशन को पहली बार चलाने का समय आ गया है.

एम्युलेटर चलाना

Firebase एम्युलेटर शुरू करने के लिए, अपने टर्मिनल में friendlyeats-android डायरेक्ट्री में जाकर firebase emulators:start चलाएं. आपको इस तरह के लॉग दिखेंगे:

$ 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 ऐप्लिकेशन को एम्युलेटर से कनेक्ट करना होगा.

ऐप्लिकेशन को एम्युलेटर से कनेक्ट करना

Android Studio में फ़ाइलें util/FirestoreInitializer.kt और util/AuthInitializer.kt खोलें. इन फ़ाइलों में, ऐप्लिकेशन के शुरू होने पर, Firebase SDK टूल को आपकी मशीन पर चल रहे लोकल एमुलेटर से कनेक्ट करने का लॉजिक होता है.

FirestoreInitializer क्लास के create() तरीके पर, इस कोड की जांच करें:

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

हम BuildConfig का इस्तेमाल करके यह पक्का करते हैं कि हम सिर्फ़ तब एम्युलेटर से कनेक्ट करें, जब हमारा ऐप्लिकेशन debug मोड में चल रहा हो. जब हम ऐप्लिकेशन को release मोड में कंपाइल करेंगे, तो यह शर्त गलत होगी.

हम देख सकते हैं कि यह Firebase SDK टूल को स्थानीय Firestore एमुलेटर से कनेक्ट करने के लिए, useEmulator(host, port) तरीके का इस्तेमाल कर रहा है. हम पूरे ऐप्लिकेशन में, FirebaseFirestore के इस इंस्टेंस को ऐक्सेस करने के लिए FirebaseUtil.getFirestore() का इस्तेमाल करेंगे. इससे हमें यह पक्का करने में मदद मिलेगी कि debug मोड में चलने पर, हम हमेशा Firestore एम्युलेटर से कनेक्ट रहेंगे.

ऐप्लिकेशन चलाना

अगर आपने google-services.json फ़ाइल को सही तरीके से जोड़ा है, तो प्रोजेक्ट अब कंपाइल हो जाना चाहिए. Android Studio में, बिल्ड करें > प्रोजेक्ट फिर से बनाएं पर क्लिक करें. साथ ही, पक्का करें कि कोई गड़बड़ी न हो.

Android Studio में, अपने Android एमुलेटर पर ऐप्लिकेशन चालू करें. सबसे पहले, आपको "साइन इन करें" स्क्रीन दिखेगी. ऐप्लिकेशन में साइन इन करने के लिए, किसी भी ईमेल पते और पासवर्ड का इस्तेमाल किया जा सकता है. साइन इन करने की यह प्रोसेस, Firebase Authentication एमुलेटर से कनेक्ट हो रही है. इसलिए, कोई भी असली क्रेडेंशियल ट्रांसफ़र नहीं किया जा रहा है.

अब अपने वेब ब्राउज़र में http://localhost:4000 पर जाकर, एमुलेटर का यूज़र इंटरफ़ेस खोलें. इसके बाद, पुष्टि टैब पर क्लिक करें. आपको वह खाता दिखेगा जो आपने अभी बनाया है:

Firebase Auth Emulator

साइन इन करने की प्रोसेस पूरी करने के बाद, आपको ऐप्लिकेशन की होम स्क्रीन दिखेगी:

de06424023ffb4b9.png

जल्द ही, हम होम स्क्रीन पर जानकारी दिखाने के लिए कुछ डेटा जोड़ेंगे.

6. Firestore में डेटा सेव करना

इस सेक्शन में, हम Firestore में कुछ डेटा डालेंगे, ताकि हम फ़िलहाल खाली होम स्क्रीन को पॉप्युलेट कर सकें.

हमारे ऐप्लिकेशन में मुख्य मॉडल ऑब्जेक्ट, रेस्टोरेंट है (model/Restaurant.kt देखें). Firestore डेटा को दस्तावेज़ों, कलेक्शन, और सब-कलेक्शन में बांटा जाता है. हम हर रेस्टोरेंट को "restaurants" नाम के टॉप-लेवल कलेक्शन में दस्तावेज़ के तौर पर सेव करेंगे. Firestore डेटा मॉडल के बारे में ज़्यादा जानने के लिए, दस्तावेज़ में दस्तावेज़ों और कलेक्शन के बारे में पढ़ें.

उदाहरण के लिए, हम ऐप्लिकेशन में एक सुविधा जोड़ेंगे. इससे, ओवरफ़्लो मेन्यू में "रैंडम आइटम जोड़ें" बटन पर क्लिक करने पर, रैंडम तौर पर 10 रेस्टोरेंट दिखेंगे. फ़ाइल 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

यह डेटा आपकी मशीन पर 100% स्थानीय तौर पर सेव होता है. असल में, आपके असली प्रोजेक्ट में अब तक 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

चलिए, MainFragment.kt के onFilter() तरीके में बदलाव करते हैं. यह तरीका, 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
    }

ऊपर दिए गए स्निपेट में, दिए गए फ़िल्टर से मैच करने के लिए, where और orderBy क्लॉज़ अटैच करके, हम Query ऑब्जेक्ट बनाते हैं.

कम कीमत वाले सबसे लोकप्रिय रेस्टोरेंट दिखाने के लिए, ऐप्लिकेशन को फिर से चालू करें और यह फ़िल्टर चुनें:

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

सबमिट करें पर क्लिक करने से लेन-देन शुरू हो जाएगा. लेन-देन पूरा होने के बाद, आपको अपनी समीक्षा नीचे दिखेगी. साथ ही, रेस्टोरेंट की समीक्षाओं की संख्या में बदलाव दिखेगा:

f9e670f40bd615b0.png

बधाई हो! अब आपके पास Cloud Firestore पर बनाया गया, रेस्टोरेंट की समीक्षा करने वाला सोशल, लोकल, और मोबाइल ऐप्लिकेशन है. मुझे पता है कि ये इन दिनों काफ़ी लोकप्रिय हैं.

10. अपना डेटा सुरक्षित करना

फ़िलहाल, हमने इस ऐप्लिकेशन की सुरक्षा पर विचार नहीं किया है. हमें कैसे पता चलेगा कि उपयोगकर्ता सिर्फ़ अपने डेटा को पढ़ और लिख सकते हैं? Firestore डेटाबेस को सुरक्षा नियम नाम की कॉन्फ़िगरेशन फ़ाइल से सुरक्षित किया जाता है.

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 कंसोल में, पुष्टि सेक्शन पर जाएं और शुरू करें पर क्लिक करें. साइन इन करने का तरीका टैब पर जाएं और नेटिव प्रोवाइडर से ईमेल/पासवर्ड विकल्प चुनें.

साइन इन करने के लिए, ईमेल/पासवर्ड का तरीका चालू करें और सेव करें पर क्लिक करें.

sign-in-providers.png

Firestore

डेटाबेस बनाना

कंसोल के Firestore डेटाबेस सेक्शन पर जाएं और डेटाबेस बनाएं पर क्लिक करें:

  1. सुरक्षा नियमों के बारे में पूछे जाने पर, प्रोडक्शन मोड में शुरू करने का विकल्प चुनें. हम जल्द ही उन नियमों को अपडेट कर देंगे.
  2. डेटाबेस की वह जगह चुनें जिसका इस्तेमाल आपको अपने ऐप्लिकेशन के लिए करना है. ध्यान दें कि डेटाबेस की जगह चुनने का फ़ैसला हमेशा के लिए होता है. इसे बदलने के लिए, आपको एक नया प्रोजेक्ट बनाना होगा. प्रोजेक्ट की जगह चुनने के बारे में ज़्यादा जानने के लिए, दस्तावेज़ देखें.

नियम डिप्लॉय करना

पहले से लिखे गए सुरक्षा नियमों को डिप्लॉय करने के लिए, codelab डायरेक्ट्री में यह कमांड चलाएं:

$ firebase deploy --only firestore:rules

इससे, firestore.rules का कॉन्टेंट आपके प्रोजेक्ट में डिप्लॉय हो जाएगा. इसकी पुष्टि करने के लिए, Console में नियम टैब पर जाएं.

इंडेक्स डिप्लॉय करना

FriendlyEats ऐप्लिकेशन में, डेटा को क्रम से लगाने और फ़िल्टर करने की सुविधा जटिल है. इसके लिए, कई कस्टम कंपाउंड इंडेक्स की ज़रूरत होती है. इन्हें Firebase कंसोल में मैन्युअल तरीके से बनाया जा सकता है. हालांकि, firestore.indexes.json फ़ाइल में उनकी परिभाषाएं लिखना और Firebase CLI का इस्तेमाल करके उन्हें डिप्लॉय करना आसान है.

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. ऐप्लिकेशन को रिलीज़ मोड में बनाएं और उसे किसी डिवाइस पर चलाएं.
  2. कुछ समय के लिए BuildConfig.DEBUG को false से बदलें और ऐप्लिकेशन को फिर से चलाएं.

ध्यान दें कि प्रोडक्शन से सही तरीके से कनेक्ट करने के लिए, आपको ऐप्लिकेशन से साइन आउट करके फिर से साइन इन करना पड़ सकता है.