אחזור נתונים באמצעות מסד נתונים בזמן אמת ב-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();

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

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

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

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

  // 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(), אבל במקום הערך של מפתח צאצא שצוין, נעשה שימוש בערך של הצומת.

השלבים הבאים