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

(اختياري) إنشاء نموذج أولي واختباره باستخدام 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 كالمعتاد، أو باستخدام Realtime Database REST API.

يتوفّر شرح تفصيلي يتضمّن و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(). يؤدي استدعاء off() في الموقع الجغرافي بدون أي وسيطات إلى إزالة جميع المستمعين في هذا الموقع الجغرافي.

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

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

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

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

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