1. खास जानकारी
लक्ष्य
इस कोडलैब में, Cloud Firestore की मदद से Android पर रेस्टोरेंट के सुझाव देने वाला ऐप्लिकेशन बनाया जाएगा. आपको इनके बारे में जानकारी मिलेगी:
- Android ऐप्लिकेशन से Firestore में डेटा पढ़ना और लिखना
- Firestore के डेटा में हुए बदलावों को रीयलटाइम में सुनना
- Firestore के डेटा को सुरक्षित रखने के लिए, Firebase Authentication और सुरक्षा के नियमों का इस्तेमाल करना
- Firestore की मुश्किल क्वेरी लिखना
ज़रूरी शर्तें
इस कोडलैब को शुरू करने से पहले, पक्का करें कि आपके पास ये चीज़ें हों:
- Android Studio Flamingo या इसके बाद का वर्शन
- एपीआई 19 या इसके बाद के वर्शन वाला Android एम्युलेटर
- Node.js का 16 या इसके बाद का वर्शन
- Java का वर्शन 17 या इसके बाद का वर्शन
2. Firebase प्रोजेक्ट बनाना
- अपने Google खाते का इस्तेमाल करके, Firebase कंसोल में साइन इन करें.
- नया प्रोजेक्ट बनाने के लिए, बटन पर क्लिक करें. इसके बाद, प्रोजेक्ट का नाम डालें. उदाहरण के लिए,
FriendlyEats
.
- जारी रखें पर क्लिक करें.
- अगर आपसे कहा जाए, तो Firebase की शर्तें पढ़ें और स्वीकार करें. इसके बाद, जारी रखें पर क्लिक करें.
- (ज़रूरी नहीं) Firebase कंसोल में एआई की मदद पाने की सुविधा चालू करें. इसे "Firebase में Gemini" कहा जाता है.
- इस कोडलैब के लिए, आपको Google Analytics की ज़रूरत नहीं है. इसलिए, Google Analytics के विकल्प को टॉगल करके बंद करें.
- प्रोजेक्ट बनाएं पर क्लिक करें. इसके बाद, प्रोजेक्ट के प्रोविज़न होने का इंतज़ार करें. इसके बाद, जारी रखें पर क्लिक करें.
3. सैंपल प्रोजेक्ट सेट अप करना
कोड डाउनलोड करना
इस कोडलैब के लिए, सैंपल कोड को क्लोन करने के लिए यहां दिया गया निर्देश चलाएं. इससे आपकी मशीन पर friendlyeats-android
नाम का एक फ़ोल्डर बन जाएगा:
$ git clone https://github.com/firebase/friendlyeats-android
अगर आपकी मशीन पर git नहीं है, तो कोड को सीधे GitHub से भी डाउनलोड किया जा सकता है.
Firebase कॉन्फ़िगरेशन जोड़ना
- Firebase कंसोल में, बाईं ओर मौजूद नेविगेशन बार में जाकर प्रोजेक्ट की खास जानकारी को चुनें. प्लैटफ़ॉर्म चुनने के लिए, Android बटन पर क्लिक करें. जब पैकेज का नाम डालने के लिए कहा जाए, तब
com.google.firebase.example.fireeats
का इस्तेमाल करें
- ऐप्लिकेशन रजिस्टर करें पर क्लिक करें. इसके बाद,
google-services.json
फ़ाइल डाउनलोड करने के लिए दिए गए निर्देशों का पालन करें. साथ ही, इसे अभी डाउनलोड किए गए कोड केapp/
फ़ोल्डर में ले जाएं. इसके बाद, आगे बढ़ें पर क्लिक करें.
प्रोजेक्ट इंपोर्ट करना
Android Studio खोलें. फ़ाइल > नया > प्रोजेक्ट इंपोर्ट करें पर क्लिक करें. इसके बाद, friendlyeats-android फ़ोल्डर चुनें.
4. Firebase Emulator Suite को सेट अप करना
इस कोडलैब में, 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 Emulator शुरू करने के लिए, 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 टूल को आपकी मशीन पर चल रहे लोकल एम्युलेटर से कनेक्ट करने का लॉजिक होता है.
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
के इस इंस्टेंस को ऐक्सेस करेंगे. इससे हमें यह पक्का करने में मदद मिलेगी कि debug
मोड में चलाने पर, हम हमेशा Firestore एम्युलेटर से कनेक्ट हों.
ऐप्लिकेशन चलाएं
अगर आपने google-services.json
फ़ाइल को सही तरीके से जोड़ा है, तो अब प्रोजेक्ट कंपाइल हो जाना चाहिए. Android Studio में, बनाएं > प्रोजेक्ट फिर से बनाएं पर क्लिक करें. साथ ही, यह पक्का करें कि कोई गड़बड़ी बाकी न हो.
Android Studio में, अपने Android एम्युलेटर पर ऐप्लिकेशन चलाएं. सबसे पहले, आपको "साइन इन करें" स्क्रीन दिखेगी. ऐप्लिकेशन में साइन इन करने के लिए, किसी भी ईमेल और पासवर्ड का इस्तेमाल किया जा सकता है. साइन इन करने की यह प्रोसेस, Firebase Authentication Emulator से कनेक्ट हो रही है. इसलिए, कोई भी असली क्रेडेंशियल ट्रांसमिट नहीं किया जा रहा है.
अब अपने वेब ब्राउज़र में http://localhost:4000 पर जाकर, Emulators का यूज़र इंटरफ़ेस (यूआई) खोलें. इसके बाद, Authentication टैब पर क्लिक करें. आपको अभी बनाया गया खाता दिखेगा:
साइन इन करने की प्रोसेस पूरी होने के बाद, आपको ऐप्लिकेशन की होम स्क्रीन दिखेगी:
हम जल्द ही होम स्क्रीन पर कुछ डेटा जोड़ेंगे.
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()
तरीके से, किसी कलेक्शन में अपने-आप जनरेट हुए आईडी वाला दस्तावेज़ जोड़ा जाता है. इसलिए, हमें हर रेस्टोरेंट के लिए यूनीक आईडी तय करने की ज़रूरत नहीं पड़ी.
अब ऐप्लिकेशन को फिर से चलाएं और ओवरफ़्लो मेन्यू (सबसे ऊपर दाएं कोने में) में मौजूद "Add Random Items" बटन पर क्लिक करें, ताकि आपने अभी जो कोड लिखा है उसे लागू किया जा सके:
अब अपने वेब ब्राउज़र में http://localhost:4000 पर जाकर, Emulators का यूज़र इंटरफ़ेस (यूआई) खोलें. इसके बाद, Firestore टैब पर क्लिक करें. यहां आपको वह डेटा दिखेगा जिसे आपने अभी-अभी जोड़ा है:
यह डेटा, आपके कंप्यूटर पर पूरी तरह से सेव होता है. असल में, आपके प्रोजेक्ट में अब तक 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 से डेटा पढ़ने के लिए पूरी तरह से कॉन्फ़िगर कर दिया गया है. ऐप्लिकेशन को फिर से चलाएं. आपको पिछले चरण में जोड़े गए रेस्टोरेंट दिखने चाहिए:
अब अपने ब्राउज़र में Emulator UI पर वापस जाएं और किसी रेस्टोरेंट के नाम में बदलाव करें. आपको ऐप्लिकेशन में यह बदलाव तुरंत दिख जाएगा!
8. डेटा को क्रम से लगाना और फ़िल्टर करना
फ़िलहाल, ऐप्लिकेशन में सभी रेस्टोरेंट में से सबसे ज़्यादा रेटिंग वाले रेस्टोरेंट दिखाए जाते हैं. हालांकि, रेस्टोरेंट के ऐप्लिकेशन में उपयोगकर्ता, डेटा को क्रम से लगाना और फ़िल्टर करना चाहेगा. उदाहरण के लिए, ऐप्लिकेशन को "फ़िलाडेल्फ़िया में सबसे अच्छे सीफ़ूड रेस्टोरेंट" या "सबसे सस्ता पिज़्ज़ा" दिखाने में सक्षम होना चाहिए.
ऐप्लिकेशन में सबसे ऊपर मौजूद सफ़ेद बार पर क्लिक करने से, फ़िल्टर का डायलॉग बॉक्स खुलता है. इस सेक्शन में, हम इस डायलॉग को काम करने के लिए Firestore क्वेरी का इस्तेमाल करेंगे:
आइए, 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
ऑब्जेक्ट बनाया है.
ऐप्लिकेशन को फिर से चलाएं और सबसे लोकप्रिय और कम कीमत वाले रेस्टोरेंट दिखाने के लिए, यह फ़िल्टर चुनें:
अब आपको रेस्टोरेंट की फ़िल्टर की गई सूची दिखेगी. इसमें सिर्फ़ कम कीमत वाले विकल्प होंगे:
अगर आपने यहाँ तक काम कर लिया है, तो अब आपने Firestore पर, रेस्टोरेंट के सुझाव दिखाने वाला एक ऐप्लिकेशन बना लिया है! अब रेस्टोरेंट को रीयल टाइम में क्रम से लगाया और फ़िल्टर किया जा सकता है. अगले कुछ सेक्शन में, हम रेस्टोरेंट के लिए समीक्षाएं जोड़ेंगे और ऐप्लिकेशन में सुरक्षा से जुड़े नियम जोड़ेंगे.
9. सबकलेक्शन में डेटा व्यवस्थित करना
इस सेक्शन में, हम ऐप्लिकेशन में रेटिंग जोड़ने का तरीका बताएंगे, ताकि लोग अपने पसंदीदा (या नापसंद) रेस्टोरेंट की समीक्षा कर सकें.
कलेक्शन और सब-कलेक्शन
अब तक हमने रेस्टोरेंट का सारा डेटा, "restaurants" नाम के टॉप-लेवल कलेक्शन में सेव किया है. जब कोई उपयोगकर्ता किसी रेस्टोरेंट को रेटिंग देता है, तो हमें रेस्टोरेंट में एक नया 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()
फ़ंक्शन में, टास्क में लिसनर जोड़े जाते हैं, ताकि लेन-देन के नतीजे का जवाब दिया जा सके.
अब ऐप्लिकेशन को फिर से चलाएं और किसी रेस्टोरेंट पर क्लिक करें. इससे रेस्टोरेंट की जानकारी वाली स्क्रीन खुल जाएगी. समीक्षा जोड़ने के लिए, + बटन पर क्लिक करें. स्टार की संख्या चुनकर और कुछ टेक्स्ट डालकर समीक्षा जोड़ें.
सबमिट करें पर क्लिक करने से, लेन-देन शुरू हो जाएगा. लेन-देन पूरा होने पर, आपको अपनी समीक्षा यहां दिखेगी. साथ ही, रेस्टोरेंट की समीक्षाओं की संख्या में अपडेट दिखेगा:
बधाई हो! अब आपके पास 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 Emulator Suite का इस्तेमाल किया गया है. अगर आपको इस ऐप्लिकेशन को किसी असली Firebase प्रोजेक्ट पर डिप्लॉय करने का तरीका जानना है, तो अगले चरण पर जाएं.
12. (ज़रूरी नहीं) अपना ऐप्लिकेशन डिप्लॉय करना
अब तक यह ऐप्लिकेशन पूरी तरह से लोकल था. इसका सारा डेटा, Firebase Emulator Suite में सेव है. इस सेक्शन में, आपको अपने Firebase प्रोजेक्ट को कॉन्फ़िगर करने का तरीका बताया जाएगा, ताकि यह ऐप्लिकेशन प्रोडक्शन में काम कर सके.
Firebase से पुष्टि करना
Firebase कंसोल में, Authentication सेक्शन पर जाएं और Get started पर क्लिक करें. साइन-इन करने का तरीका टैब पर जाएं. इसके बाद, नेटिव प्रोवाइडर से ईमेल/पासवर्ड विकल्प चुनें.
ईमेल/पासवर्ड से साइन-इन करने का तरीका चालू करें और सेव करें पर क्लिक करें.
Firestore
डेटाबेस बनाना
कंसोल के Firestore डेटाबेस सेक्शन पर जाएं और डेटाबेस बनाएं पर क्लिक करें:
- जब आपसे सुरक्षा नियमों के बारे में पूछा जाए, तब प्रोडक्शन मोड में शुरू करने का विकल्प चुनें. हम उन नियमों को जल्द ही अपडेट कर देंगे.
- अपने ऐप्लिकेशन के लिए, डेटाबेस की वह जगह चुनें जिसका आपको इस्तेमाल करना है. ध्यान दें कि डेटाबेस की जगह चुनना एक स्थायी फ़ैसला होता है. इसे बदलने के लिए, आपको एक नया प्रोजेक्ट बनाना होगा. प्रोजेक्ट की जगह चुनने के बारे में ज़्यादा जानकारी के लिए, दस्तावेज़ देखें.
नियम लागू करना
पहले लिखी गई सुरक्षा से जुड़े नियमों को डिप्लॉय करने के लिए, कोड लैब डायरेक्ट्री में यह कमांड चलाएं:
$ firebase deploy --only firestore:rules
इससे firestore.rules
का कॉन्टेंट आपके प्रोजेक्ट में डिप्लॉय हो जाएगा. इसकी पुष्टि करने के लिए, कंसोल में नियम टैब पर जाएं.
इंडेक्स डिप्लॉय करना
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 प्रोजेक्ट के साथ टेस्ट करना है, तो इनमें से कोई एक तरीका अपनाएं:
- ऐप्लिकेशन को रिलीज़ मोड में बनाएं और उसे किसी डिवाइस पर चलाएं.
- कुछ समय के लिए
BuildConfig.DEBUG
की जगहfalse
का इस्तेमाल करें और ऐप्लिकेशन को फिर से चलाएं.
ध्यान दें कि प्रोडक्शन से ठीक से कनेक्ट करने के लिए, आपको ऐप्लिकेशन से साइन आउट करके फिर से साइन इन करना पड़ सकता है.