يتناول هذا المستند كيفية استخدام قوائم البيانات في 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 // ... });
يحدّد هذا الإجراء طلب بحث، وعند دمجه مع مستمع عنصر فرعي تتم مزامنة العميل مع منشورات المستخدِم من المسار في قاعدة البيانات استنادًا إلى رقم تعريف المستخدِم، ويتم ترتيبها حسب عدد النجوم التي تلقاها كل منشور. تُعرف هذه التقنية التي تستخدم أرقام التعريف كمفاتيح فهرس باسم توسيع البيانات، ويمكنك قراءة المزيد عنها في مقالة تنظيم قاعدة بياناتك.
يحدّد استدعاء طريقة 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 // ... });
لمزيد من المعلومات حول كيفية ترتيب أنواع البيانات الأخرى، يُرجى الاطّلاع على مقالة كيفية ترتيب بيانات طلب البحث.
فلترة البيانات
لفلترة البيانات، يمكنك دمج أي من طرق الحدّ أو النطاق مع طريقة ترتيب عند إنشاء طلب بحث.
| الطريقة | الاستخدام |
|---|---|
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()، باستثناء أنّه يتم استخدام قيمة العقدة بدلاً من قيمة مفتاح عنصر فرعي محدّد.