डेटा पढ़ें और लिखें

(ज़रूरी नहीं) Firebase Emulator Suite की मदद से प्रोटोटाइप बनाना और टेस्ट करना

इससे पहले कि हम यह बताएं कि आपका ऐप्लिकेशन, Realtime Database से डेटा कैसे पढ़ता है और उसमें डेटा कैसे लिखता है, आइए हम आपको टूल के एक ऐसे सेट के बारे में बताते हैं जिसका इस्तेमाल, Realtime Database की सुविधाओं का प्रोटोटाइप बनाने और उनकी जांच करने के लिए किया जा सकता है: Firebase Emulator Suite. अगर आपको अलग-अलग डेटा मॉडल आज़माने हैं, सुरक्षा नियमों को ऑप्टिमाइज़ करना है या बैक-एंड के साथ इंटरैक्ट करने का सबसे किफ़ायती तरीका ढूंढना है, तो लाइव सेवाओं को डिप्लॉय किए बिना स्थानीय तौर पर काम करना एक अच्छा विकल्प हो सकता है.

रीयलटाइम डेटाबेस एम्युलेटर, Emulator Suite का हिस्सा है. इसकी मदद से, आपका ऐप्लिकेशन, एम्युलेट किए गए डेटाबेस के कॉन्टेंट और कॉन्फ़िगरेशन के साथ-साथ, एम्युलेट किए गए प्रोजेक्ट के संसाधनों (फ़ंक्शन, अन्य डेटाबेस, और सुरक्षा के नियम) के साथ इंटरैक्ट कर सकता है.emulator_suite_short

रीयलटाइम डेटाबेस एम्युलेटर का इस्तेमाल करने के लिए, आपको बस कुछ चरणों का पालन करना होगा:

  1. एम्युलेटर से कनेक्ट करने के लिए, अपने ऐप्लिकेशन की टेस्ट कॉन्फ़िगरेशन में कोड की एक लाइन जोड़ना.
  2. अपनी लोकल प्रोजेक्ट डायरेक्ट्री की रूट से, firebase emulators:start को चलाकर.
  3. रीयलटाइम डेटाबेस प्लैटफ़ॉर्म के एसडीके का इस्तेमाल करके, अपने ऐप्लिकेशन के प्रोटोटाइप कोड से कॉल करना या रीयलटाइम डेटाबेस के 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 क्लाइंट उस डेटा को रिमोट डेटाबेस सर्वर और अन्य क्लाइंट के साथ "बेस्ट-एफ़र्ट" के आधार पर सिंक करता है.

इस वजह से, डेटाबेस में कुछ भी लिखने पर, लोकल इवेंट तुरंत ट्रिगर हो जाते हैं. ऐसा सर्वर पर कोई भी डेटा लिखे जाने से पहले होता है. इसका मतलब है कि नेटवर्क की स्पीड कम होने या कनेक्टिविटी न होने पर भी, आपका ऐप्लिकेशन काम करता रहेगा.

कनेक्टिविटी फिर से चालू होने पर, आपके ऐप्लिकेशन को इवेंट का सही सेट मिलता है. इससे क्लाइंट, सर्वर की मौजूदा स्थिति के साथ सिंक हो जाता है. इसके लिए, आपको कोई कस्टम कोड लिखने की ज़रूरत नहीं होती.

हम ऑनलाइन और ऑफ़लाइन सुविधाओं के बारे में ज़्यादा जानें लेख में, ऑफ़लाइन व्यवहार के बारे में ज़्यादा जानकारी देंगे.

अगले चरण