שמירת נתונים

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

כדי להשתמש ב-Realtime Database, צריך:

  • רושמים את הפרויקט ב-Unity ומגדירים אותו לשימוש ב-Firebase.

    • אם בפרויקט Unity שלכם כבר נעשה שימוש ב-Firebase, הוא כבר רשום ומותאם ל-Firebase.

    • אם אין לכם פרויקט Unity, אתם יכולים להוריד אפליקציה לדוגמה.

  • מוסיפים את Firebase Unity SDK (במיוחד את FirebaseDatabase.unitypackage) לפרויקט Unity.

שימו לב: הוספת Firebase לפרויקט ב-Unity כוללת משימות במסוף Firebase ובפרויקט הפתוח ב-Unity (לדוגמה, הורדה של קובצי תצורה של Firebase מהמסוף והעברה שלהם לפרויקט ב-Unity).

שמירת נתונים

יש חמש שיטות לכתיבת נתונים אל Firebase Realtime Database:

שיטה שימושים נפוצים
SetValueAsync() כתיבה או החלפה של נתונים בנתיב מוגדר, כמו users/<user-id>/<username>.
SetRawJsonValueAsync() כותבים או מחליפים נתונים באמצעות JSON גולמי, כמו users/<user-id>/<username>.
Push() להוסיף לרשימת נתונים. בכל פעם שמבצעים קריאה ל-Push(), ‏ Firebase יוצר מפתח ייחודי שיכול לשמש גם כמזהה ייחודי, כמו user-scores/<user-id>/<unique-score-id>.
UpdateChildrenAsync() עדכון חלק מהמפתחות של נתיב מוגדר בלי להחליף את כל הנתונים.
RunTransaction() עדכון נתונים מורכבים שעלולים להיפגם בגלל עדכונים בו-זמניים.

קבלת DatabaseReference

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

using Firebase;
using Firebase.Database;

public class MyScript: MonoBehaviour {
  void Start() {
    // Get the root reference location of the database.
    DatabaseReference reference = FirebaseDatabase.DefaultInstance.RootReference;
  }
}

כתיבה, עדכון או מחיקה של נתונים בהפניה

פעולות כתיבה בסיסיות

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

  • string
  • long
  • double
  • bool
  • Dictionary<string, Object>
  • List<Object>

אם משתמשים באובייקט C# מוקלד, אפשר להשתמש ב-JsonUtility.ToJson() המובנה כדי להמיר את האובייקט ל-Json גולמי ולקרוא ל-SetRawJsonValueAsync(). לדוגמה, יכול להיות שיש לכם מחלקה של משתמשים שנראית כך:

public class User {
    public string username;
    public string email;

    public User() {
    }

    public User(string username, string email) {
        this.username = username;
        this.email = email;
    }
}

כדי להוסיף משתמש עם SetRawJsonValueAsync():

private void writeNewUser(string userId, string name, string email) {
    User user = new User(name, email);
    string json = JsonUtility.ToJson(user);

    mDatabaseRef.Child("users").Child(userId).SetRawJsonValueAsync(json);
}

שימוש ב-SetValueAsync() או ב-SetRawJsonValueAsync() באופן הזה מחליף את הנתונים במיקום שצוין, כולל כל צמתי הצאצא. עם זאת, עדיין אפשר לעדכן ילד בלי לשכתב את כל האובייקט. אם רוצים לאפשר למשתמשים לעדכן את הפרופילים שלהם, אפשר לעדכן את שם המשתמש באופן הבא:

mDatabaseRef.Child("users").Child(userId).Child("username").SetValueAsync(name);

הוספה לרשימת נתונים

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

אפשר להשתמש בהפניה לנתונים החדשים שמוחזרים על ידי השיטה Push() כדי לקבל את הערך של המפתח שנוצר אוטומטית עבור הצאצא או להגדיר נתונים עבור הצאצא. הפעלת הפונקציה Key על הפניה Push() מחזירה את הערך של המפתח שנוצר אוטומטית.

עדכון שדות ספציפיים

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

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

public class LeaderboardEntry {
    public string uid;
    public int score = 0;

    public LeaderboardEntry() {
    }

    public LeaderboardEntry(string uid, int score) {
        this.uid = uid;
        this.score = score;
    }

    public Dictionary&ltstring, Object&gt ToDictionary() {
        Dictionary&ltstring, Object&gt result = new Dictionary&ltstring, Object&gt();
        result["uid"] = uid;
        result["score"] = score;

        return result;
    }
}

כדי ליצור LeaderboardEntry ולעדכן אותו בו-זמנית בפיד של הניקוד האחרון וברשימת הניקוד של המשתמש, המשחק משתמש בקוד כזה:

private void WriteNewScore(string userId, int score) {
    // Create new entry at /user-scores/$userid/$scoreid and at
    // /leaderboard/$scoreid simultaneously
    string key = mDatabase.Child("scores").Push().Key;
    LeaderBoardEntry entry = new LeaderBoardEntry(userId, score);
    Dictionary&ltstring, Object&gt entryValues = entry.ToDictionary();

    Dictionary&ltstring, Object&gt childUpdates = new Dictionary&ltstring, Object&gt();
    childUpdates["/scores/" + key] = entryValues;
    childUpdates["/user-scores/" + userId + "/" + key] = entryValues;

    mDatabase.UpdateChildrenAsync(childUpdates);
}

בדוגמה הזו נעשה שימוש ב-Push() כדי ליצור רשומה בצומת שמכילה רשומות של כל המשתמשים ב-/scores/$key, ובמקביל לאחזר את המפתח באמצעות Key. אחר כך אפשר להשתמש במפתח כדי ליצור רשומה שנייה בניקוד של המשתמש בכתובת /user-scores/$userid/$key.

באמצעות הנתיבים האלה, אפשר לבצע עדכונים בו-זמנית בכמה מיקומים בעץ ה-JSON באמצעות קריאה אחת ל-UpdateChildrenAsync(), כמו בדוגמה הזו שבה נוצרת הרשומה החדשה בשני המיקומים. עדכונים שמתבצעים בו-זמנית בדרך הזו הם אטומיים: או שכל העדכונים מצליחים או שכולם נכשלים.

מחיקת נתונים

הדרך הפשוטה ביותר למחוק נתונים היא לקרוא ל-RemoveValue() בהפניה למיקום של הנתונים האלה.

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

לדעת מתי הנתונים נשמרים.

כדי לדעת מתי הנתונים שלכם מועברים לשרת Firebase Realtime Database, אתם יכולים להוסיף המשך. הפונקציות SetValueAsync() ו-UpdateChildrenAsync() מחזירות את הערך Task, שמאפשר לדעת מתי הפעולה הושלמה. אם הקריאה נכשלת מסיבה כלשהי, הערך של Tasks IsFaulted יהיה true, והמאפיין Exception יציין את הסיבה לכשל.

שמירת נתונים כעסקאות

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

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

private void AddScoreToLeaders(string email, 
                               long score,
                               DatabaseReference leaderBoardRef) {

    leaderBoardRef.RunTransaction(mutableData =&gt {
      List&ltobject&gt leaders = mutableData.Value as List&ltobject>

      if (leaders == null) {
        leaders = new List&ltobject&gt();
      } else if (mutableData.ChildrenCount &gt= MaxScores) {
        long minScore = long.MaxValue;
        object minVal = null;
        foreach (var child in leaders) {
          if (!(child is Dictionary&ltstring, object&gt)) continue;
          long childScore = (long)
                      ((Dictionary&ltstring, object&gt)child)["score"];
          if (childScore &lt minScore) {
            minScore = childScore;
            minVal = child;
          }
        }
        if (minScore &gt score) {
          // The new score is lower than the existing 5 scores, abort.
          return TransactionResult.Abort();
        }

        // Remove the lowest score.
        leaders.Remove(minVal);
      }

      // Add the new high score.
      Dictionary&ltstring, object&gt newScoreMap =
                       new Dictionary&ltstring, object&gt();
      newScoreMap["score"] = score;
      newScoreMap["email"] = email;
      leaders.Add(newScoreMap);
      mutableData.Value = leaders;
      return TransactionResult.Success(mutableData);
    });
}

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

כתיבת נתונים במצב אופליין

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

לכל לקוח שמחובר למסד נתונים של Firebase יש גרסה פנימית משלו של כל הנתונים הפעילים. כשנתונים נכתבים, הם נכתבים קודם לגרסה המקומית הזו. לאחר מכן, לקוח Firebase מסנכרן את הנתונים האלה עם שרתי מסד הנתונים המרוחק ועם לקוחות אחרים על בסיס 'המאמץ הטוב ביותר'.

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

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

השלבים הבאים