العمل باستخدام قوائم البيانات على Android

يتناول هذا المستند كيفية التعامل مع قوائم البيانات في 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() ترتيب النتائج حسب القيم الفرعية

يمكنك استخدام طريقة واحدة للترتيب في كل مرة. سيؤدي استدعاء طريقة order-by عدّة مرات في طلب البحث نفسه إلى حدوث خطأ.

يوضّح المثال التالي كيف يمكنك استرداد قائمة بأهم مشاركات المستخدمين مرتّبة حسب عدد النجوم:

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
    // ...
});

يحدّد هذا الإجراء طلب بحث يتم دمجه مع معالج أحداث فرعي لمزامنة العميل مع مشاركات المستخدم من المسار في قاعدة البيانات استنادًا إلى معرّف المستخدم، ويتم ترتيبها حسب عدد النجوم التي حصلت عليها كل مشاركة. تُعرف تقنية استخدام المعرّفات كمفاتيح فهرس باسم &quot;توزيع البيانات&quot;، ويمكنك الاطّلاع على مزيد من المعلومات حول هذا الموضوع في مقالة تنظيم قاعدة البيانات.

يحدّد طلب الطريقة 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() عرض العناصر التي تساوي المفتاح أو القيمة المحدّدة استنادًا إلى طريقة الترتيب المحدّدة

على عكس طرق الترتيب حسب، يمكنك الجمع بين عدّة دوال للحدّ أو النطاق. على سبيل المثال، يمكنك الجمع بين الطريقتَين 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()، يتم ترتيب البيانات التي تحتوي على مفتاح العنصر التابع المحدّد على النحو التالي:

  1. يتم عرض الأطفال الذين لديهم قيمة null لمفتاح الطفل المحدّد أولاً.
  2. يأتي بعد ذلك الأطفال الذين لديهم القيمة false لمفتاح الطفل المحدّد. إذا كان لدى عدة عناصر فرعية القيمة false، يتم ترتيبها معجميًا حسب المفتاح.
  3. يأتي بعد ذلك الأطفال الذين لديهم القيمة true لمفتاح الطفل المحدّد. إذا كان لدى عدة عناصر فرعية القيمة true، يتم ترتيبها معجميًا حسب المفتاح.
  4. تأتي بعد ذلك العناصر الفرعية التي تتضمّن قيمة رقمية، ويتم ترتيبها بترتيب تصاعدي. إذا كان لدى عدة عناصر فرعية القيمة الرقمية نفسها لعقدة الطفل المحدّدة، يتم ترتيبها حسب المفتاح.
  5. تأتي السلاسل بعد الأرقام ويتم ترتيبها معجميًا بترتيب تصاعدي. إذا كان لدى عدة عناصر فرعية القيمة نفسها لعقدة الطفل المحدّدة، يتم ترتيبها معجميًا حسب المفتاح.
  6. تأتي الكائنات في النهاية ويتم ترتيبها معجميًا حسب المفتاح بترتيب تصاعدي.

orderByKey

عند استخدام orderByKey() لفرز بياناتك، يتم عرض البيانات بترتيب تصاعدي حسب المفتاح.

  1. يتم عرض الأطفال الذين لديهم مفتاح يمكن تحليله كعدد صحيح 32 بت أولاً، ويتم ترتيبهم بترتيب تصاعدي.
  2. يأتي بعد ذلك الأطفال الذين لديهم قيمة سلسلة كمفتاح، ويتم ترتيبهم معجميًا بترتيب تصاعدي.

orderByValue

عند استخدام orderByValue()، يتم ترتيب الأطفال حسب قيمتهم. معايير الترتيب هي نفسها الواردة في orderByChild()، باستثناء أنّه يتم استخدام قيمة العقدة بدلاً من قيمة مفتاح ثانوي محدّد.

الخطوات التالية