العمل مع قوائم البيانات على 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() من أي مستمعين تابعين لإزالة رد النداء.

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

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

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