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

يغطي هذا المستند العمل مع قوائم البيانات في Firebase. للتعرف على أساسيات قراءة بيانات Firebase وكتابتها، راجع قراءة البيانات وكتابتها على Android .

الحصول على مرجع قاعدة البيانات

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

فرز وتصفية البيانات

يمكنك استخدام فئة Query قاعدة البيانات في الوقت الفعلي لاسترداد البيانات التي تم فرزها حسب المفتاح أو حسب القيمة أو حسب قيمة الطفل. يمكنك أيضًا تصفية النتيجة التي تم فرزها إلى عدد محدد من النتائج أو نطاق من المفاتيح أو القيم.

فرز البيانات

لاسترداد البيانات التي تم فرزها، ابدأ بتحديد إحدى طرق الترتيب حسب لتحديد كيفية ترتيب النتائج:

طريقة الاستخدام
orderByChild() ترتيب النتائج حسب قيمة مفتاح فرعي محدد أو مسار فرعي متداخل.
orderByKey() ترتيب النتائج حسب المفاتيح الفرعية.
orderByValue() ترتيب النتائج حسب القيم الفرعية.

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

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

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

لمزيد من المعلومات حول كيفية ترتيب أنواع البيانات الأخرى، راجع كيفية ترتيب بيانات الاستعلام .

تصفية البيانات

لتصفية البيانات، يمكنك دمج أي من أساليب الحد أو النطاق مع أسلوب ترتيب حسب عند إنشاء استعلام.

طريقة الاستخدام
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() ، يتم ترتيب البيانات التي تحتوي على المفتاح الفرعي المحدد كما يلي:

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

orderByKey

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

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

orderByValue

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

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