(اختياري) إنشاء نموذج أوّلي واختباره باستخدام Firebase Local Emulator Suite
قبل الحديث عن كيفية قراءة تطبيقك لبيانات Realtime Database وكتابتها، نريد أن نقدّم لك مجموعة من الأدوات التي يمكنك استخدامها لإنشاء نماذج أولية لوظائف Realtime Database واختبارها: Firebase Local Emulator Suite. إذا كنت تختبر نماذج بيانات مختلفة أو تعمل على تحسين قواعد الأمان أو تبحث عن الطريقة الأكثر فعالية من حيث التكلفة للتفاعل مع الخلفية، قد يكون من المفيد أن تتمكّن من العمل على الجهاز بدون نشر الخدمات المباشرة.
يُعدّ محاكي Realtime Database جزءًا من Local Emulator Suite، ما يتيح لتطبيقك التفاعل مع محتوى قاعدة البيانات ومحتواها المحاكيَين، بالإضافة إلى موارد المشروع المحاكيَين (الدوالّ وقواعد الأمان وغيرها من قواعد بيانات) بشكل اختياري.
يتضمّن استخدام محاكي Realtime Database بضع خطوات فقط:
- إضافة سطر رمز إلى إعدادات اختبار تطبيقك للاتصال بالمحاكي
- من جذر دليل المشروع المحلي، يمكنك تشغيل
firebase emulators:start
. - إجراء طلبات من رمز النموذج الأولي لتطبيقك باستخدام حزمة تطوير البرامج (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 هذه البيانات مع قاعدة بيانات المشغّلات البعيدة ومع برامج أخرى على أساس "أقصى جهد".
ونتيجةً لذلك، تؤدي جميع عمليات الكتابة إلى قاعدة البيانات إلى بدء الأحداث المحلية على الفور، قبل كتابة أي بيانات على الخادم. وهذا يعني أنّ تطبيقك يظلّ سريع الاستجابة بغض النظر عن وقت استجابة الشبكة أو الاتصال بها.
بعد إعادة الاتصال، يتلقّى تطبيقك المجموعة المناسبة من الأحداث حتى تتم مزامنة العميل مع حالة الخادم الحالية، بدون الحاجة إلى كتابة أي رمز مخصّص.
.سنتحدث أكثر عن السلوك بلا إنترنت في مقالة مزيد من المعلومات حول الإمكانات المتاحة على الإنترنت وبلا إنترنت.