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

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

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

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

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

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

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

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

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

واجهة برمجة التطبيقات Web modular API

import { getDatabase } from "firebase/database";

const database = getDatabase();

واجهة برمجة التطبيقات لمساحة الاسم على الويب

var database = firebase.database();

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

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

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

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

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

واجهة برمجة التطبيقات Web modular API

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
  });
}

واجهة برمجة التطبيقات لمساحة الاسم على الويب

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 modular API

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);
});

واجهة برمجة التطبيقات لمساحة الاسم على الويب

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 modular API

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);
});

واجهة برمجة التطبيقات لمساحة الاسم على الويب

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 modular API

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
});

واجهة برمجة التطبيقات لمساحة الاسم على الويب

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 modular API

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);
}

واجهة برمجة التطبيقات لمساحة الاسم على الويب

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 modular API

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...
});

واجهة برمجة التطبيقات لمساحة الاسم على الويب

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 في الوقت الفعلي، يمكنك استخدام Promise. ويمكن لكل من set() وupdate() عرض Promise يمكنك استخدامه لمعرفة متى تكون الكتابة ملتزمة بقاعدة البيانات.

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

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

يمكنك إزالة مستمع واحد من خلال تمريره كمَعلمة إلى off(). يؤدي طلب الرقم off() في الموقع الجغرافي بدون وسيطات إلى إزالة جميع المستمعين في هذا الموقع الجغرافي.

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

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

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

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

واجهة برمجة التطبيقات Web modular API

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;
  });
}

واجهة برمجة التطبيقات لمساحة الاسم على الويب

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 modular API

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);
}

واجهة برمجة التطبيقات لمساحة الاسم على الويب

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

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

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

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

الخطوات اللاحقة