Catch up on everything we announced at this year's Firebase Summit. Learn more

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

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

נתוני Firebase נכתבים FirebaseDatabase התייחסות לאחזר באמצעות הצמדת מאזין אסינכרוני להתייחסות. המאזין מופעל פעם אחת למצב הראשוני של הנתונים ושוב בכל פעם שהנתונים משתנים.

(אופציונלי) אב טיפוס ובדיקה עם חבילת אמולטור מקומית של Firebase

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

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

השימוש באמולטור מסד הנתונים בזמן אמת כרוך במספר שלבים בלבד:

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

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

קבל DatabaseReference

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

ג'אווה

private DatabaseReference mDatabase;
// ...
mDatabase = FirebaseDatabase.getInstance().getReference();

Kotlin+KTX

private lateinit var database: DatabaseReference
// ...
database = Firebase.database.reference

כתוב נתונים

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

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

  • סוגי מעבר המתאימים לסוגי JSON הזמינים כדלקמן:
    • String
    • Long
    • Double
    • Boolean
    • Map<String, Object>
    • List<Object>
  • העבר אובייקט Java מותאם אישית, אם למחלקה המגדירה אותו יש קונסטרוקטור ברירת מחדל שאינו דורש ארגומנטים ויש לו גטרים ציבוריים למקצות שיוקצו.

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

ג'אווה

@IgnoreExtraProperties
public class User {

    public String username;
    public String email;

    public User() {
        // Default constructor required for calls to DataSnapshot.getValue(User.class)
    }

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

}

Kotlin+KTX

@IgnoreExtraProperties
data class User(val username: String? = null, val email: String? = null) {
    // Null default values create a no-argument default constructor, which is needed
    // for deserialization from a DataSnapshot.
}

אתה יכול להוסיף משתמש עם setValue() כדלקמן:

ג'אווה

public void writeNewUser(String userId, String name, String email) {
    User user = new User(name, email);

    mDatabase.child("users").child(userId).setValue(user);
}

Kotlin+KTX

fun writeNewUser(userId: String, name: String, email: String) {
    val user = User(name, email)

    database.child("users").child(userId).setValue(user)
}

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

ג'אווה

mDatabase.child("users").child(userId).child("username").setValue(name);

Kotlin+KTX

database.child("users").child(userId).child("username").setValue(name)

קרא נתונים

קרא נתונים עם מאזינים מתמידים

כדי לקרוא נתונים על נתיב ולהקשיב לשינויים, להשתמש addValueEventListener() שיטה להוסיף ValueEventListener על DatabaseReference .

מַאֲזִין התקשרות חזרה לאירוע שימוש אופייני
ValueEventListener onDataChange() קרא והקשב לשינויים בתוכן הנתיב כולו.

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

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

ג'אווה

ValueEventListener postListener = new ValueEventListener() {
    @Override
    public void onDataChange(DataSnapshot dataSnapshot) {
        // Get Post object and use the values to update the UI
        Post post = dataSnapshot.getValue(Post.class);
        // ..
    }

    @Override
    public void onCancelled(DatabaseError databaseError) {
        // Getting Post failed, log a message
        Log.w(TAG, "loadPost:onCancelled", databaseError.toException());
    }
};
mPostReference.addValueEventListener(postListener);

Kotlin+KTX

val postListener = object : ValueEventListener {
    override fun onDataChange(dataSnapshot: DataSnapshot) {
        // Get Post object and use the values to update the UI
        val post = dataSnapshot.getValue<Post>()
        // ...
    }

    override fun onCancelled(databaseError: DatabaseError) {
        // Getting Post failed, log a message
        Log.w(TAG, "loadPost:onCancelled", databaseError.toException())
    }
}
postReference.addValueEventListener(postListener)

המאזין מקבל DataSnapshot המכיל את הנתונים במיקום שצוין באתר בעת האירוע. שיחות getValue() על תשואות תמונת מצב ייצוג אובייקט Java של הנתונים. אם לא קיימים נתוני המיקום, קוראים getValue() מחזירה null .

בדוגמה זו, ValueEventListener גם מגדיר את onCancelled() שיטה הנקראת אם לקרוא מתבטלת. לדוגמה, קריאה ניתנת לביטול אם ללקוח אין הרשאה לקרוא ממיקום מסד נתונים של Firebase. שיטה זו מועברת DatabaseError אובייקט המציין מדוע אירעה התקלה.

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

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

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

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

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

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

ג'אווה

mDatabase.child("users").child(userId).get().addOnCompleteListener(new OnCompleteListener<DataSnapshot>() {
    @Override
    public void onComplete(@NonNull Task<DataSnapshot> task) {
        if (!task.isSuccessful()) {
            Log.e("firebase", "Error getting data", task.getException());
        }
        else {
            Log.d("firebase", String.valueOf(task.getResult().getValue()));
        }
    }
});

Kotlin+KTX

mDatabase.child("users").child(userId).get().addOnSuccessListener {
    Log.i("firebase", "Got value ${it.value}")
}.addOnFailureListener{
    Log.e("firebase", "Error getting data", it)
}

קראו פעם אחת באמצעות מאזין

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

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

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

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

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

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

ג'אווה

@IgnoreExtraProperties
public class Post {

    public String uid;
    public String author;
    public String title;
    public String body;
    public int starCount = 0;
    public Map<String, Boolean> stars = new HashMap<>();

    public Post() {
        // Default constructor required for calls to DataSnapshot.getValue(Post.class)
    }

    public Post(String uid, String author, String title, String body) {
        this.uid = uid;
        this.author = author;
        this.title = title;
        this.body = body;
    }

    @Exclude
    public Map<String, Object> toMap() {
        HashMap<String, Object> result = new HashMap<>();
        result.put("uid", uid);
        result.put("author", author);
        result.put("title", title);
        result.put("body", body);
        result.put("starCount", starCount);
        result.put("stars", stars);

        return result;
    }
}

Kotlin+KTX

@IgnoreExtraProperties
data class Post(
    var uid: String? = "",
    var author: String? = "",
    var title: String? = "",
    var body: String? = "",
    var starCount: Int = 0,
    var stars: MutableMap<String, Boolean> = HashMap()
) {

    @Exclude
    fun toMap(): Map<String, Any?> {
        return mapOf(
                "uid" to uid,
                "author" to author,
                "title" to title,
                "body" to body,
                "starCount" to starCount,
                "stars" to stars
        )
    }
}

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

ג'אווה

private void writeNewPost(String userId, String username, String title, String body) {
    // Create new post at /user-posts/$userid/$postid and at
    // /posts/$postid simultaneously
    String key = mDatabase.child("posts").push().getKey();
    Post post = new Post(userId, username, title, body);
    Map<String, Object> postValues = post.toMap();

    Map<String, Object> childUpdates = new HashMap<>();
    childUpdates.put("/posts/" + key, postValues);
    childUpdates.put("/user-posts/" + userId + "/" + key, postValues);

    mDatabase.updateChildren(childUpdates);
}

Kotlin+KTX

private fun writeNewPost(userId: String, username: String, title: String, body: String) {
    // Create new post at /user-posts/$userid/$postid and at
    // /posts/$postid simultaneously
    val key = database.child("posts").push().key
    if (key == null) {
        Log.w(TAG, "Couldn't get push key for posts")
        return
    }

    val post = Post(userId, username, title, body)
    val postValues = post.toMap()

    val childUpdates = hashMapOf<String, Any>(
            "/posts/$key" to postValues,
            "/user-posts/$userId/$key" to postValues
    )

    database.updateChildren(childUpdates)
}

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

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

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

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

ג'אווה

mDatabase.child("users").child(userId).setValue(user)
        .addOnSuccessListener(new OnSuccessListener<Void>() {
            @Override
            public void onSuccess(Void aVoid) {
                // Write was successful!
                // ...
            }
        })
        .addOnFailureListener(new OnFailureListener() {
            @Override
            public void onFailure(@NonNull Exception e) {
                // Write failed
                // ...
            }
        });

Kotlin+KTX

database.child("users").child(userId).setValue(user)
        .addOnSuccessListener {
            // Write was successful!
            // ...
        }
        .addOnFailureListener {
            // Write failed
            // ...
        }

מחק נתונים

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

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

נתק מאזינים

התקשרויות חוזרות יוסרו על ידי קריאה removeEventListener() שיטה על הפנייה למאגר הנתונים Firebase שלך.

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

שיחות removeEventListener() על מאזין ההורה אינו מסיר אוטומטית המאזינים רשום על בלוטות הילד שלה; removeEventListener() חייב גם להיקרא על כול מאזיני ילד להסיר את ההתקשרות.

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

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

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

ג'אווה

private void onStarClicked(DatabaseReference postRef) {
    postRef.runTransaction(new Transaction.Handler() {
        @Override
        public Transaction.Result doTransaction(MutableData mutableData) {
            Post p = mutableData.getValue(Post.class);
            if (p == null) {
                return Transaction.success(mutableData);
            }

            if (p.stars.containsKey(getUid())) {
                // Unstar the post and remove self from stars
                p.starCount = p.starCount - 1;
                p.stars.remove(getUid());
            } else {
                // Star the post and add self to stars
                p.starCount = p.starCount + 1;
                p.stars.put(getUid(), true);
            }

            // Set value and report transaction success
            mutableData.setValue(p);
            return Transaction.success(mutableData);
        }

        @Override
        public void onComplete(DatabaseError databaseError, boolean committed,
                               DataSnapshot currentData) {
            // Transaction completed
            Log.d(TAG, "postTransaction:onComplete:" + databaseError);
        }
    });
}

Kotlin+KTX

private fun onStarClicked(postRef: DatabaseReference) {
    // ...
    postRef.runTransaction(object : Transaction.Handler {
        override fun doTransaction(mutableData: MutableData): Transaction.Result {
            val p = mutableData.getValue(Post::class.java)
                    ?: return Transaction.success(mutableData)

            if (p.stars.containsKey(uid)) {
                // Unstar the post and remove self from stars
                p.starCount = p.starCount - 1
                p.stars.remove(uid)
            } else {
                // Star the post and add self to stars
                p.starCount = p.starCount + 1
                p.stars[uid] = true
            }

            // Set value and report transaction success
            mutableData.value = p
            return Transaction.success(mutableData)
        }

        override fun onComplete(
                databaseError: DatabaseError?,
                committed: Boolean,
                currentData: DataSnapshot?
        ) {
            // Transaction completed
            Log.d(TAG, "postTransaction:onComplete:" + databaseError!!)
        }
    })
}

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

תוספות בצד השרת האטומי

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

ג'אווה

private void onStarClicked(String uid, String key) {
    Map<String, Object> updates = new HashMap<>();
    updates.put("posts/"+key+"/stars/"+uid, true);
    updates.put("posts/"+key+"/starCount", ServerValue.increment(1));
    updates.put("user-posts/"+uid+"/"+key+"/stars/"+uid, true);
    updates.put("user-posts/"+uid+"/"+key+"/starCount", ServerValue.increment(1));
    mDatabase.updateChildren(updates);
}

Kotlin+KTX

private fun onStarClicked(uid: String, key: String) {
    val updates: MutableMap<String, Any> = HashMap()
    updates["posts/$key/stars/$uid"] = true
    updates["posts/$key/starCount"] = ServerValue.increment(1)
    updates["user-posts/$uid/$key/stars/$uid"] = true
    updates["user-posts/$uid/$key/starCount"] = ServerValue.increment(1)
    database.updateChildren(updates)
}

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

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

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

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

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

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

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

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

הצעדים הבאים