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

(اختياري) إنشاء نموذج أوّلي واختباره باستخدام Firebase Local Emulator Suite

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

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

يتطلّب استخدام محاكي Realtime Database بضع خطوات فقط:

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

يتوفّر دليل تفصيلي يتضمّن Realtime Database وCloud Functions. ننصحك أيضًا بالاطّلاع على مقدمة حول Local Emulator Suite.

الحصول على مرجع قاعدة بيانات

لقراءة البيانات من قاعدة البيانات أو كتابتها، تحتاج إلى مثيل من firebase.database.Reference:

Web

import { getDatabase } from "firebase/database";

const database = getDatabase();

Web

var database = firebase.database();

كتابة البيانات

يتناول هذا المستند أساسيات استرداد البيانات وكيفية ترتيب بيانات Firebase وفلترتها.

يتم استرداد بيانات Firebase من خلال إرفاق مستمع غير متزامن بـ firebase.database.Reference. يتم تشغيل المستمع مرة واحدة للحالة الأولية للبيانات ومرة أخرى في أي وقت تتغير فيه البيانات.

عمليات الكتابة الأساسية

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

Web

import { getDatabase, ref, set } from "firebase/database";

function writeUserData(userId, name, email, imageUrl) {
  const db = getDatabase();
  set(ref(db, 'users/' + userId), {
    username: name,
    email: email,
    profile_picture : imageUrl
  });
}

Web

function writeUserData(userId, name, email, imageUrl) {
  firebase.database().ref('users/' + userId).set({
    username: name,
    email: email,
    profile_picture : imageUrl
  });
}

يؤدي استخدام set() إلى استبدال البيانات في الموقع الجغرافي المحدّد، بما في ذلك أي عُقد فرعية.

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

الاستماع إلى أحداث القيمة

لقراءة البيانات في مسار معيّن والاستماع إلى التغييرات، استخدِم onValue() لمراقبة الأحداث. يمكنك استخدام هذا الحدث لقراءة لقطات ثابتة للمحتوى في مسار معيّن، كما كانت موجودة في وقت الحدث. يتم تشغيل هذه الطريقة مرة واحدة عند إرفاق المستمع ومرة أخرى في كل مرة تتغير فيها البيانات، بما في ذلك العناصر الفرعية. يتم تمرير لقطة تحتوي على جميع البيانات في ذلك الموقع الجغرافي، بما في ذلك بيانات العناصر الثانوية، إلى دالة الاستدعاء الخاصة بالحدث. في حال عدم توفّر بيانات، ستعرض اللقطة false عند طلب exists() وnull عند طلب val().

يوضح المثال التالي تطبيق تدوين اجتماعي يسترد عدد النجوم لمنشور من قاعدة البيانات:

Web

import { getDatabase, ref, onValue } from "firebase/database";

const db = getDatabase();
const starCountRef = ref(db, 'posts/' + postId + '/starCount');
onValue(starCountRef, (snapshot) => {
  const data = snapshot.val();
  updateStarCount(postElement, data);
});

Web

var starCountRef = firebase.database().ref('posts/' + postId + '/starCount');
starCountRef.on('value', (snapshot) => {
  const data = snapshot.val();
  updateStarCount(postElement, data);
});

يتلقّى المستمع snapshot يحتوي على البيانات في الموقع المحدّد في قاعدة البيانات في وقت الحدث. يمكنك استرداد البيانات في snapshot باستخدام طريقة val().

قراءة البيانات لمرة واحدة

قراءة البيانات مرة واحدة باستخدام get()‎

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

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

إذا كنت بحاجة إلى البيانات مرة واحدة فقط، يمكنك استخدام get() للحصول على لقطة من البيانات من قاعدة البيانات. إذا لم يتمكّن get() لأي سبب من عرض قيمة الخادم، سيفحص العميل ذاكرة التخزين المؤقت المحلية ويعرض رسالة خطأ إذا لم يتم العثور على القيمة.

يمكن أن يؤدي الاستخدام غير الضروري لـ get() إلى زيادة استخدام معدل نقل البيانات وانخفاض الأداء، وهو ما يمكن منعه باستخدام أداة استماع في الوقت الفعلي كما هو موضّح أعلاه.

Web

import { getDatabase, ref, child, get } from "firebase/database";

const dbRef = ref(getDatabase());
get(child(dbRef, `users/${userId}`)).then((snapshot) => {
  if (snapshot.exists()) {
    console.log(snapshot.val());
  } else {
    console.log("No data available");
  }
}).catch((error) => {
  console.error(error);
});

Web

const dbRef = firebase.database().ref();
dbRef.child("users").child(userId).get().then((snapshot) => {
  if (snapshot.exists()) {
    console.log(snapshot.val());
  } else {
    console.log("No data available");
  }
}).catch((error) => {
  console.error(error);
});

قراءة البيانات مرة واحدة باستخدام مراقب

في بعض الحالات، قد تحتاج إلى عرض القيمة من ذاكرة التخزين المؤقت المحلية على الفور، بدلاً من البحث عن قيمة معدَّلة على الخادم. في هذه الحالات، يمكنك استخدام once() للحصول على البيانات من ذاكرة التخزين المؤقت للقرص المحلي على الفور.

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

Web

import { getDatabase, ref, onValue } from "firebase/database";
import { getAuth } from "firebase/auth";

const db = getDatabase();
const auth = getAuth();

const userId = auth.currentUser.uid;
return onValue(ref(db, '/users/' + userId), (snapshot) => {
  const username = (snapshot.val() && snapshot.val().username) || 'Anonymous';
  // ...
}, {
  onlyOnce: true
});

Web

var userId = firebase.auth().currentUser.uid;
return firebase.database().ref('/users/' + userId).once('value').then((snapshot) => {
  var username = (snapshot.val() && snapshot.val().username) || 'Anonymous';
  // ...
});

تعديل البيانات أو حذفها

تعديل حقول معيّنة

للكتابة في الوقت نفسه في عناصر فرعية محدّدة لعقدة بدون استبدال عناصر فرعية أخرى، استخدِم الطريقة update().

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

على سبيل المثال، قد ينشئ تطبيق تدوين اجتماعي منشورًا ويعدّله في الوقت نفسه في خلاصة الأنشطة الأخيرة وخلاصة أنشطة المستخدم الذي ينشر المحتوى باستخدام رمز برمجي مثل هذا:

Web

import { getDatabase, ref, child, push, update } from "firebase/database";

function writeNewPost(uid, username, picture, title, body) {
  const db = getDatabase();

  // A post entry.
  const postData = {
    author: username,
    uid: uid,
    body: body,
    title: title,
    starCount: 0,
    authorPic: picture
  };

  // Get a key for a new Post.
  const newPostKey = push(child(ref(db), 'posts')).key;

  // Write the new post's data simultaneously in the posts list and the user's post list.
  const updates = {};
  updates['/posts/' + newPostKey] = postData;
  updates['/user-posts/' + uid + '/' + newPostKey] = postData;

  return update(ref(db), updates);
}

Web

function writeNewPost(uid, username, picture, title, body) {
  // A post entry.
  var postData = {
    author: username,
    uid: uid,
    body: body,
    title: title,
    starCount: 0,
    authorPic: picture
  };

  // Get a key for a new Post.
  var newPostKey = firebase.database().ref().child('posts').push().key;

  // Write the new post's data simultaneously in the posts list and the user's post list.
  var updates = {};
  updates['/posts/' + newPostKey] = postData;
  updates['/user-posts/' + uid + '/' + newPostKey] = postData;

  return firebase.database().ref().update(updates);
}

يستخدم هذا المثال push() لإنشاء مشاركة في العقدة التي تحتوي على مشاركات لجميع المستخدمين في /posts/$postid واسترداد المفتاح في الوقت نفسه. يمكن بعد ذلك استخدام المفتاح لإنشاء إدخال ثانٍ في مشاركات المستخدم على /user-posts/$userid/$postid.

باستخدام هذه المسارات، يمكنك إجراء تعديلات متزامنة على مواقع متعددة في شجرة JSON من خلال طلب واحد إلى update()، مثل الطريقة التي ينشئ بها هذا المثال المنشور الجديد في كلا الموقعين. التعديلات المتزامنة التي يتم إجراؤها بهذه الطريقة تكون ذرية: إما أن تنجح جميع التعديلات أو تفشل جميع التعديلات.

إضافة دالة ردّ نداء للإكمال

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

Web

import { getDatabase, ref, set } from "firebase/database";

const db = getDatabase();
set(ref(db, 'users/' + userId), {
  username: name,
  email: email,
  profile_picture : imageUrl
})
.then(() => {
  // Data saved successfully!
})
.catch((error) => {
  // The write failed...
});

Web

firebase.database().ref('users/' + userId).set({
  username: name,
  email: email,
  profile_picture : imageUrl
}, (error) => {
  if (error) {
    // The write failed...
  } else {
    // Data saved successfully!
  }
});

حذف البيانات

أبسط طريقة لحذف البيانات هي استدعاء remove() على مرجع إلى موقع تلك البيانات.

يمكنك أيضًا حذف البيانات من خلال تحديد null كقيمة لعملية كتابة أخرى، مثل set() أو update(). يمكنك استخدام هذه الطريقة مع update() لحذف عدة عناصر فرعية في طلب واحد من واجهة برمجة التطبيقات.

الحصول على Promise

لمعرفة متى يتم إرسال بياناتك إلى خادم Firebase Realtime Database، يمكنك استخدام Promise. يمكن لكل من set() وupdate() عرض Promise يمكنك استخدامه لمعرفة وقت تنفيذ عملية الكتابة في قاعدة البيانات.

فصل المستمعين

تتم إزالة عمليات الاستدعاء من خلال استدعاء طريقة off() في مرجع قاعدة بيانات Firebase.

يمكنك إزالة مستمع واحد من خلال تمريره كمَعلمة إلى off(). Calling off() on the location with no arguments removes all listeners at that location.

لا يؤدي استدعاء off() على مستمع رئيسي إلى إزالة المستمعين المسجّلين على العُقد الفرعية تلقائيًا، بل يجب أيضًا استدعاء off() على أي مستمعين فرعيين لإزالة دالة الاستدعاء.

حفظ البيانات كمعاملات

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

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

Web

import { getDatabase, ref, runTransaction } from "firebase/database";

function toggleStar(uid) {
  const db = getDatabase();
  const postRef = ref(db, '/posts/foo-bar-123');

  runTransaction(postRef, (post) => {
    if (post) {
      if (post.stars && post.stars[uid]) {
        post.starCount--;
        post.stars[uid] = null;
      } else {
        post.starCount++;
        if (!post.stars) {
          post.stars = {};
        }
        post.stars[uid] = true;
      }
    }
    return post;
  });
}

Web

function toggleStar(postRef, uid) {
  postRef.transaction((post) => {
    if (post) {
      if (post.stars && post.stars[uid]) {
        post.starCount--;
        post.stars[uid] = null;
      } else {
        post.starCount++;
        if (!post.stars) {
          post.stars = {};
        }
        post.stars[uid] = true;
      }
    }
    return post;
  });
}

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

الزيادات التصاعدية البسيطة من جهة الخادم

في حالة الاستخدام أعلاه، نكتب قيمتين في قاعدة البيانات: رقم تعريف المستخدم الذي يضيف/يزيل النجمة من المنشور، وعدد النجوم المتزايد. إذا كنا نعلم بالفعل أن المستخدم يضع نجمة على المنشور، فيمكننا استخدام عملية زيادة ذرية بدلاً من المعاملة.

Web

function addStar(uid, key) {
  import { getDatabase, increment, ref, update } from "firebase/database";
  const dbRef = ref(getDatabase());

  const updates = {};
  updates[`posts/${key}/stars/${uid}`] = true;
  updates[`posts/${key}/starCount`] = increment(1);
  updates[`user-posts/${key}/stars/${uid}`] = true;
  updates[`user-posts/${key}/starCount`] = increment(1);
  update(dbRef, updates);
}

Web

function addStar(uid, key) {
  const updates = {};
  updates[`posts/${key}/stars/${uid}`] = true;
  updates[`posts/${key}/starCount`] = firebase.database.ServerValue.increment(1);
  updates[`user-posts/${key}/stars/${uid}`] = true;
  updates[`user-posts/${key}/starCount`] = firebase.database.ServerValue.increment(1);
  firebase.database().ref().update(updates);
}

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

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

العمل على البيانات بلا اتصال بالإنترنت

إذا فقد أحد العملاء اتصاله بالشبكة، سيستمر تطبيقك في العمل بشكل صحيح.

يحتفظ كل عميل متصل بقاعدة بيانات Firebase بنسخته الداخلية الخاصة من أي بيانات نشطة. عند كتابة البيانات، تتم كتابتها في هذه النسخة المحلية أولاً. بعد ذلك، يُزامن عميل Firebase هذه البيانات مع خوادم قواعد البيانات البعيدة ومع العملاء الآخرين على أساس "بذل قصارى الجهد".

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

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

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

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