(ज़रूरी नहीं) Firebase Emulator Suite की मदद से प्रोटोटाइप बनाना और परफ़ॉर्मेंस की जांच करना
इससे पहले कि हम यह बताएं कि आपका ऐप्लिकेशन, रीयलटाइम डेटाबेस से डेटा कैसे पढ़ता और उसमें डेटा कैसे लिखता है, आइए हम आपको टूल के एक ऐसे सेट के बारे में बताते हैं जिसका इस्तेमाल करके, रीयलटाइम डेटाबेस की सुविधाओं के प्रोटोटाइप बनाए जा सकते हैं और उनकी परफ़ॉर्मेंस की जांच की जा सकती है. यह सेट, Firebase Emulator Suite है. अगर अलग-अलग डेटा मॉडल आज़माए जा रहे हैं, सुरक्षा के नियमों को ऑप्टिमाइज़ किया जा रहा है या बैक-एंड के साथ इंटरैक्ट करने का सबसे किफ़ायती तरीका खोजा जा रहा है, तो लाइव सेवाओं को डिप्लॉय किए बिना स्थानीय तौर पर काम करना एक अच्छा विकल्प हो सकता है.
रीयलटाइम डेटाबेस एम्युलेटर, Emulator Suite का हिस्सा है. इसकी मदद से, आपका ऐप्लिकेशन, एम्युलेट किए गए डेटाबेस के कॉन्टेंट और कॉन्फ़िगरेशन के साथ-साथ, एम्युलेट किए गए प्रोजेक्ट के संसाधनों (फ़ंक्शन, अन्य डेटाबेस, और सुरक्षा के नियम) के साथ इंटरैक्ट कर सकता है.emulator_suite_short
रीयलटाइम डेटाबेस एम्युलेटर का इस्तेमाल करने के लिए, आपको ये कुछ चरण पूरे करने होंगे:
- एम्युलेटर से कनेक्ट करने के लिए, अपने ऐप्लिकेशन के टेस्ट कॉन्फ़िगरेशन में कोड की एक लाइन जोड़ना.
- अपने लोकल प्रोजेक्ट डायरेक्ट्री के रूट से,
firebase emulators:startचलाना. - रीयलटाइम डेटाबेस प्लैटफ़ॉर्म के एसडीके का इस्तेमाल करके, अपने ऐप्लिकेशन के प्रोटोटाइप कोड से सामान्य तरीके से कॉल करना या रीयलटाइम डेटाबेस के REST API का इस्तेमाल करना.
रीयलटाइम डेटाबेस और Cloud Functions के इस्तेमाल के बारे में, पूरी जानकारी उपलब्ध है. आपको Emulator Suite के परिचय के बारे में भी पढ़ना चाहिए.
DatabaseReference पाना
डेटाबेस से डेटा पढ़ने या उसमें डेटा लिखने के लिए, आपको DatabaseReference का एक इंस्टेंस चाहिए:
DatabaseReference ref = FirebaseDatabase.instance.ref();
डेटा सेव करने की अनुमति दें
इस दस्तावेज़ में, Firebase डेटा को पढ़ने और उसमें डेटा लिखने की बुनियादी जानकारी दी गई है.
Firebase डेटा को DatabaseReference में लिखा जाता है. साथ ही, रेफ़रंस से जनरेट होने वाले इवेंट के लिए इंतज़ार करके या उन्हें सुनकर, डेटा को वापस पाया जाता है. डेटा की शुरुआती स्थिति के लिए, इवेंट एक बार जनरेट होते हैं. इसके अलावा, डेटा में बदलाव होने पर भी इवेंट जनरेट होते हैं.
डेटा लिखने की बुनियादी कार्रवाइयां
डेटा लिखने की बुनियादी कार्रवाइयों के लिए, set() का इस्तेमाल करके, किसी तय रेफ़रंस में डेटा सेव किया जा सकता है. इससे उस पाथ पर मौजूद मौजूदा डेटा बदल जाता है. किसी रेफ़रंस को इन टाइप में सेट किया जा सकता है: String, boolean, int, double, Map, List.
उदाहरण के लिए, set() का इस्तेमाल करके, किसी उपयोगकर्ता को इस तरह जोड़ा जा सकता है:
DatabaseReference ref = FirebaseDatabase.instance.ref("users/123");
await ref.set({
"name": "John",
"age": 18,
"address": {
"line1": "100 Mountain View"
}
});
set() का इस तरह इस्तेमाल करने पर, तय की गई जगह पर मौजूद डेटा बदल जाता है. इसमें चाइल्ड नोड भी शामिल हैं. हालांकि, पूरे ऑब्जेक्ट को फिर से लिखे बिना भी किसी चाइल्ड को अपडेट किया जा सकता है. अगर उपयोगकर्ताओं को अपनी प्रोफ़ाइल अपडेट करने की अनुमति देनी है, तो उपयोगकर्ता नाम को इस तरह अपडेट किया जा सकता है:
DatabaseReference ref = FirebaseDatabase.instance.ref("users/123");
// Only update the age, leave the name and address!
await ref.update({
"age": 19,
});
update() तरीके में नोड के लिए सब-पाथ स्वीकार किया जाता है. इससे डेटाबेस पर एक साथ कई नोड अपडेट किए जा सकते हैं:
DatabaseReference ref = FirebaseDatabase.instance.ref("users");
await ref.update({
"123/age": 19,
"123/address/line1": "1 Mountain View",
});
डेटा पढ़ने की अनुमति दें
वैल्यू इवेंट सुनकर डेटा पढ़ना
किसी पाथ पर मौजूद डेटा को पढ़ने और उसमें होने वाले बदलावों को सुनने के लिए, DatabaseReference की onValue प्रॉपर्टी का इस्तेमाल करके, DatabaseEvents को सुना जा सकता है.
किसी दिए गए पाथ पर मौजूद डेटा को पढ़ने के लिए, DatabaseEvent का इस्तेमाल किया जा सकता है. यह डेटा, इवेंट के समय मौजूद होता है. जब लिसनर अटैच होता है, तब यह इवेंट एक बार ट्रिगर होता है. इसके अलावा, डेटा में बदलाव होने पर भी यह इवेंट ट्रिगर होता है. इसमें चाइल्ड नोड में होने वाले बदलाव भी शामिल हैं. इस इवेंट में snapshot प्रॉपर्टी होती है. इसमें उस जगह पर मौजूद सारा डेटा शामिल होता है. इसमें चाइल्ड डेटा भी शामिल है. अगर कोई डेटा मौजूद नहीं है, तो स्नैपशॉट की exists प्रॉपर्टी false होगी और उसकी value प्रॉपर्टी, शून्य होगी.
यहां दिए गए उदाहरण में, सोशल ब्लॉगिंग ऐप्लिकेशन, डेटाबेस से किसी पोस्ट की जानकारी वापस पा रहा है:
DatabaseReference starCountRef =
FirebaseDatabase.instance.ref('posts/$postId/starCount');
starCountRef.onValue.listen((DatabaseEvent event) {
final data = event.snapshot.value;
updateStarCount(data);
});
लिसनर को DataSnapshot मिलता है. इसमें value प्रॉपर्टी में, इवेंट के समय डेटाबेस में तय की गई जगह पर मौजूद डेटा होता है.
डेटा को एक बार पढ़ना
get() का इस्तेमाल करके, डेटा को एक बार पढ़ना
एसडीके को डेटाबेस सर्वर के साथ इंटरैक्शन मैनेज करने के लिए डिज़ाइन किया गया है. भले ही, आपका ऐप्लिकेशन ऑनलाइन हो या ऑफ़लाइन.
आम तौर पर, डेटा पढ़ने के लिए, ऊपर बताए गए वैल्यू इवेंट की तकनीकों का इस्तेमाल करना चाहिए. इससे बैकएंड से डेटा में होने वाले अपडेट की सूचना मिलती है. इन तकनीकों से, आपके इस्तेमाल और बिलिंग में कमी आती है. साथ ही, इन्हें ऑप्टिमाइज़ किया जाता है, ताकि आपके उपयोगकर्ताओं को ऑनलाइन और ऑफ़लाइन होने पर बेहतर अनुभव मिल सके.
अगर आपको डेटा की ज़रूरत सिर्फ़ एक बार है, तो डेटाबेस से डेटा का स्नैपशॉट पाने के लिए, get() का इस्तेमाल किया जा सकता है. अगर किसी वजह से get() सर्वर की वैल्यू नहीं दिखा पाता है, तो क्लाइंट, लोकल स्टोरेज कैश की जांच करेगा. अगर वैल्यू अब भी नहीं मिलती है, तो गड़बड़ी का मैसेज दिखेगा.
यहां दिए गए उदाहरण में, डेटाबेस से किसी उपयोगकर्ता का सार्वजनिक तौर पर दिखने वाला उपयोगकर्ता नाम एक बार वापस पाया जा रहा है:
final ref = FirebaseDatabase.instance.ref();
final snapshot = await ref.child('users/$userId').get();
if (snapshot.exists) {
print(snapshot.value);
} else {
print('No data available.');
}
get() का ज़रूरत से ज़्यादा इस्तेमाल करने पर, बैंडविथ का इस्तेमाल बढ़ सकता है और परफ़ॉर्मेंस में कमी आ सकती है. इसे रोकने के लिए, ऊपर दिखाए गए तरीके से रीयलटाइम लिसनर का इस्तेमाल किया जा सकता है.
once() का इस्तेमाल करके, डेटा को एक बार पढ़ना
कुछ मामलों में, ऐसा हो सकता है कि आपको सर्वर पर अपडेट की गई वैल्यू की जांच करने के बजाय, लोकल कैश से वैल्यू तुरंत वापस चाहिए. ऐसे मामलों में, लोकल डिस्क की कैश मेमोरी से डेटा तुरंत पाने के लिए, once() का इस्तेमाल किया जा सकता है.
यह उन डेटा के लिए काम का है जिन्हें सिर्फ़ एक बार लोड करना होता है और जिनके बार-बार बदलने की उम्मीद नहीं होती या जिन्हें सुनने के लिए ऐक्टिव लिसनर की ज़रूरत नहीं होती. उदाहरण के लिए, पिछले उदाहरणों में ब्लॉगिंग ऐप्लिकेशन, उपयोगकर्ता की प्रोफ़ाइल लोड करने के लिए इस तरीके का इस्तेमाल करता है. ऐसा तब होता है, जब उपयोगकर्ता कोई नई पोस्ट लिखता है:
final event = await ref.once(DatabaseEventType.value);
final username = event.snapshot.value?.username ?? 'Anonymous';
डेटा अपडेट करना या मिटाना
तय किए गए फ़ील्ड अपडेट करना
अन्य चाइल्ड नोड को बदले बिना, किसी नोड के तय किए गए चाइल्ड नोड में एक साथ डेटा लिखने के लिए, update() तरीके का इस्तेमाल करें.
update() को कॉल करते समय, कुंजी के लिए पाथ तय करके, निचले लेवल की चाइल्ड वैल्यू अपडेट की जा सकती हैं. अगर बेहतर तरीके से स्केल करने के लिए, डेटा को एक से ज़्यादा जगहों पर सेव किया जाता है, तो डेटा फ़ैन-आउट का इस्तेमाल करके, उस डेटा के सभी इंस्टेंस अपडेट किए जा सकते हैं. उदाहरण के लिए, कोई सोशल ब्लॉगिंग ऐप्लिकेशन, कोई पोस्ट बनाना और उसे एक साथ, हाल की गतिविधि वाले फ़ीड और पोस्ट करने वाले उपयोगकर्ता की गतिविधि वाले फ़ीड में अपडेट करना चाह सकता है. इसके लिए, ब्लॉगिंग ऐप्लिकेशन इस तरह का कोड इस्तेमाल करता है:
void writeNewPost(String uid, String username, String picture, String title,
String body) async {
// A post entry.
final postData = {
'author': username,
'uid': uid,
'body': body,
'title': title,
'starCount': 0,
'authorPic': picture,
};
// Get a key for a new Post.
final newPostKey =
FirebaseDatabase.instance.ref().child('posts').push().key;
// Write the new post's data simultaneously in the posts list and the
// user's post list.
final Map<String, Map> updates = {};
updates['/posts/$newPostKey'] = postData;
updates['/user-posts/$uid/$newPostKey'] = postData;
return FirebaseDatabase.instance.ref().update(updates);
}
इस उदाहरण में, push() का इस्तेमाल करके, /posts/$postid पर सभी उपयोगकर्ताओं के लिए पोस्ट वाले नोड में पोस्ट बनाई जाती है. साथ ही, key का इस्तेमाल करके, कुंजी वापस पाई जाती है. इसके बाद, कुंजी का इस्तेमाल करके, उपयोगकर्ता की पोस्ट में /user-posts/$userid/$postid पर दूसरी एंट्री बनाई जा सकती है.
इन पाथ का इस्तेमाल करके, JSON ट्री में एक से ज़्यादा जगहों पर एक साथ अपडेट किए जा सकते हैं. इसके लिए, update() को सिर्फ़ एक बार कॉल करना होता है. जैसे, इस उदाहरण में नई पोस्ट को दोनों जगहों पर बनाया गया है. इस तरह किए गए एक साथ अपडेट, एटॉमिक होते हैं. इसका मतलब है कि या तो सभी अपडेट सफल होते हैं या सभी अपडेट फ़ेल होते हैं.
पूरा होने पर कॉलबैक जोड़ना
अगर आपको यह जानना है कि आपका डेटा कब सबमिट किया गया है, तो पूरा होने पर कॉलबैक रजिस्टर किए जा सकते हैं. set() और update(), दोनों Futures दिखाते हैं. इनमें सफलता और गड़बड़ी के कॉलबैक अटैच किए जा सकते हैं. जब डेटाबेस में डेटा लिखा जाता है, तब सफलता का कॉलबैक कॉल किया जाता है. वहीं, जब कॉल सफल नहीं होता, तब गड़बड़ी का कॉलबैक कॉल किया जाता है.
FirebaseDatabase.instance
.ref('users/$userId/email')
.set(emailAddress)
.then((_) {
// Data saved successfully!
})
.catchError((error) {
// The write failed...
});
डाटा हटाएं
डेटा मिटाने का सबसे आसान तरीका है कि उस डेटा की जगह के रेफ़रंस पर remove() को कॉल किया जाए.
set() या update() जैसी किसी अन्य कार्रवाई के लिए, वैल्यू के तौर पर शून्य तय करके भी डेटा मिटाया जा सकता है. update() के साथ इस तकनीक का इस्तेमाल करके, एपीआई कॉल में एक से ज़्यादा चाइल्ड नोड मिटाए जा सकते हैं.
डेटा को ट्रांज़ैक्शन के तौर पर सेव करना
जब ऐसे डेटा के साथ काम किया जाता है जिसे एक साथ किए गए बदलावों से खराब किया जा सकता है,
जैसे कि बढ़ते क्रम में काउंटर, तो
ट्रांज़ैक्शन हैंडलर पास करके, runTransaction()का इस्तेमाल किया जा सकता है. ट्रांज़ैक्शन हैंडलर, डेटा की मौजूदा स्थिति को एक आर्ग्युमेंट के तौर पर लेता है और नई स्थिति दिखाता है जिसे आपको लिखना है. अगर आपकी नई वैल्यू के सफलतापूर्वक लिखे जाने से पहले, कोई अन्य क्लाइंट उस जगह पर डेटा लिखता है, तो आपका अपडेट फ़ंक्शन, नई मौजूदा वैल्यू के साथ फिर से कॉल किया जाता है. साथ ही, डेटा लिखने की प्रोसेस फिर से शुरू होती है.
उदाहरण के लिए, सोशल ब्लॉगिंग ऐप्लिकेशन में, उपयोगकर्ताओं को पोस्ट को स्टार करने और अनस्टार करने की अनुमति दी जा सकती है. साथ ही, यह ट्रैक किया जा सकता है कि किसी पोस्ट को कितने स्टार मिले हैं. इसके लिए, यह तरीका अपनाएं:
void toggleStar(String uid) async {
DatabaseReference postRef =
FirebaseDatabase.instance.ref("posts/foo-bar-123");
TransactionResult result = await postRef.runTransaction((Object? post) {
// Ensure a post at the ref exists.
if (post == null) {
return Transaction.abort();
}
Map<String, dynamic> _post = Map<String, dynamic>.from(post as Map);
if (_post["stars"] is Map && _post["stars"][uid] != null) {
_post["starCount"] = (_post["starCount"] ?? 1) - 1;
_post["stars"][uid] = null;
} else {
_post["starCount"] = (_post["starCount"] ?? 0) + 1;
if (!_post.containsKey("stars")) {
_post["stars"] = {};
}
_post["stars"][uid] = true;
}
// Return the new data.
return Transaction.success(_post);
});
}
डिफ़ॉल्ट रूप से, ट्रांज़ैक्शन अपडेट फ़ंक्शन के हर बार चलने पर, इवेंट जनरेट होते हैं. इसलिए, अगर फ़ंक्शन कई बार चलता है, तो आपको बीच की स्थितियां दिख सकती हैं.
इन बीच की स्थितियों को छिपाने के लिए, applyLocally को false पर सेट किया जा सकता है. इसके बाद, इवेंट जनरेट होने से पहले, ट्रांज़ैक्शन के पूरा होने तक इंतज़ार किया जा सकता है:
await ref.runTransaction((Object? post) {
// ...
}, applyLocally: false);
किसी ट्रांज़ैक्शन का नतीजा, TransactionResult होता है. इसमें यह जानकारी शामिल होती है कि ट्रांज़ैक्शन सबमिट किया गया है या नहीं. साथ ही, इसमें नया स्नैपशॉट भी शामिल होता है:
DatabaseReference ref = FirebaseDatabase.instance.ref("posts/123");
TransactionResult result = await ref.runTransaction((Object? post) {
// ...
});
print('Committed? ${result.committed}'); // true / false
print('Snapshot? ${result.snapshot}'); // DataSnapshot
लेन-देन रद्द करना
अगर आपको किसी ट्रांज़ैक्शन को सुरक्षित तरीके से रद्द करना है, तो Transaction.abort() को
कॉल करें ताकि AbortTransactionException जनरेट हो:
TransactionResult result = await ref.runTransaction((Object? user) {
if (user !== null) {
return Transaction.abort();
}
// ...
});
print(result.committed); // false
सर्वर-साइड पर एटॉमिक तरीके से बढ़ोतरी करना
ऊपर दिए गए इस्तेमाल के उदाहरण में, हम डेटाबेस में दो वैल्यू लिख रहे हैं: पोस्ट को स्टार/अनस्टार करने वाले उपयोगकर्ता का आईडी और स्टार की संख्या में की गई बढ़ोतरी. अगर हमें पहले से पता है कि उपयोगकर्ता पोस्ट को स्टार कर रहा है, तो ट्रांज़ैक्शन के बजाय, एटॉमिक तरीके से बढ़ोतरी करने की कार्रवाई का इस्तेमाल किया जा सकता है.
void addStar(uid, key) async {
Map<String, Object?> updates = {};
updates["posts/$key/stars/$uid"] = true;
updates["posts/$key/starCount"] = ServerValue.increment(1);
updates["user-posts/$key/stars/$uid"] = true;
updates["user-posts/$key/starCount"] = ServerValue.increment(1);
return FirebaseDatabase.instance.ref().update(updates);
}
इस कोड में ट्रांज़ैक्शन की कार्रवाई का इस्तेमाल नहीं किया जाता. इसलिए, अगर कोई ऐसा अपडेट होता है जो इस कार्रवाई के साथ काम नहीं करता, तो यह कार्रवाई अपने-आप फिर से नहीं चलती. हालांकि, बढ़ोतरी की कार्रवाई सीधे डेटाबेस सर्वर पर होती है. इसलिए, टकराव की कोई संभावना नहीं होती.
अगर आपको ऐप्लिकेशन के हिसाब से होने वाले टकरावों का पता लगाना है और उन्हें अस्वीकार करना है, तो उस इस्तेमाल के उदाहरण के लिए, सुरक्षा के कस्टम नियम लिखने चाहिए. उदाहरण के लिए, कोई उपयोगकर्ता किसी ऐसी पोस्ट को स्टार कर रहा है जिसे उसने पहले ही स्टार किया है.
ऑफ़लाइन होने पर डेटा के साथ काम करना
अगर किसी क्लाइंट का नेटवर्क कनेक्शन टूट जाता है, तो भी आपका ऐप्लिकेशन सही तरीके से काम करता रहेगा.
Firebase डेटाबेस से कनेक्ट किया गया हर क्लाइंट, ऐक्टिव डेटा का अपना इंटरनल वर्शन बनाए रखता है. जब डेटा लिखा जाता है, तो उसे सबसे पहले इस लोकल वर्शन में लिखा जाता है. इसके बाद, Firebase क्लाइंट, उस डेटा को रिमोट डेटाबेस सर्वर और अन्य क्लाइंट के साथ "बेस्ट-एफ़र्ट" के आधार पर सिंक करता है.
नतीजतन, डेटाबेस में लिखे जाने वाले सभी डेटा के लिए, सर्वर पर डेटा लिखे जाने से पहले ही लोकल इवेंट ट्रिगर होते हैं. इसका मतलब है कि नेटवर्क की लेटेन्सी या कनेक्टिविटी के बावजूद, आपका ऐप्लिकेशन रिस्पॉन्सिव बना रहता है.
कनेक्टिविटी फिर से बहाल होने के बाद, आपका ऐप्लिकेशन, इवेंट का सही सेट पाता है. इससे क्लाइंट, सर्वर की मौजूदा स्थिति के साथ सिंक हो जाता है. इसके लिए, आपको कोई कस्टम कोड लिखने की ज़रूरत नहीं होती.
ऑफ़लाइन होने पर ऐप्लिकेशन के काम करने के तरीके के बारे में ज़्यादा जानने के लिए, ऑनलाइन और ऑफ़लाइन सुविधाओं के बारे में ज़्यादा जानें लेख पढ़ें.