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

حتی وقتی فقط یک مورد منطبق برای پُرسمان وجود دارد، عکس آنی همچنان به‌صورت فهرست است؛ فقط یک مورد دارد. برای دسترسی به مورد، باید نتیجه را تکرار کنید:

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() نتایج را براساس مقادیر فرزند مرتب کنید.

در هر زمان فقط می‌توانید از یک روش به‌ترتیب کردن استفاده کنید. فراخوانی روش به‌ترتیب کردن به‌دفعات در یک پُرسمان باعث ایجاد خطا می‌شود.

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

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

این یک پُرسمان را تعریف می‌کند که وقتی با یک شنونده فرزند ترکیب می‌شود، کارخواه را با پست‌های کاربر از مسیر موجود در پایگاه داده براساس شناسه کاربر آن‌ها همگام‌سازی می‌کند، که براساس تعداد ستاره‌هایی که هر پست دریافت کرده است مرتب شده‌اند. این تکنیک استفاده از شناسه‌ها به‌عنوان کلیدهای نمایه، «پخش داده» نامیده می‌شود، می‌توانید اطلاعات بیشتر درباره آن را در ساختار پایگاه داده بخوانید.

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

حتی وقتی فقط یک مورد منطبق برای پُرسمان وجود دارد، خلاصه همچنان یک فهرست است؛ فقط یک مورد دارد. برای دسترسی به مورد، باید نتیجه را تکرار کنید:

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() فراخوانی برگشتی برای مواردی که از آن خارج می‌شوند دریافت می‌کنید تا تعداد کل روی ۱۰۰ باقی بماند.

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

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. فرزندان با کلیدی که می‌تواند به‌عنوان عدد صحیح ۳۲ بیتی تجزیه شود، ابتدا به‌صورت صعودی مرتب می‌شوند.
  2. فرزندانی که مقدار رشته‌ای به‌عنوان کلید دارند در ردیف بعدی قرار می‌گیرند و به‌ترتیب صعودی واژه‌نامه‌ای مرتب می‌شوند.

orderByValue

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

مراحل بعدی