با لیست داده ها در اندروید کار کنید

این سند کار با لیست داده‌ها در Firebase را پوشش می‌دهد. برای یادگیری اصول اولیه خواندن و نوشتن داده‌های Firebase، به بخش «خواندن و نوشتن داده‌ها در اندروید» مراجعه کنید.

دریافت مرجع پایگاه داده

برای خواندن و نوشتن داده‌ها از پایگاه داده، به یک نمونه از 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 ارسال شده به رویداد callback شامل داده‌های مربوط به فرزند حذف شده است.
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 واحد برمی‌گرداند، که می‌توانید برای دسترسی به تک تک فرزندان، آن را پیمایش کنید.

حتی وقتی فقط یک مورد منطبق برای کوئری وجود دارد، snapshot هنوز یک لیست است؛ فقط شامل یک مورد است. برای دسترسی به مورد، باید روی نتیجه حلقه بزنید:

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 برای بازیابی داده‌های مرتب‌شده بر اساس کلید، مقدار یا مقدار یک فرزند استفاده کنید. همچنین می‌توانید نتیجه مرتب‌شده را به تعداد مشخصی از نتایج یا طیف وسیعی از کلیدها یا مقادیر فیلتر کنید.

مرتب‌سازی داده‌ها

برای بازیابی داده‌های مرتب‌شده، با مشخص کردن یکی از روش‌های order-by برای تعیین نحوه‌ی مرتب‌سازی نتایج شروع کنید:

روش کاربرد
orderByChild() نتایج را بر اساس مقدار یک کلید فرزند مشخص شده یا مسیر فرزند تو در تو مرتب می‌کند.
orderByKey() نتایج را بر اساس کلیدهای فرزند مرتب کنید.
orderByValue() نتایج را بر اساس مقادیر فرزند مرتب کنید.

شما فقط می‌توانید از یک متد order-by در یک زمان استفاده کنید. فراخوانی چندین باره‌ی یک متد order-by در یک query باعث ایجاد خطا می‌شود.

مثال زیر نشان می‌دهد که چگونه می‌توانید لیستی از برترین پست‌های یک کاربر را که بر اساس تعداد ستاره‌هایش مرتب شده‌اند، بازیابی کنید:

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

این یک کوئری تعریف می‌کند که وقتی با یک شنونده فرزند ترکیب می‌شود، کلاینت را با پست‌های کاربر از مسیر موجود در پایگاه داده بر اساس شناسه کاربری آنها، که بر اساس تعداد ستاره‌هایی که هر پست دریافت کرده است، مرتب شده است، همگام‌سازی می‌کند. این تکنیک استفاده از شناسه‌ها به عنوان کلیدهای اندیس، data fan out نامیده می‌شود، می‌توانید اطلاعات بیشتر در مورد آن را در Structure Your Database بخوانید.

فراخوانی متد 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() را برای محدود کردن نتایج به یک محدوده مشخص از مقادیر ترکیب کنید.

حتی وقتی فقط یک مورد منطبق برای کوئری وجود دارد، snapshot هنوز یک لیست است؛ فقط شامل یک مورد است. برای دسترسی به مورد، باید روی نتیجه حلقه بزنید:

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() برای تعیین محدودیت ۱۰۰ استفاده کنید، در ابتدا فقط تا ۱۰۰ فراخوانی onChildAdded() دریافت خواهید کرد. اگر کمتر از ۱۰۰ مورد در پایگاه داده Firebase خود ذخیره کرده‌اید، یک فراخوانی onChildAdded() برای هر مورد اجرا می‌شود.

با تغییر آیتم‌ها، برای آیتم‌هایی که وارد کوئری می‌شوند، فراخوانی‌های onChildAdded() و برای آیتم‌هایی که از آن حذف می‌شوند، فراخوانی‌های onChildRemoved() دریافت می‌کنید تا تعداد کل روی ۱۰۰ باقی بماند.

مثال زیر نشان می‌دهد که چگونه example blogging app یک کوئری برای بازیابی لیستی از ۱۰۰ پست اخیر همه کاربران تعریف می‌کند:

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);

این مثال فقط یک کوئری تعریف می‌کند، برای همگام‌سازی داده‌ها، به یک شنونده (listener) متصل نیاز دارد.

فیلتر بر اساس کلید یا مقدار

شما می‌توانید از startAt() ، startAfter() ، endAt() ، endBefore() و equalTo() برای انتخاب نقاط شروع، پایان و هم‌ارزی دلخواه برای کوئری‌ها استفاده کنید. این می‌تواند برای صفحه‌بندی داده‌ها یا یافتن مواردی با فرزندهایی که مقدار خاصی دارند مفید باشد.

نحوه مرتب سازی داده های پرس و جو

این بخش توضیح می‌دهد که چگونه داده‌ها بر اساس هر یک از متدهای order-by در کلاس Query مرتب می‌شوند.

orderByChild

هنگام استفاده از orderByChild() ، داده‌هایی که حاوی کلید فرزند مشخص شده هستند به صورت زیر مرتب می‌شوند:

  1. فرزندانی که مقدار null برای کلید فرزند مشخص شده دارند، در اولویت قرار می‌گیرند.
  2. فرزندانی که مقدار false برای کلید فرزند مشخص شده دارند، در مرحله بعد قرار می‌گیرند. اگر چندین فرزند مقدار false داشته باشند، بر اساس کلید به صورت لغوی مرتب می‌شوند.
  3. فرزندانی که مقدار true برای کلید فرزند مشخص شده دارند، در مرحله بعد قرار می‌گیرند. اگر چندین فرزند مقدار true داشته باشند، بر اساس کلید به صورت لغوی مرتب می‌شوند.
  4. فرزندانی که مقدار عددی دارند، در مرحله بعد قرار می‌گیرند و به ترتیب صعودی مرتب می‌شوند. اگر چندین فرزند برای گره فرزند مشخص شده، مقدار عددی یکسانی داشته باشند، بر اساس کلید مرتب می‌شوند.
  5. رشته‌ها بعد از اعداد می‌آیند و به صورت لغوی و به ترتیب صعودی مرتب می‌شوند. اگر چندین فرزند برای گره فرزند مشخص شده مقدار یکسانی داشته باشند، بر اساس کلید به صورت لغوی مرتب می‌شوند.
  6. اشیاء در آخر می‌آیند و از نظر لغوی بر اساس کلید به ترتیب صعودی مرتب می‌شوند.

orderByKey

هنگام استفاده از orderByKey() برای مرتب‌سازی داده‌ها، داده‌ها بر اساس کلید به صورت صعودی مرتب می‌شوند.

  1. فرزندانی که کلیدی دارند که می‌تواند به عنوان یک عدد صحیح ۳۲ بیتی تجزیه شود، ابتدا به صورت صعودی مرتب می‌شوند.
  2. فرزندانی که کلیدشان یک مقدار رشته‌ای است، در مرحله‌ی بعد قرار می‌گیرند و به صورت لغوی و به ترتیب صعودی مرتب شده‌اند.

orderByValue

هنگام استفاده از orderByValue() ، فرزندان بر اساس مقدارشان مرتب می‌شوند. معیارهای مرتب‌سازی مشابه orderByChild() هستند، با این تفاوت که به جای مقدار یک کلید فرزند مشخص، از مقدار گره استفاده می‌شود.

مراحل بعدی