עבודה עם רשימות נתונים באנדרואיד

מסמך זה עוסק בעבודה עם רשימות נתונים ב-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
    // ...
});

זה מגדיר שאילתה שבשילוב עם מאזין ילד מסנכרנת את הלקוח עם הפוסטים של המשתמש מהנתיב במסד הנתונים על פי זיהוי המשתמש שלו, לפי מספר הכוכבים שכל פוסט קיבל. טכניקה זו של שימוש במזהים כמפתחות אינדקס נקראת יציאת נתונים, תוכל לקרוא עליה עוד ב- 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+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() callbacks עבור פריטים שנפלטים ממנה כך שהמספר הכולל נשאר על 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() , אלא שהערך של הצומת משמש במקום הערך של מפתח צאצא שצוין.

הצעדים הבאים