يتناول هذا المستند كيفية العمل مع قوائم البيانات في 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() |
ترتيب النتائج حسب القيم الفرعية |
يمكنك استخدام طريقة ترتيب واحدة فقط في كل مرة. يؤدي استدعاء طريقة الترتيب عدة مرات في طلب البحث نفسه إلى ظهور خطأ.
يوضّح المثال التالي كيفية استرداد قائمة بأهم منشورات المستخدم مرتّبة حسب عدد النجوم:
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 // ... });
يحدّد هذا طلب بحث عند دمجه مع مستمع فرعي يزامن العميل مع مشاركات المستخدم من المسار في قاعدة البيانات بناءً على رقم تعريف المستخدم، مرتبة حسب عدد النجوم التي حصلت عليها كل مشاركة. تُعرف هذه التقنية التي تستخدم المعرّفات كمفاتيح فهرسة باسم "توزيع البيانات"، ويمكنك الاطّلاع على مزيد من المعلومات عنها في مقالة بناء قاعدة بياناتك.
The call to the orderByChild() method specifies the child key to order the
results by. في هذه الحالة، يتم ترتيب المشاركات حسب قيمة العنصر الثانوي "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 // ... });
لمزيد من المعلومات حول كيفية ترتيب أنواع البيانات الأخرى، يُرجى الاطّلاع على كيفية ترتيب بيانات طلبات البحث.
تصفية البيانات
لفلترة البيانات، يمكنك دمج أي من طرق الحد أو النطاق مع طريقة الترتيب عند إنشاء طلب بحث.
| الطريقة | الاستخدام |
|---|---|
limitToFirst() |
تحدّد هذه المَعلمة الحد الأقصى لعدد العناصر التي سيتم عرضها من بداية قائمة النتائج المرتبة. |
limitToLast() |
تحدّد هذه المَعلمة الحد الأقصى لعدد العناصر التي يتم عرضها من نهاية قائمة النتائج المرتبة. |
startAt() |
عرض العناصر التي تزيد قيمتها عن أو تساوي المفتاح أو القيمة المحدّدة استنادًا إلى طريقة الترتيب التي تم اختيارها |
startAfter() |
عرض العناصر الأكبر من المفتاح أو القيمة المحدّدة استنادًا إلى طريقة الترتيب التي تم اختيارها |
endAt() |
عرض العناصر التي تكون أقل من أو تساوي المفتاح أو القيمة المحدّدة حسب طريقة الترتيب التي تم اختيارها |
endBefore() |
عرض العناصر التي تكون أقل من المفتاح أو القيمة المحدّدة حسب طريقة الترتيب التي تم اختيارها |
equalTo() |
عرض العناصر التي تساوي المفتاح أو القيمة المحدّدة حسب طريقة الترتيب التي تم اختيارها |
على عكس طرق الترتيب، يمكنك الجمع بين عدة دوال للحدّ أو النطاق.
على سبيل المثال، يمكنك الجمع بين الطريقتين 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.
يوضح المثال التالي كيفية قيام تطبيق التدوين النموذجي بتعريف استعلام لاسترداد قائمة بأحدث 100 مشاركة من قبل جميع المستخدمين:
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()، باستثناء أنّه يتم استخدام قيمة العقدة بدلاً من قيمة مفتاح فرعي محدّد.