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

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

लक्ष्य

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

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

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

कोडलैब का यह मॉड्यूल शुरू करने से पहले, पक्का कर लें कि:

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

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

  1. अपने Google खाते से Firebase कंसोल में साइन इन करें.
  2. Firebase कंसोल में, प्रोजेक्ट जोड़ें पर क्लिक करें.
  3. जैसा कि नीचे स्क्रीन कैप्चर में दिखाया गया है, अपने Firebase प्रोजेक्ट का नाम डालें (उदाहरण के लिए, "फ़्रेंडली ईट्स") और जारी रखें पर क्लिक करें.

9d2f625aebbb6af.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 खोलें. फ़ाइल > नया > प्रोजेक्ट इंपोर्ट करें और friendeats-android फ़ोल्डर चुनें.

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

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

Firebase सीएलआई इंस्टॉल करें

सबसे पहले आपको Firebase सीएलआई इंस्टॉल करना होगा. अगर 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 और AdaptiveEats के 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 ऐप्लिकेशन को एम्युलेटर से कनेक्ट करना होगा.

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

util/FirestoreInitializer.kt और util/AuthInitializer.kt फ़ाइलों को Android Studio में खोलें. ऐप्लिकेशन के शुरू होने पर, इन फ़ाइलों में 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 ऑथेंटिकेशन एम्युलेटर से कनेक्ट हो रही है. इसलिए, कोई असली क्रेडेंशियल ट्रांसमिट नहीं हो रहा है.

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

Firebase पुष्टि करने वाला एम्युलेटर

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

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 पर जाकर एम्युलेटर का यूज़र इंटरफ़ेस (यूआई) खोलें. इसके बाद फ़ायरस्टोर टैब पर क्लिक करें और आपको अभी-अभी जोड़ा गया डेटा दिखेगा:

Firebase पुष्टि करने वाला एम्युलेटर

यह डेटा आपकी मशीन के लिए 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 {
    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;
    }
  }
}

आइए, इन नियमों को बदलकर अनचाहे डेटा के ऐक्सेस या बदलावों से बचें. साथ ही, 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 के बारे में जानना जारी रखना है, तो शुरुआत करने के लिए यहां कुछ जगहें दी गई हैं:

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

ज़रूरी नहीं: प्रोडक्शन में डिप्लॉय करें

अब तक इस ऐप्लिकेशन ने सिर्फ़ Firebase Emulator Suite का इस्तेमाल किया है. अगर आपको इस ऐप्लिकेशन को किसी असली Firebase प्रोजेक्ट में डिप्लॉय करने का तरीका जानना है, तो अगले चरण पर जाएं.

12. (ज़रूरी नहीं) अपना ऐप्लिकेशन डिप्लॉय करना

अब तक यह ऐप्लिकेशन पूरी तरह से लोकल है, सारा डेटा Firebase Emulator Suite में मौजूद होता है. इस सेक्शन में आपको Firebase प्रोजेक्ट को कॉन्फ़िगर करने का तरीका पता चलेगा, ताकि यह ऐप्लिकेशन प्रोडक्शन में काम कर सके.

Firebase से पुष्टि करना

Firebase कंसोल में, पुष्टि करें सेक्शन पर जाएं और शुरू करें पर क्लिक करें. साइन इन करने का तरीका टैब पर जाएं और मूल सेवा देने वाली कंपनियों से ईमेल/पासवर्ड विकल्प चुनें.

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

साइन-इन-providers.png

Firestore

डेटाबेस बनाएं

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

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

नियम डिप्लॉय करें

आपने पहले जो सुरक्षा नियम लिखे हैं उन्हें डिप्लॉय करने के लिए, कोडलैब डायरेक्ट्री में यहां दिया गया कमांड चलाएं:

$ firebase deploy --only firestore:rules

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

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

AdaptiveEats ऐप्लिकेशन का इस्तेमाल, डेटा को क्रम से लगाने और फ़िल्टर करने में मुश्किल होता है. इसके लिए, कई कस्टम कंपाउंड इंडेक्स की ज़रूरत होती है. इन्हें Firebase कंसोल में खुद बनाया जा सकता है. हालांकि, firestore.indexes.json फ़ाइल में इनकी परिभाषाएं लिखना और 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. ऐप्लिकेशन को रिलीज़ मोड में बनाएं और उसे डिवाइस पर चलाएं.
  2. BuildConfig.DEBUG को कुछ समय के लिए false से बदलें और ऐप्लिकेशन को फिर से चलाएं.

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