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

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

الحصول على DatabaseReference

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

وحتى في حالة وجود مطابقة واحدة فقط لطلب البحث، تظل اللقطة list؛ فإنها تحتوي فقط على عنصر واحد. للوصول إلى العنصر، تحتاج إلى التكرار فوق النتيجة:

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 التي تتضمّن Realtime Database لاسترداد بيانات مرتّبة حسب. مفتاح أو حسب القيمة أو حسب القيمة الفرعية. يمكنك أيضًا فلترة النتيجة التي تم فرزها إلى عدد محدد من النتائج أو مجموعة من المفاتيح أو القيم.

فرز البيانات

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

الطريقة الاستخدام
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.

يوضّح المثال التالي كيفية تحديد تطبيق التدوين مثالًا طلب بحث ل retrieving list of the 100 most recent posts by all users:

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()، باستثناء أنّه يتم استخدام قيمة العقدة بدلاً من قيمة مفتاح فرعي محدّد.

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