(Optional) Firebase Emulator Suite की मदद से प्रोटोटाइप बनाना और परफ़ॉर्मेंस की जांच करना
इससे पहले कि हम यह बताएं कि आपका ऐप्लिकेशन, रीयलटाइम डेटाबेस से डेटा कैसे पढ़ता और उसमें डेटा कैसे सेव करता है, आइए हम आपको टूल के एक सेट के बारे में बताते हैं. इसका इस्तेमाल, रीयलटाइम डेटाबेस की सुविधाओं का प्रोटोटाइप बनाने और उनकी परफ़ॉर्मेंस की जांच करने के लिए किया जा सकता है. यह सेट, Firebase Emulator Suite है. अगर अलग-अलग डेटा मॉडल आज़माए जा रहे हैं, सुरक्षा के नियमों को ऑप्टिमाइज़ किया जा रहा है या बैक-एंड के साथ इंटरैक्ट करने का सबसे किफ़ायती तरीका खोजा जा रहा है, तो लाइव सेवाओं को डिप्लॉय किए बिना स्थानीय तौर पर काम करना एक अच्छा विकल्प हो सकता है.
रीयलटाइम डेटाबेस एम्युलेटर, Emulator Suite का हिस्सा है. इसकी मदद से, आपका ऐप्लिकेशन, एम्युलेट किए गए डेटाबेस के कॉन्टेंट और कॉन्फ़िगरेशन के साथ-साथ, एम्युलेट किए गए प्रोजेक्ट के संसाधनों (फ़ंक्शन, अन्य डेटाबेस, और सुरक्षा के नियम) के साथ इंटरैक्ट कर सकता है.emulator_suite_short
रीयलटाइम डेटाबेस एम्युलेटर का इस्तेमाल करने के लिए, आपको ये कुछ चरण पूरे करने होंगे:
- एम्युलेटर से कनेक्ट करने के लिए, अपने ऐप्लिकेशन के टेस्ट कॉन्फ़िगरेशन में कोड की एक लाइन जोड़ना.
- अपने स्थानीय प्रोजेक्ट डायरेक्ट्री के रूट से,
firebase emulators:startचलाना. - रीयलटाइम डेटाबेस प्लैटफ़ॉर्म के एसडीके का इस्तेमाल करके, अपने ऐप्लिकेशन के प्रोटोटाइप कोड से सामान्य तरीके से कॉल करना या रीयलटाइम डेटाबेस के REST एपीआई का इस्तेमाल करना.
रीयलटाइम डेटाबेस और 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 क्लाइंट, उस डेटा को रिमोट डेटाबेस सर्वर और अन्य क्लाइंट के साथ "बेस्ट-एफ़र्ट" के आधार पर सिंक करता है.
नतीजतन, डेटाबेस में सेव करने की सभी कार्रवाइयों से, सर्वर पर डेटा सेव होने से पहले ही लोकल इवेंट ट्रिगर होते हैं. इसका मतलब है कि नेटवर्क की लेटेन्सी या कनेक्टिविटी से कोई फ़र्क़ नहीं पड़ता, आपका ऐप्लिकेशन हमेशा काम करता रहता है.
कनेक्टिविटी फिर से चालू होने के बाद, आपके ऐप्लिकेशन को इवेंट का सही सेट मिलता है. इससे क्लाइंट, मौजूदा सर्वर की स्थिति के साथ सिंक हो जाता है. इसके लिए, आपको कोई कस्टम कोड लिखने की ज़रूरत नहीं होती.
ऑफ़लाइन होने पर काम करने की सुविधा के बारे में ज़्यादा जानने के लिए, ऑनलाइन और ऑफ़लाइन होने पर काम करने की सुविधाओं के बारे में ज़्यादा जानें लेख पढ़ें.