يتناول هذا المستند العمل مع قوائم البيانات في Firebase. لمعرفة أساسيات قراءة بيانات Firebase وكتابتها، يمكنك الاطّلاع على قراءة البيانات وكتابتها على Android.
الحصول على DatabaseReference
لقراءة البيانات وكتابتها من قاعدة البيانات، تحتاج إلى مثيل من DatabaseReference
:
Kotlin+KTX
private lateinit var database: DatabaseReference // ... database = Firebase.database.reference
Java
private DatabaseReference mDatabase; // ... mDatabase = FirebaseDatabase.getInstance().getReference();
قراءة القوائم وكتابتها
إلحاق بقائمة من البيانات
يمكنك استخدام الطريقة push()
لإلحاق البيانات بقائمة في التطبيقات المتعددة المستخدمين.
تنشئ الطريقة push()
مفتاحًا فريدًا في كل مرة تتم فيها إضافة
عنصر فرعي جديد إلى مرجع Firebase المحدّد. باستخدام هذه
المفاتيح التي يتم إنشاؤها تلقائيًا لكل عنصر جديد في القائمة، يمكن لعدة عملاء
إضافة عناصر فرعية إلى الموقع الجغرافي نفسه في الوقت نفسه بدون حدوث تعارضات في الكتابة. يستند
المفتاح الفريد الذي تم إنشاؤه بواسطة push()
إلى طابع زمني، لذلك يتم ترتيب عناصر القائمة
تلقائيًا ترتيبًا زمنيًا.
يمكنك استخدام إشارة إلى البيانات الجديدة التي تعرضها الطريقة push()
للحصول على
قيمة مفتاح الطفل الذي تم إنشاؤه تلقائيًا أو ضبط بيانات للطفل. يؤدي استدعاء
getKey()
على مرجع push()
إلى عرض قيمة
المفتاح الذي يتم إنشاؤه تلقائيًا.
يمكنك استخدام هذه المفاتيح التي يتم إنشاؤها تلقائيًا لتبسيط تسطيح بنية البيانات. لمزيد من المعلومات، يمكنك الاطّلاع على مثال توزيع البيانات.
الاستماع إلى أحداث الطفل
عند العمل مع القوائم، يجب أن يستمع تطبيقك إلى أحداث العناصر الفرعية بدلاً من أحداث القيمة المستخدَمة للكائنات الفردية.
يتم بدء أحداث العناصر الفرعية استجابةً لعمليات معيّنة تحدث ل
العناصر الفرعية للعقدة من عملية، مثل إضافة عنصر فرعي جديد من خلال الأسلوب
push()
أو تعديل عنصر فرعي من خلال الأسلوب updateChildren()
.
يمكن أن يكون كل من هذه العناصر معًا مفيدًا للاستماع إلى التغييرات في عقدة معينة في قاعدة بيانات.
للاستماع إلى الأحداث الفرعية على DatabaseReference
، يجب إرفاق
ChildEventListener
:
أداة معالجة الأحداث | معاودة الاتصال بالحدث | الاستخدام المعتاد |
---|---|---|
ChildEventListener
| onChildAdded() |
استرداد قوائم بالعناصر أو الاستماع إلى الإضافات إلى قائمة بالعناصر
يتم تشغيل هذا المرجع مرة واحدة لكل عنصر فرعي حالي، ثم مرة أخرى
في كل مرة تتم فيها إضافة عنصر فرعي جديد إلى المسار المحدّد. يحتوي العنصر DataSnapshot الذي تم تمريره إلى المُستمع على
بيانات الطفل الجديد.
|
onChildChanged() |
الاستماع إلى التغييرات في العناصر ضمن قائمة يتمّ تشغيل هذا الحدث في أيّ وقت يتمّ فيه تعديل
عقدة فرعية، بما في ذلك أيّ تعديلات على العناصر المشتقة
من العقدة الفرعية. يحتوي العنصر DataSnapshot الذي تم تمريره إلى مستمع الحدث
على البيانات المعدَّلة للطفل.
|
|
onChildRemoved() |
استمع إلى العناصر التي تتم إزالتها من قائمة. يحتوي العنصر
DataSnapshot الذي تم تمريره إلى دالة الاستدعاء للحدث على
بيانات الطفل الذي تمّت إزالته.
|
|
onChildMoved() |
استمع إلى التغييرات في ترتيب العناصر في قائمة مرتبة.
يتم تشغيل هذا الحدث كلما تم تشغيل دالة الاستدعاء onChildChanged()
نتيجةً لتحديث يؤدي إلى إعادة ترتيب العنصر الفرعي.
ويتم استخدام هذه القيمة مع البيانات المطلوبة باستخدام orderByChild أو orderByValue .
|
على سبيل المثال، قد يستخدم تطبيق التدوين الاجتماعي هذه الأساليب معًا لمراقبة النشاط في تعليقات المشاركة، كما هو موضح أدناه:
Kotlin+KTX
val childEventListener = object : ChildEventListener { override fun onChildAdded(dataSnapshot: DataSnapshot, previousChildName: String?) { Log.d(TAG, "onChildAdded:" + dataSnapshot.key!!) // A new comment has been added, add it to the displayed list val comment = dataSnapshot.getValue<Comment>() // ... } override fun onChildChanged(dataSnapshot: DataSnapshot, previousChildName: String?) { Log.d(TAG, "onChildChanged: ${dataSnapshot.key}") // A comment has changed, use the key to determine if we are displaying this // comment and if so displayed the changed comment. val newComment = dataSnapshot.getValue<Comment>() val commentKey = dataSnapshot.key // ... } override fun onChildRemoved(dataSnapshot: DataSnapshot) { Log.d(TAG, "onChildRemoved:" + dataSnapshot.key!!) // A comment has changed, use the key to determine if we are displaying this // comment and if so remove it. val commentKey = dataSnapshot.key // ... } override fun onChildMoved(dataSnapshot: DataSnapshot, previousChildName: String?) { Log.d(TAG, "onChildMoved:" + dataSnapshot.key!!) // A comment has changed position, use the key to determine if we are // displaying this comment and if so move it. val movedComment = dataSnapshot.getValue<Comment>() val commentKey = dataSnapshot.key // ... } override fun onCancelled(databaseError: DatabaseError) { Log.w(TAG, "postComments:onCancelled", databaseError.toException()) Toast.makeText( context, "Failed to load comments.", Toast.LENGTH_SHORT, ).show() } } databaseReference.addChildEventListener(childEventListener)
Java
ChildEventListener childEventListener = new ChildEventListener() { @Override public void onChildAdded(DataSnapshot dataSnapshot, String previousChildName) { Log.d(TAG, "onChildAdded:" + dataSnapshot.getKey()); // A new comment has been added, add it to the displayed list Comment comment = dataSnapshot.getValue(Comment.class); // ... } @Override public void onChildChanged(DataSnapshot dataSnapshot, String previousChildName) { Log.d(TAG, "onChildChanged:" + dataSnapshot.getKey()); // A comment has changed, use the key to determine if we are displaying this // comment and if so displayed the changed comment. Comment newComment = dataSnapshot.getValue(Comment.class); String commentKey = dataSnapshot.getKey(); // ... } @Override public void onChildRemoved(DataSnapshot dataSnapshot) { Log.d(TAG, "onChildRemoved:" + dataSnapshot.getKey()); // A comment has changed, use the key to determine if we are displaying this // comment and if so remove it. String commentKey = dataSnapshot.getKey(); // ... } @Override public void onChildMoved(DataSnapshot dataSnapshot, String previousChildName) { Log.d(TAG, "onChildMoved:" + dataSnapshot.getKey()); // A comment has changed position, use the key to determine if we are // displaying this comment and if so move it. Comment movedComment = dataSnapshot.getValue(Comment.class); String commentKey = dataSnapshot.getKey(); // ... } @Override public void onCancelled(DatabaseError databaseError) { Log.w(TAG, "postComments:onCancelled", databaseError.toException()); Toast.makeText(mContext, "Failed to load comments.", Toast.LENGTH_SHORT).show(); } }; databaseReference.addChildEventListener(childEventListener);
رصد أحداث القيم
مع أنّ استخدام ChildEventListener
هو الطريقة التي ننصح بها لقراءة قوائم البيانات، هناك حالات يكون فيها إرفاق ValueEventListener
بمرجع قائمة مفيدًا.
سيؤدي إرفاق ValueEventListener
بقائمة بيانات إلى عرض
قائمة البيانات بالكامل كDataSnapshot
واحد، ويمكنك بعد ذلك تكرار العملية للوصول إلى
أطفال فرديين.
حتى في حال توفّر مطابقة واحدة فقط لطلب البحث، تظلّ اللقطة قائمة، ولكنها تحتوي على عنصر واحد فقط. للوصول إلى العنصر، عليك تكرار قراءة النتيجة:
Kotlin+KTX
// My top posts by number of stars myTopPostsQuery.addValueEventListener(object : ValueEventListener { override fun onDataChange(dataSnapshot: DataSnapshot) { for (postSnapshot in dataSnapshot.children) { // TODO: handle the post } } override fun onCancelled(databaseError: DatabaseError) { // Getting Post failed, log a message Log.w(TAG, "loadPost:onCancelled", databaseError.toException()) // ... } })
Java
// My top posts by number of stars myTopPostsQuery.addValueEventListener(new ValueEventListener() { @Override public void onDataChange(@NonNull DataSnapshot dataSnapshot) { for (DataSnapshot postSnapshot: dataSnapshot.getChildren()) { // TODO: handle the post } } @Override public void onCancelled(@NonNull DatabaseError databaseError) { // Getting Post failed, log a message Log.w(TAG, "loadPost:onCancelled", databaseError.toException()); // ... } });
يمكن أن يكون هذا النمط مفيدًا عندما تريد جلب جميع العناصر الفرعية لقائمة
في عملية واحدة، بدلاً من الانتظار لأحداث onChildAdded
إضافية.
فصل المستمعين
تتم إزالة طلبات إعادة الاتصال من خلال استدعاء طريقة removeEventListener()
في
مرجع قاعدة بيانات Firebase.
إذا تمت إضافة مستمع عدة مرات إلى موقع بيانات، يتم استدعاؤه عدة مرات لكل حدث، ويجب فصله العدد نفسه من المرات لإزالته بالكامل.
لا يؤدي استدعاء removeEventListener()
في مستمع رئيسي إلى
إزالة المستمعين المسجَّلين في العقد الفرعية تلقائيًا،
ويجب أيضًا استدعاء removeEventListener()
في أي مستمعين فرعيين
لإزالة طلب الاستدعاء.
فرز البيانات وتصفيتها
يمكنك استخدام فئة Realtime Database Query
لاسترداد البيانات التي تم ترتيبها حسب
المفتاح أو القيمة أو قيمة العنصر الفرعي. يمكنك أيضًا فلترة
النتيجة المرتبطة بعدد معيّن من النتائج أو نطاق من المفاتيح أو
القيم.
ترتيب البيانات
لاسترداد البيانات التي تم فرزها، ابدأ بتحديد إحدى طرق الترتيب حسب لتحديد كيفية ترتيب النتائج:
الطريقة | الاستخدام |
---|---|
orderByChild() |
ترتيب النتائج حسب قيمة مفتاح فرعي محدّد أو مسار فرعي متداخل |
orderByKey()
| ترتيب النتائج حسب مفاتيح العناصر الفرعية |
orderByValue() |
ترتيب النتائج حسب قيم السمات الثانوية |
يمكنك استخدام طريقة واحدة فقط لترتيب النتائج في كل مرة. يؤدي استدعاء طريقة ترتيب بالاستناد إلى عمود معيّن مرارًا وتكرارًا في طلب البحث نفسه إلى ظهور خطأ.
يوضّح المثال التالي كيفية استرداد قائمة بأحد مستخدمي Flickr أهم مشاركاته مرتبة حسب عدد النجوم:
Kotlin+KTX
// My top posts by number of stars val myUserId = uid val myTopPostsQuery = databaseReference.child("user-posts").child(myUserId) .orderByChild("starCount") myTopPostsQuery.addChildEventListener(object : ChildEventListener { // TODO: implement the ChildEventListener methods as documented above // ... })
Java
// My top posts by number of stars String myUserId = getUid(); Query myTopPostsQuery = databaseReference.child("user-posts").child(myUserId) .orderByChild("starCount"); myTopPostsQuery.addChildEventListener(new ChildEventListener() { // TODO: implement the ChildEventListener methods as documented above // ... });
يحدِّد هذا طلب بحث يتم من خلاله عند دمجه مع مستمع فرعي مزامنة العميل مع المشاركات الخاصة بالمستخدم من المسار في قاعدة البيانات استنادًا إلى معرّف المستخدم، وترتيبها حسب عدد النجوم التي حصلت عليها كل مشاركة. يُطلق على أسلوب استخدام أرقام التعريف كمفاتيح فهرسة اسم "توزيع البيانات"، ويمكنك قراءة المزيد عنها في تنظيم قاعدة البيانات.
تحدِّد الدعوة إلى الطريقة orderByChild()
مفتاح الطفل لترتيب
النتائج. في هذه الحالة، يتم ترتيب المشاركات حسب قيمة عنصر
"starCount"
الفرعي المعني. يمكن أيضًا ترتيب الاستعلامات حسب العناصر الثانوية
المتداخلة، في حال كان لديك بيانات تبدو كالتالي:
"posts": { "ts-functions": { "metrics": { "views" : 1200000, "likes" : 251000, "shares": 1200, }, "title" : "Why you should use TypeScript for writing Cloud Functions", "author": "Doug", }, "android-arch-3": { "metrics": { "views" : 900000, "likes" : 117000, "shares": 144, }, "title" : "Using Android Architecture Components with Firebase Realtime Database (Part 3)", "author": "Doug", } },
في هذا المثال، يمكننا ترتيب عناصر القائمة حسب القيم المُدمجة ضمن المفتاح
metrics
من خلال تحديد المسار النسبي للعنصر الثانوي المُدمج في طلب
orderByChild()
.
Kotlin+KTX
// Most viewed posts val myMostViewedPostsQuery = databaseReference.child("posts") .orderByChild("metrics/views") myMostViewedPostsQuery.addChildEventListener(object : ChildEventListener { // TODO: implement the ChildEventListener methods as documented above // ... })
Java
// Most viewed posts Query myMostViewedPostsQuery = databaseReference.child("posts") .orderByChild("metrics/views"); myMostViewedPostsQuery.addChildEventListener(new ChildEventListener() { // TODO: implement the ChildEventListener methods as documented above // ... });
لمزيد من المعلومات عن كيفية ترتيب أنواع البيانات الأخرى، يُرجى الاطّلاع على كيفية ترتيب بيانات طلبات البحث.
تصفية البيانات
لفلترة البيانات، يمكنك دمج أيّ من طرق الحدّ أو النطاق مع طريقة order-by عند إنشاء طلب بحث.
الطريقة | الاستخدام |
---|---|
limitToFirst() |
تحدد هذه السمة الحد الأقصى لعدد العناصر المطلوب عرضها من بداية قائمة النتائج المرتبة. |
limitToLast() |
تُستخدَم لضبط الحد الأقصى لعدد العناصر التي سيتم عرضها من نهاية قائمة النتائج مرتبةً. |
startAt() |
عرض العناصر التي تكون أكبر من أو تساوي المفتاح أو القيمة المحدّدة استنادًا إلى طريقة الترتيب المحدّدة |
startAfter() |
عرض العناصر التي تزيد عن المفتاح أو القيمة المحدّدة استنادًا إلى طريقة الترتيب التي تم اختيارها |
endAt() |
عرض العناصر التي تكون قيمتها أقل من أو مساوية للمفتاح أو القيمة المحدّدة استنادًا إلى طريقة الترتيب التي تم اختيارها |
endBefore() |
يمكنك إرجاع عناصر أقل من المفتاح أو القيمة المحدّدة بناءً على طريقة الترتيب المحدّدة. |
equalTo() |
عرض العناصر التي تساوي المفتاح أو القيمة المحدّدة استنادًا إلى طريقة الترتيب المحدّدة |
على عكس طرق "الترتيب حسب"، يمكنك دمج عدّة دوالّ حدّ أو نطاق.
على سبيل المثال، يمكنك دمج الطريقتَين startAt()
وendAt()
للحدّ من
النتائج إلى نطاق محدّد من القيم.
حتى عندما يكون هناك تطابق واحد فقط للاستعلام، فإن اللقطة لا تزال قائمة؛ بل تحتوي فقط على عنصر واحد. للوصول إلى العنصر، عليك تكرار النتيجة:
Kotlin+KTX
// My top posts by number of stars myTopPostsQuery.addValueEventListener(object : ValueEventListener { override fun onDataChange(dataSnapshot: DataSnapshot) { for (postSnapshot in dataSnapshot.children) { // TODO: handle the post } } override fun onCancelled(databaseError: DatabaseError) { // Getting Post failed, log a message Log.w(TAG, "loadPost:onCancelled", databaseError.toException()) // ... } })
Java
// My top posts by number of stars myTopPostsQuery.addValueEventListener(new ValueEventListener() { @Override public void onDataChange(@NonNull DataSnapshot dataSnapshot) { for (DataSnapshot postSnapshot: dataSnapshot.getChildren()) { // TODO: handle the post } } @Override public void onCancelled(@NonNull DatabaseError databaseError) { // Getting Post failed, log a message Log.w(TAG, "loadPost:onCancelled", databaseError.toException()); // ... } });
الحد من عدد النتائج
يمكنك استخدام الطريقتَين limitToFirst()
وlimitToLast()
لتحديد
الحد الأقصى لعدد العناصر الفرعية التي سيتم مزامنتها لطلب معاودة اتصال معيّن. على سبيل المثال، إذا
كنت تستخدم limitToFirst()
لضبط حدّ أقصى يبلغ 100، لن تتلقّى في البداية سوى
ما يصل إلى 100 مكالمة onChildAdded()
. إذا كان لديك أقل من 100 عنصر مخزَّن في
قاعدة بيانات Firebase، يتم تنشيط استدعاء onChildAdded()
لكل عنصر.
عندما تتغيّر العناصر، تتلقّى onChildAdded()
طلب استدعاء للعناصر التي تدخل في
طلب البحث وonChildRemoved()
طلب استدعاء للعناصر التي تخرج منه كي يظل
إجمالي العدد 100.
يوضح المثال التالي كيف يحدد تطبيق تدوين البيانات استعلامًا لاسترداد قائمة بأحدث 100 مشاركة نشرها جميع المستخدمين:
Kotlin+KTX
// Last 100 posts, these are automatically the 100 most recent // due to sorting by push() keys. databaseReference.child("posts").limitToFirst(100)
Java
// Last 100 posts, these are automatically the 100 most recent // due to sorting by push() keys Query recentPostsQuery = databaseReference.child("posts") .limitToFirst(100);
يحدِّد هذا المثال طلب بحث فقط، ولكن لمزامنة البيانات فعليًا، يجب أن يكون لديه مستمع مرفق.
الفلترة حسب المفتاح أو القيمة
يمكنك استخدام startAt()
وstartAfter()
وendAt()
وendBefore()
وequalTo()
لاختيار نقاط بداية ونهاية وتكافؤ عشوائية
لطلبات البحث. يمكن أن يكون هذا مفيدًا لتقسيم البيانات إلى صفحات أو البحث عن عناصر بها أطفال لها قيمة محددة.
كيفية ترتيب بيانات طلبات البحث
يشرح هذا القسم كيفية ترتيب البيانات حسب كل طريقة من الطرق بالترتيب في
الفئة Query
.
orderByChild
عند استخدام orderByChild()
، يتم ترتيب البيانات التي تحتوي على مفتاح فرعي محدّد
على النحو التالي:
- تظهر أولاً العناصر الفرعية التي تحتوي على قيمة
null
لمفتاح العنصر الفرعي المحدّد. - تأتي بعد ذلك القيم التي تحتوي على
false
لمفتاح الطفل المحدّد. إذا كانت القيمةfalse
لعدة عناصر ثانوية، يتم ترتيبها معجميًا حسب المفتاح. - تأتي بعد ذلك القيم التي تحتوي على
true
لمفتاح الطفل المحدّد. إذا كانت قيمة عناصر فرعية متعددة هيtrue
، يتم ترتيبها أبجديًا حسب المفتاح. - تأتي العناصر الفرعية التي تحتوي على قيمة رقمية بعد ذلك، ويتم ترتيبها تصاعديًا. إذا كانت هناك عدة عناصر ثانوية لها القيمة الرقمية نفسها للعقدة الفرعية المحدّدة، يتم ترتيبها حسب المفتاح.
- تأتي السلاسل بعد الأرقام ويتم ترتيبها أبجديًا بترتيب تصاعدي. إذا كانت عدّة عناصر فرعية لها القيمة نفسها لعنصر فرعي محدّد، يتم ترتيبها أبجديًا حسب المفتاح.
- تأتي الكائنات في النهاية ويتم فرزها قاموسًا حسب المفتاح بترتيب تصاعدي.
orderByKey
عند استخدام orderByKey()
لترتيب بياناتك، يتم عرض البيانات بترتيب تصاعدي
حسب المفتاح.
- تظهر أولاً العناصر الفرعية التي تحتوي على مفتاح يمكن تحليله كعدد صحيح 32 بت، ويتم ترتيبها تصاعديًا.
- تأتي بعد ذلك العناصر الفرعية التي تحتوي على قيمة سلسلة كمفتاح لها، ويتم ترتيبها أبجديًا بترتيب تصاعدي.
orderByValue
عند استخدام orderByValue()
، يتم ترتيب العناصر الفرعية حسب قيمتها. تكون معايير الترتيب
نفسها كما في orderByChild()
، باستثناء أنّه يتم استخدام قيمة العقدة بدلاً من قيمة مفتاح فرعي محدّد.