אחזור נתונים באמצעות מסד נתונים בזמן אמת ב-Firebase עבור C++

במאמר הזה נסביר את העקרונות הבסיסיים של אחזור נתונים ואיך לסדר ולסנן נתונים ב-Firebase.

לפני שמתחילים

מוודאים שהגדרתם את האפליקציה ואתם יכולים לגשת למסד הנתונים, כמו שמוסבר במדריך Get Started.

אחזור נתונים

כדי לאחזר נתונים מ-Firebase, צריך לבצע קריאה חד-פעמית ל-GetValue() או לצרף ל-ValueListener בהפניה ל-FirebaseDatabase. המאזין לערך נקרא פעם אחת למצב הראשוני של הנתונים, ופעם נוספת בכל פעם שהנתונים משתנים.

קבלת DatabaseReference

כדי לכתוב נתונים במסד הנתונים, צריך מופע של DatabaseReference:

    // Get the root reference location of the database.
    firebase::database::DatabaseReference dbref = database->GetReference();

קריאת נתונים פעם אחת

אפשר להשתמש ב-method‏ GetValue() כדי לקרוא פעם אחת תמונת מצב סטטית של התוכן בנתיב נתון. תוצאת המשימה תכיל תמונת מצב שכוללת את כל הנתונים במיקום הזה, כולל נתוני הצאצאים. אם אין נתונים, תמונת המצב שמוחזרת היא null.

  firebase::Future&ltfirebase::database::DataSnapshot&gt result =
    dbRef.GetReference("Leaders").GetValue();

בשלב הזה הבקשה נשלחה, אבל אנחנו צריכים לחכות שה-Future יסתיים לפני שנוכל לקרוא את הערך. בדרך כלל משחקים פועלים בלולאה, והם פחות מבוססים על קריאות חוזרות (callbacks) מאשר אפליקציות אחרות, לכן בדרך כלל תצטרכו לבצע סקר כדי לבדוק אם הפעולה הושלמה.

  // In the game loop that polls for the result...

  if (result.status() != firebase::kFutureStatusPending) {
    if (result.status() != firebase::kFutureStatusComplete) {
      LogMessage("ERROR: GetValue() returned an invalid result.");
      // Handle the error...
    } else if (result.error() != firebase::database::kErrorNone) {
      LogMessage("ERROR: GetValue() returned error %d: %s", result.error(),
                 result.error_message());
      // Handle the error...
    } else {
      firebase::database::DataSnapshot snapshot = result.result();
      // Do something with the snapshot...
    }
  }

כאן מוצגת בדיקת שגיאות בסיסית. למידע נוסף על בדיקת שגיאות ועל דרכים לקבוע מתי התוצאה מוכנה, עיינו בחומר העזר בנושא firebase::Future.

האזנה לאירועים

אתם יכולים להוסיף מאזינים כדי להירשם לשינויים בנתונים:

מחלקה בסיסית ValueListener

התקשרות חזרה שימוש אופייני
OnValueChanged קריאה וחיפוש של שינויים בכל התוכן של נתיב.

מחלקה בסיסית OnChildListener

OnChildAdded אחזור רשימות של פריטים או האזנה לתוספות לרשימת פריטים. מומלץ להשתמש עם OnChildChanged ו-OnChildRemoved כדי לעקוב אחרי שינויים ברשימות.
OnChildChanged האזנה לשינויים בפריטים ברשימה. אפשר להשתמש ב-OnChildAdded וב-OnChildRemoved כדי לעקוב אחרי שינויים ברשימות.
OnChildRemoved האזנה לפריטים שמוסרים מרשימה. אפשר להשתמש ב-OnChildAdded וב-OnChildChanged כדי לעקוב אחרי שינויים ברשימות.
OnChildMoved האזנה לשינויים בסדר הפריטים ברשימה ממוינת. הקריאות החוזרות (callbacks) של OnChildMoved תמיד מגיעות אחרי הקריאות החוזרות של OnChildChanged, כי הסדר של הפריט משתנה (בהתאם לשיטת הסידור הנוכחית).

ValueListener class

אתם יכולים להשתמש בקריאות החוזרות (callbacks) של OnValueChanged כדי להירשם לשינויים בתוכן בנתיב נתון. הקריאה החוזרת הזו מופעלת פעם אחת כשהמאזין מצורף, ושוב בכל פעם שהנתונים משתנים, כולל הצאצאים. הקריאה החוזרת (callback) מקבלת תמונת מצב שמכילה את כל הנתונים במיקום הזה, כולל נתוני הצאצאים. אם אין נתונים, תמונת המצב שמוחזרת היא null.

בדוגמה הבאה אפשר לראות איך משחק מאחזר את הניקוד של טבלת המובילים ממסד הנתונים:

  class LeadersValueListener : public firebase::database::ValueListener {
   public:
    void OnValueChanged(
        const firebase::database::DataSnapshot& snapshot) override {
      // Do something with the data in snapshot...
    }
    void OnCancelled(const firebase::database::Error& error_code,
                     const char* error_message) override {
      LogMessage("ERROR: LeadersValueListener canceled: %d: %s", error_code,
                 error_message);
    }
  };

  // Elsewhere in the code...

  LeadersValueListener* listener = new LeadersValueListener();
  firebase::Future&ltfirebase::database::DataSnapshot&gt result =
    dbRef.GetReference("Leaders").AddValueListener(listener);

התוצאה Future&ltDataSnapshot&gt מכילה את הנתונים במיקום שצוין במסד הנתונים בזמן האירוע. קריאה ל-value() בתמונת מצב מחזירה Variant שמייצג את הנתונים.

בדוגמה הזו, השיטה OnCancelled גם מבוטלת כדי לבדוק אם הקריאה בוטלה. לדוגמה, פעולת קריאה יכולה להתבטל אם ללקוח אין הרשאה לקרוא ממיקום של מסד נתונים ב-Firebase. השדה database::Error יציין את הסיבה לכשל.

הכיתה ChildListener

אירועים משניים מופעלים בתגובה לפעולות ספציפיות שמתרחשות בצאצאים של צומת, כמו הוספת צאצא חדש באמצעות השיטה PushChild() או עדכון של צאצא באמצעות השיטה UpdateChildren(). כל אחד מהם יכול להיות שימושי להאזנה לשינויים בצומת ספציפי במסד נתונים. לדוגמה, במשחק מסוים אפשר להשתמש בשיטות האלה ביחד כדי לעקוב אחרי הפעילות בתגובות של סשן משחק, כמו שמוצג בהמשך:

  class SessionCommentsChildListener : public firebase::database::ChildListener {
   public:
    void OnChildAdded(const firebase::database::DataSnapshot& snapshot,
                      const char* previous_sibling) override {
      // Do something with the data in snapshot ...
    }
    void OnChildChanged(const firebase::database::DataSnapshot& snapshot,
                        const char* previous_sibling) override {
      // Do something with the data in snapshot ...
    }
    void OnChildRemoved(
        const firebase::database::DataSnapshot& snapshot) override {
      // Do something with the data in snapshot ...
    }
    void OnChildMoved(const firebase::database::DataSnapshot& snapshot,
                      const char* previous_sibling) override {
      // Do something with the data in snapshot ...
    }
    void OnCancelled(const firebase::database::Error& error_code,
                     const char* error_message) override {
      LogMessage("ERROR: SessionCommentsChildListener canceled: %d: %s",
                 error_code, error_message);
    }
  };

  // elsewhere ....

  SessionCommentsChildListener* listener = new SessionCommentsChildListener();
  firebase::Future&ltfirebase::database::DataSnapshot&gt result =
    dbRef.GetReference("GameSessionComments").AddChildListener(listener);

בדרך כלל משתמשים בקריאה החוזרת (callback) OnChildAdded כדי לאחזר רשימה של פריטים במסד נתונים של Firebase. הקריאה החוזרת (callback) של OnChildAdded מתבצעת פעם אחת לכל צאצא קיים, ואז שוב בכל פעם שנוסף צאצא חדש לנתיב שצוין. המאזין מקבל תמונת מצב שמכילה את הנתונים של הצאצא החדש.

הקריאה החוזרת (callback) של OnChildChanged מתבצעת בכל פעם שצומת משני משתנה. זה כולל כל שינוי בצאצאים של צומת הצאצא. בדרך כלל משתמשים בו בשילוב עם הקריאות OnChildAdded ו-OnChildRemoved כדי להגיב לשינויים ברשימת פריטים. תמונת המצב שמועברת למאזין מכילה את הנתונים המעודכנים של הצאצא.

הקריאה החוזרת (callback) של OnChildRemoved מופעלת כשמסירים צאצא מיידי. בדרך כלל משתמשים בו בשילוב עם פונקציות ה-callback‏ OnChildAdded ו-OnChildChanged. תמונת המצב שמועברת לקריאה החוזרת מכילה את הנתונים של הצאצא שהוסר.

הקריאה החוזרת (callback) של OnChildMoved מופעלת בכל פעם שהקריאה ל-OnChildChanged נוצרת על ידי עדכון שגורם לסידור מחדש של הצאצא. הוא משמש לנתונים שמסודרים לפי OrderByChild או OrderByValue.

מיון וסינון נתונים

אפשר להשתמש במחלקה Realtime Database Query כדי לאחזר נתונים שממוינים לפי מפתח, לפי ערך או לפי ערך של צאצא. אפשר גם לסנן את התוצאה הממוינת למספר ספציפי של תוצאות או לטווח של מפתחות או ערכים.

מיון נתונים

כדי לאחזר נתונים ממוינים, קודם צריך לציין את אחת משיטות הסידור כדי לקבוע איך התוצאות יסודרו:

שיטה שימוש
OrderByChild() סידור התוצאות לפי הערך של מפתח צאצא שצוין.
OrderByKey() סידור התוצאות לפי מפתחות צאצא.
OrderByValue() סידור התוצאות לפי ערכי הצאצאים.

אפשר להשתמש רק בשיטת סידור אחת בכל פעם. אם קוראים לשיטת הסידור כמה פעמים באותה שאילתה, מתקבלת שגיאה.

בדוגמה הבאה אפשר לראות איך נרשמים לטבלת ניקוד שממוינת לפי הניקוד.

  firebase::database::Query query =
    dbRef.GetReference("Leaders").OrderByChild("score");

  // To get the resulting DataSnapshot either use query.GetValue() and poll the
  // future, or use query.AddValueListener() and register to handle the
  // OnValueChanged callback.

כך מוגדרת firebase::Query שכאשר משלבים אותה עם ValueListener, היא מסנכרנת את הלקוח עם טבלת המובילים במסד הנתונים, לפי הניקוד של כל רשומה. במאמר Structure Your Database תוכלו לקרוא מידע נוסף על בניית מבנה יעיל לנתונים.

הקריאה ל-method‏ OrderByChild() מציינת את מפתח הצאצא שלפיו יסודרו התוצאות. במקרה הזה, התוצאות ממוינות לפי הערך של "score" בכל צאצא. במאמר איך מסודרים נתוני השאילתה מוסבר איך מסודרים סוגי נתונים אחרים.

סינון נתונים

כדי לסנן נתונים, אפשר לשלב כל אחת משיטות ההגבלה או הטווח עם שיטת הסידור כשיוצרים שאילתה.

שיטה שימוש
LimitToFirst() מגדיר את המספר המקסימלי של פריטים שיוחזרו מתחילת רשימת התוצאות המסודרת.
LimitToLast() מגדירה את המספר המקסימלי של פריטים שיוחזרו מסוף רשימת התוצאות המסודרת.
StartAt() החזרת פריטים שגדולים מהמפתח או מהערך שצוינו או שווים להם, בהתאם לשיטת הסידור שנבחרה.
EndAt() החזרת פריטים שקטנים מהמפתח או מהערך שצוינו או שווים להם, בהתאם לשיטת הסידור שנבחרה.
EqualTo() הפונקציה מחזירה פריטים ששווים למפתח או לערך שצוינו, בהתאם לשיטת הסידור שנבחרה.

בניגוד לשיטות הסידור, אפשר לשלב כמה פונקציות של הגבלה או טווח. לדוגמה, אפשר לשלב את השיטות StartAt() ו-EndAt() כדי להגביל את התוצאות לטווח ערכים מסוים.

גם אם יש רק התאמה אחת לשאילתה, תמונת המצב עדיין תהיה רשימה, אבל היא תכיל רק פריט אחד.

להגביל את מספר התוצאות

אפשר להשתמש בשיטות LimitToFirst() ו-LimitToLast() כדי להגדיר את המספר המקסימלי של צאצאים שיסונכרנו בקריאה חוזרת (callback) נתונה. לדוגמה, אם אתם משתמשים ב-LimitToFirst() כדי להגדיר מגבלה של 100, בהתחלה תקבלו עד 100 קריאות חוזרות (callbacks) של OnChildAdded. אם יש פחות מ-100 פריטים במסד הנתונים של Firebase, מופעלת קריאה חוזרת (callback) מסוג OnChildAdded לכל פריט.

כשהפריטים משתנים, אתם מקבלים קריאות חוזרות (callbacks) מסוג OnChildAdded לפריטים שנכנסים לשאילתה וקריאות חוזרות מסוג OnChildRemoved לפריטים שיוצאים ממנה, כך שהמספר הכולל נשאר 100.

לדוגמה, הקוד הבא מחזיר את הניקוד הגבוה ביותר מטבלת המובילים:

  firebase::database::Query query =
    dbRef.GetReference("Leaders").OrderByChild("score").LimitToLast(1);

  // To get the resulting DataSnapshot either use query.GetValue() and poll the
  // future, or use query.AddValueListener() and register to handle the
  // OnValueChanged callback.

סינון לפי מפתח או ערך

אפשר להשתמש ב-StartAt(), ב-EndAt() וב-EqualTo() כדי לבחור נקודות התחלה, סיום ושוויון שרירותיות לשאילתות. האפשרות הזו יכולה להיות שימושית להוספת מספרי עמודים לנתונים או למציאת פריטים עם צאצאים שיש להם ערך ספציפי.

איך נתוני השאילתות מסודרים

בקטע הזה מוסבר איך הנתונים ממוינים לפי כל אחת משיטות הסידור במחלקה Query.

OrderByChild

כשמשתמשים ב-OrderByChild(), הנתונים שמכילים את מפתח הצאצא שצוין מסודרים באופן הבא:

  1. ילדים עם ערך null למפתח הצאצא שצוין מופיעים ראשונים.
  2. אחריהם מופיעים צאצאים עם הערך false במפתח הצאצא שצוין. אם לכמה צאצאים יש ערך של false, הם ממוינים לקסיקוגרפית לפי מפתח.
  3. אחריהם מופיעים צאצאים עם הערך true במפתח הצאצא שצוין. אם לכמה צאצאים יש ערך של true, הם ממוינים לפי סדר לקסיקוגרפי לפי מפתח.
  4. אחריהם מופיעים צאצאים עם ערך מספרי, בסדר עולה. אם לכמה צאצאים יש אותו ערך מספרי בצומת הצאצא שצוין, הם ממוינים לפי מפתח.
  5. מחרוזות מופיעות אחרי מספרים וממוינות בסדר לקסיקוגרפי עולה. אם לכמה צאצאים יש את אותו ערך בצומת הצאצא שצוין, הם מסודרים לפי סדר לקסיקוגרפי לפי מפתח.
  6. האובייקטים מופיעים בסוף וממוינים בסדר לקסיקוגרפי לפי מפתח בסדר עולה.

OrderByKey

כשמשתמשים ב-OrderByKey() כדי למיין את הנתונים, הנתונים מוחזרים בסדר עולה לפי מפתח.

  1. הצאצאים עם מפתח שאפשר לנתח כמספר שלם של 32 ביט מופיעים קודם, בסדר עולה.
  2. אחריהם מופיעים צאצאים עם ערך מחרוזת כמפתח, ממוינים בסדר לקסיקוגרפי עולה.

OrderByValue

כשמשתמשים ב-OrderByValue(), הצאצאים מסודרים לפי הערך שלהם. הקריטריונים לסידור זהים לאלה של OrderByChild(), אבל נעשה שימוש בערך של הצומת במקום בערך של מפתח צאצא שצוין.

השלבים הבאים