يتناول هذا المستند العمل مع قوائم البيانات في Firebase. للتعرّف على أساسيات قراءة بيانات Firebase وكتابتها، اطّلِع على مقالة قراءة البيانات وكتابتها على Android.
الحصول على DatabaseReference
لقراءة البيانات وكتابتها من قاعدة البيانات، تحتاج إلى مثيل من DatabaseReference
:
Kotlin
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
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
// 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
// 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
// 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() |
عرض العناصر التي تساوي المفتاح أو القيمة المحدّدة حسب طريقة الترتيب التي تم اختيارها |
على عكس طرق order-by، يمكنك دمج عدّة دوالّ حدّ أو نطاق.
على سبيل المثال، يمكنك دمج الطريقتَين startAt()
وendAt()
للحدّ من
النتائج إلى نطاق محدّد من القيم.
حتى في حال توفّر مطابقة واحدة فقط لطلب البحث، تظلّ اللقطة قائمة، ولكنها تحتوي على عنصر واحد فقط. للوصول إلى العنصر، عليك تكرار النتيجة:
Kotlin
// 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.
يوضّح المثال التالي كيفية تحديد تطبيق التدوين مثالًا طلب بحث ل retrieving list of the 100 most recent posts by all users:
Kotlin
// 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()
، باستثناء أنّه يتم استخدام قيمة العقدة بدلاً من قيمة مفتاح فرعي محدّد.