קרא וכתוב נתונים

(אופציונלי) אב טיפוס ובדיקה עם Firebase Emulator Suite

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

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

השימוש באמולטור Realtime Database כולל רק כמה שלבים:

  1. הוספת שורת קוד לתצורת הבדיקה של האפליקציה שלך כדי להתחבר לאמולטור.
  2. מהשורש של ספריית הפרויקט המקומית שלך, הפעל firebase emulators:start .
  3. ביצוע שיחות מקוד אב הטיפוס של האפליקציה שלך באמצעות SDK של פלטפורמת Realtime Database כרגיל, או באמצעות Realtime Database REST API.

הדרכה מפורטת הכוללת מסד נתונים ופונקציות ענן בזמן אמת זמין. כדאי שתעיין גם במבוא של Emulator Suite .

קבל הפניית מסד נתונים

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

DatabaseReference ref = FirebaseDatabase.instance.ref();

כתוב נתונים

מסמך זה מכסה את היסודות של קריאה וכתיבה של נתוני Firebase.

נתוני Firebase נכתבים ל- DatabaseReference ומאוחזרים על ידי המתנה או האזנה לאירועים הנפלטים מההפניה. אירועים נפלטים פעם אחת עבור המצב הראשוני של הנתונים ושוב בכל פעם שהנתונים משתנים.

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

עבור פעולות כתיבה בסיסיות, אתה יכול להשתמש set() כדי לשמור נתונים בהפניה שצוינה, תוך החלפת כל הנתונים הקיימים בנתיב זה. אתה יכול להגדיר הפניה לסוגים הבאים: String , boolean , int , double , Map , List .

לדוגמה, אתה יכול להוסיף משתמש עם set() באופן הבא:

DatabaseReference ref = FirebaseDatabase.instance.ref("users/123");

await ref.set({
  "name": "John",
  "age": 18,
  "address": {
    "line1": "100 Mountain View"
  }
});

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

DatabaseReference ref = FirebaseDatabase.instance.ref("users/123");

// Only update the name, leave the age and address!
await ref.update({
  "age": 19,
});

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

DatabaseReference ref = FirebaseDatabase.instance.ref("users");

await ref.update({
  "123/age": 19,
  "123/address/line1": "1 Mountain View",
});

קרא נתונים

קרא נתונים על ידי האזנה לאירועים ערכיים

כדי לקרוא נתונים בנתיב ולהאזין לשינויים, השתמש במאפיין onValue של DatabaseReference כדי להאזין ל- DatabaseEvent s.

אתה יכול להשתמש ב- DatabaseEvent כדי לקרוא את הנתונים בנתיב נתון, כפי שהוא קיים בזמן האירוע. אירוע זה מופעל פעם אחת כאשר המאזין מחובר ושוב בכל פעם שהנתונים, כולל ילדים כלשהם, משתנים. לאירוע יש מאפיין snapshot המכיל את כל הנתונים במיקום זה, כולל נתוני צאצא. אם אין נתונים, המאפיין exists של תמונת המצב יהיה false ומאפיין value שלו יהיה null.

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

DatabaseReference starCountRef =
        FirebaseDatabase.instance.ref('posts/$postId/starCount');
starCountRef.onValue.listen((DatabaseEvent event) {
    final data = event.snapshot.value;
    updateStarCount(data);
});

המאזין מקבל DataSnapshot שמכיל את הנתונים במיקום שצוין במסד הנתונים בזמן האירוע במאפיין value שלו.

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

קרא פעם אחת באמצעות get()

ה-SDK נועד לנהל אינטראקציות עם שרתי מסד נתונים בין אם האפליקציה שלך מקוונת או לא מקוונת.

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

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

הדוגמה הבאה מדגימה אחזור של שם משתמש הפונה לציבור של משתמש פעם אחת ממסד הנתונים:

final ref = FirebaseDatabase.instance.ref();
final snapshot = await ref.child('users/$userId').get();
if (snapshot.exists) {
    print(snapshot.value);
} else {
    print('No data available.');
}

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

קרא נתונים פעם אחת עם once()

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

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

final event = await ref.once(DatabaseEventType.value);
final username = event.snapshot.value?.username ?? 'Anonymous';

עדכון או מחיקת נתונים

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

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

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

void writeNewPost(String uid, String username, String picture, String title,
        String body) async {
    // A post entry.
    final postData = {
        'author': username,
        'uid': uid,
        'body': body,
        'title': title,
        'starCount': 0,
        'authorPic': picture,
    };

    // Get a key for a new Post.
    final newPostKey =
        FirebaseDatabase.instance.ref().child('posts').push().key;

    // Write the new post's data simultaneously in the posts list and the
    // user's post list.
    final Map<String, Map> updates = {};
    updates['/posts/$newPostKey'] = postData;
    updates['/user-posts/$uid/$newPostKey'] = postData;

    return FirebaseDatabase.instance.ref().update(updates);
}

דוגמה זו משתמשת push() כדי ליצור פוסט בצומת המכיל פוסטים עבור כל המשתמשים ב- /posts/$postid ובו-זמנית לאחזר את המפתח עם key . לאחר מכן ניתן להשתמש במפתח ליצירת ערך שני בפוסטים של המשתמש ב- /user-posts/$userid/$postid .

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

הוסף התקשרות חזרה להשלמה

אם אתה רוצה לדעת מתי הנתונים שלך בוצעו, אתה יכול לרשום התקשרות חוזרת של השלמה. גם set() וגם update() מחזירים Future s, אליהם ניתן לצרף התקשרויות הצלחה ושגיאות שנקראות כאשר הכתיבה בוצעה במסד הנתונים וכאשר הקריאה לא הצליחה.

FirebaseDatabase.instance
    .ref('users/$userId/email')
    .set(emailAddress)
    .then((_) {
        // Data saved successfully!
    })
    .catchError((error) {
        // The write failed...
    });

מחק נתונים

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

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

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

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

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

void toggleStar(String uid) async {
  DatabaseReference postRef =
      FirebaseDatabase.instance.ref("posts/foo-bar-123");

  TransactionResult result = await postRef.runTransaction((Object? post) {
    // Ensure a post at the ref exists.
    if (post == null) {
      return Transaction.abort();
    }

    Map<String, dynamic> _post = Map<String, dynamic>.from(post as Map);
    if (_post["stars"] is Map && _post["stars"][uid] != null) {
      _post["starCount"] = (_post["starCount"] ?? 1) - 1;
      _post["stars"][uid] = null;
    } else {
      _post["starCount"] = (_post["starCount"] ?? 0) + 1;
      if (!_post.containsKey("stars")) {
        _post["stars"] = {};
      }
      _post["stars"][uid] = true;
    }

    // Return the new data.
    return Transaction.success(_post);
  });
}

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

await ref.runTransaction((Object? post) {
  // ...
}, applyLocally: false);

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

DatabaseReference ref = FirebaseDatabase.instance.ref("posts/123");

TransactionResult result = await ref.runTransaction((Object? post) {
  // ...
});

print('Committed? ${result.committed}'); // true / false
print('Snapshot? ${result.snapshot}'); // DataSnapshot

ביטול עסקה

אם אתה רוצה לבטל בבטחה עסקה, התקשר ל- Transaction.abort() כדי לזרוק AbortTransactionException :

TransactionResult result = await ref.runTransaction((Object? user) {
  if (user !== null) {
    return Transaction.abort();
  }

  // ...
});

print(result.committed); // false

מרווחים בצד השרת האטומי

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

void addStar(uid, key) async {
  Map<String, Object?> updates = {};
  updates["posts/$key/stars/$uid"] = true;
  updates["posts/$key/starCount"] = ServerValue.increment(1);
  updates["user-posts/$key/stars/$uid"] = true;
  updates["user-posts/$key/starCount"] = ServerValue.increment(1);
  return FirebaseDatabase.instance.ref().update(updates);
}

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

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

עבודה עם נתונים במצב לא מקוון

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

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

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

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

נדבר יותר על התנהגות לא מקוונת במידע נוסף על יכולות מקוונות ולא מקוונות .

הצעדים הבאים