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

(اختياري) إنشاء نموذج أوّلي واختباره باستخدام 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() دالّة callback اختيارية عند اكتمال العملية، ويتمّ استدعاؤها عند تأكيد عملية الكتابة في قاعدة البيانات. إذا تعذّر إكمال الطلب، يتم تمرير كائن خطأ إلى دالة الاستدعاء يشير إلى سبب حدوث الخطأ.

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() على أي مستمع طفل لإزالة رد الاتصال.

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

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

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

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

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

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

.

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

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