قراءة البيانات وكتابتها

(اختياري) إنشاء نموذج أولي واختباره باستخدام "حزمة محاكي Firebase"

قبل الحديث عن كيفية قراءة تطبيقك لقاعدة بيانات Realtime، والكتابة إليها، لنقدم مجموعة من الأدوات التي يمكنك استخدامها لإنشاء نموذج أولي واختبار قاعدة البيانات في الوقت الفعلي الوظيفة: "مجموعة محاكي Firebase". إذا كنت تجرب بيانات مختلفة أو تحسين قواعد الأمان أو العمل على العثور على فعّالة من حيث التكلفة للتفاعل مع الواجهة الخلفية، والقدرة على العمل محليًا بدون نشر خدمات مباشرة.

ويُعد محاكي قاعدة البيانات في الوقت الفعلي جزءًا من مجموعة أدوات المحاكاة، التي للتطبيق التفاعل مع محتوى قاعدة البيانات التي تمّت محاكاتها وإعداداتها، بالإضافة إلى موارد المشروع التي تمت محاكاتها (الدوال وقواعد البيانات الأخرى وقواعد الأمان).emulator_suite_short

يتضمن استخدام محاكي قاعدة البيانات في الوقت الفعلي بضع خطوات فقط:

  1. إضافة سطر من الرمز إلى إعدادات اختبار تطبيقك للاتصال بالمحاكي.
  2. من جذر دليل المشروع المحلي، مع تشغيل firebase emulators:start.
  3. إجراء الاتصالات من رمز النموذج الأولي لتطبيقك باستخدام النظام الأساسي لقاعدة البيانات في الوقت الفعلي SDK كالمعتاد، أو تستخدم Realtime Database REST API.

تتوفّر جولة تفصيلية تشمل قاعدة بيانات "الوقت الفعلي" وCloud Functions. من المفترض أيضًا أن تلقي نظرة على مقدمة عن مجموعة المحاكيات.

الحصول على 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 name, leave the age 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",
});

قراءة البيانات

قراءة البيانات من خلال الاستماع إلى أحداث القيمة

لقراءة البيانات على أحد المسارات والاستماع إلى التغييرات، استخدم سمة onValue في DatabaseReference يجب الاستماع إليها DatabaseEvent

يمكنك استخدام 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()

تم تصميم حزمة SDK لإدارة التفاعلات مع خوادم قاعدة البيانات سواء كانت تطبيقك متصلاً بالإنترنت أو غير متصل بالإنترنت.

بوجهٍ عام، عليك استخدام تقنيات أحداث القيم الموضّحة أعلاه لقراءة البيانات لتلقي إشعار بالتحديثات التي تطرأ على البيانات من الخلفية. تلك الأساليب وتقليل الاستخدام والفوترة، وتحسينهما لمنح المستخدمين أفضل المستخدم أثناء اتصاله بالإنترنت وبلا اتصال بالإنترنت.

إذا كنت بحاجة إلى البيانات مرة واحدة فقط، يمكنك استخدام 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() للحصول على البيانات من ذاكرة التخزين المؤقت على القرص المحلي. فورًا.

يفيد ذلك في البيانات التي تحتاج إلى تحميلها مرة واحدة فقط ولا يُتوقع أن يتم تحميلها تتغير بشكل متكرر أو تتطلب الاستماع الفعال. على سبيل المثال، يستخدم تطبيق التدوين في الأمثلة السابقة هذه الطريقة لتحميل الملف الشخصي لأي مستخدم عندما البدء في كتابة منشور جديد:

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 تلك البيانات مع قاعدة البيانات البعيدة. مع العملاء الآخرين على "أفضل جهد" بشكل أساسي.

ونتيجةً لذلك، تؤدي جميع عمليات الكتابة في قاعدة البيانات إلى تشغيل الأحداث المحلية على الفور، قبل كتابة أي بيانات على الخادم. هذا يعني أنّ تطبيقك سيبقى سريع الاستجابة بغض النظر عن وقت استجابة الشبكة أو الاتصال.

وبمجرد إعادة الاتصال، يتلقى تطبيقك المجموعة المناسبة من بحيث يتزامن البرنامج مع حالة الخادم الحالية، دون الحاجة إلى وكتابة أي رمز مخصص.

سنتحدث أكثر عن السلوك خارج الإنترنت في مزيد من المعلومات حول الإمكانات على الإنترنت وبلا إنترنت

الخطوات التالية