Trang này được dịch bởi Cloud Translation API.
Switch to English

Đọc và ghi dữ liệu trên Android

Tài liệu này bao gồm các kiến ​​thức cơ bản về đọc và ghi dữ liệu Firebase.

Dữ liệu Firebase được ghi vào tham chiếu FirebaseDatabase và được truy xuất bằng cách đính kèm trình nghe không đồng bộ vào tham chiếu. Trình nghe được kích hoạt một lần cho trạng thái ban đầu của dữ liệu và một lần nữa bất cứ lúc nào dữ liệu thay đổi.

Nhận cơ sở dữ liệu

Để đọc hoặc ghi dữ liệu từ cơ sở dữ liệu, bạn cần một phiên bản của DatabaseReference :

Java

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

Kotlin + KTX

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

Đọc và ghi dữ liệu

Các thao tác viết cơ bản

Đối với các thao tác ghi cơ bản, bạn có thể sử dụng setValue() để lưu dữ liệu vào một tham chiếu được chỉ định, thay thế mọi dữ liệu hiện có tại đường dẫn đó. Bạn có thể sử dụng phương pháp này để:

  • Các loại pass tương ứng với các loại JSON có sẵn như sau:
    • String
    • Long
    • Double
    • Boolean
    • Map<String, Object>
    • List<Object>
  • Truyền một đối tượng Java tùy chỉnh, nếu lớp định nghĩa nó có một phương thức khởi tạo mặc định không nhận đối số và có các bộ nhận công khai cho các thuộc tính được gán.

Nếu bạn sử dụng một đối tượng Java, nội dung của đối tượng của bạn sẽ tự động được ánh xạ tới các vị trí con theo kiểu lồng nhau. Sử dụng một đối tượng Java cũng thường làm cho mã của bạn dễ đọc hơn và dễ bảo trì hơn. Ví dụ: nếu bạn có một ứng dụng có hồ sơ người dùng cơ bản, thì đối tượng User của bạn có thể trông như sau:

Java

@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(
    var username: String? = "",
    var email: String? = ""
)

Bạn có thể thêm người dùng với setValue() như sau:

Java

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

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

Kotlin + KTX

private fun writeNewUser(userId: String, name: String, email: String?) {
    val user = User(name, email)
    database.child("users").child(userId).setValue(user)
}

Sử dụng setValue() theo cách này sẽ ghi đè dữ liệu tại vị trí được chỉ định, bao gồm bất kỳ nút con nào. Tuy nhiên, bạn vẫn có thể cập nhật một phần tử con mà không cần viết lại toàn bộ đối tượng. Nếu bạn muốn cho phép người dùng cập nhật hồ sơ của họ, bạn có thể cập nhật tên người dùng như sau:

Java

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

Kotlin + KTX

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

Lắng nghe các sự kiện giá trị

Để đọc dữ liệu tại một đường dẫn và lắng nghe các thay đổi, hãy sử dụng phương thức addValueEventListener() hoặc addListenerForSingleValueEvent() để thêm ValueEventListener vào DatabaseReference .

Thính giả Sự kiện gọi lại Cách sử dụng điển hình
ValueEventListener onDataChange() Đọc và lắng nghe các thay đổi đối với toàn bộ nội dung của đường dẫn.

Bạn có thể sử dụng phương thức onDataChange() để đọc ảnh chụp nhanh tĩnh của nội dung tại một đường dẫn nhất định, vì chúng đã tồn tại tại thời điểm diễn ra sự kiện. Phương pháp này được kích hoạt một lần khi người nghe được đính kèm và lặp lại mỗi khi dữ liệu, bao gồm cả phần tử con, thay đổi. Sự kiện gọi lại được chuyển qua một ảnh chụp nhanh chứa tất cả dữ liệu tại vị trí đó, bao gồm cả dữ liệu con. Nếu không có dữ liệu, ảnh chụp nhanh sẽ trả về giá trị false khi bạn gọi exists()null khi bạn gọi getValue() trên đó.

Ví dụ sau minh họa một ứng dụng viết blog xã hội lấy chi tiết của một bài đăng từ cơ sở dữ liệu:

Java

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)

Người nghe nhận được một Ảnh DataSnapshot dữ liệu có chứa dữ liệu tại vị trí được chỉ định trong cơ sở dữ liệu tại thời điểm diễn ra sự kiện. Gọi getValue() trên một ảnh chụp nhanh trả về biểu diễn đối tượng Java của dữ liệu. Nếu không có dữ liệu nào tồn tại tại vị trí, việc gọi getValue() trả về null .

Trong ví dụ này, ValueEventListener cũng định nghĩa phương thức onCancelled() được gọi nếu quá trình đọc bị hủy. Ví dụ: việc đọc có thể bị hủy nếu máy khách không có quyền đọc từ vị trí cơ sở dữ liệu Firebase. Phương thức này được chuyển qua một đối tượng DatabaseError cho biết lý do tại sao lỗi xảy ra.

Đọc dữ liệu một lần

Trong một số trường hợp, bạn có thể muốn gọi lại một lần và sau đó bị xóa ngay lập tức, chẳng hạn như khi khởi tạo phần tử giao diện người dùng mà bạn không mong đợi thay đổi. Bạn có thể sử dụng phương thức addListenerForSingleValueEvent() để đơn giản hóa tình huống này: nó kích hoạt một lần và sau đó không kích hoạt nữa.

Điều này hữu ích cho dữ liệu chỉ cần tải một lần và không được mong đợi thay đổi thường xuyên hoặc yêu cầu lắng nghe tích cực. Ví dụ: ứng dụng viết blog trong các ví dụ trước sử dụng phương pháp này để tải hồ sơ của người dùng khi họ bắt đầu tạo một bài đăng mới:

Cập nhật hoặc xóa dữ liệu

Cập nhật các trường cụ thể

Để ghi đồng thời cho các nút con cụ thể mà không ghi đè lên các nút con khác, hãy sử dụng phương thức updateChildren() .

Khi gọi updateChildren() , bạn có thể cập nhật các giá trị con cấp thấp hơn bằng cách chỉ định một đường dẫn cho khóa. Nếu dữ liệu được lưu trữ ở nhiều vị trí để mở rộng quy mô tốt hơn, bạn có thể cập nhật tất cả các phiên bản của dữ liệu đó bằng cách sử dụng quạt dữ liệu . Ví dụ: một ứng dụng viết blog xã hội có thể có lớp Post như thế này:

Java

@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
        )
    }
}

Để tạo một bài đăng và đồng thời cập nhật nó vào nguồn cấp dữ liệu hoạt động gần đây và nguồn cấp dữ liệu hoạt động của người dùng đăng bài, ứng dụng viết blog sử dụng mã như sau:

Java

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)
}

Ví dụ này sử dụng push() để tạo một bài đăng trong nút chứa bài đăng cho tất cả người dùng tại /posts/$postid và đồng thời truy xuất khóa bằng getKey() . Sau đó, khóa có thể được sử dụng để tạo mục nhập thứ hai trong các bài viết của người dùng tại /user-posts/$userid/$postid .

Sử dụng các đường dẫn này, bạn có thể thực hiện cập nhật đồng thời cho nhiều vị trí trong cây JSON bằng một lệnh gọi updateChildren() , chẳng hạn như cách ví dụ này tạo bài đăng mới ở cả hai vị trí. Các bản cập nhật đồng thời được thực hiện theo cách này là nguyên tử: hoặc tất cả các bản cập nhật thành công hoặc tất cả các bản cập nhật không thành công.

Thêm một cuộc gọi lại hoàn thành

Nếu bạn muốn biết khi nào dữ liệu của mình đã được cam kết, bạn có thể thêm trình nghe hoàn thành. Cả setValue()updateChildren() nhận một trình nghe hoàn thành tùy chọn được gọi khi quá trình ghi đã được cam kết thành công vào cơ sở dữ liệu. Nếu cuộc gọi không thành công, người nghe được chuyển một đối tượng lỗi cho biết lý do tại sao lỗi xảy ra.

Java

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
            // ...
        }

Xóa dữ liệu

Cách đơn giản nhất để xóa dữ liệu là gọi removeValue() trên một tham chiếu đến vị trí của dữ liệu đó.

Bạn cũng có thể xóa bằng cách chỉ định null làm giá trị cho một thao tác ghi khác như setValue() hoặc updateChildren() . Bạn có thể sử dụng kỹ thuật này với updateChildren() để xóa nhiều phần tử con trong một lệnh gọi API.

Tách người nghe

Các lệnh gọi lại được xóa bằng cách gọi phương thức removeEventListener() trên tham chiếu cơ sở dữ liệu Firebase của bạn.

Nếu một trình nghe đã được thêm nhiều lần vào một vị trí dữ liệu, thì nó được gọi nhiều lần cho mỗi sự kiện và bạn phải tách nó ra cùng một số lần để loại bỏ nó hoàn toàn.

Việc gọi removeEventListener() trên một trình nghe chính không tự động xóa các trình nghe đã đăng ký trên các nút con của nó; removeEventListener() cũng phải được gọi trên bất kỳ trình nghe con nào để loại bỏ lệnh gọi lại.

Lưu dữ liệu dưới dạng giao dịch

Khi làm việc với dữ liệu có thể bị hỏng do sửa đổi đồng thời, chẳng hạn như bộ đếm tăng dần, bạn có thể sử dụng thao tác giao dịch . Bạn cung cấp cho thao tác này hai đối số: một hàm cập nhật và một lệnh gọi lại hoàn thành tùy chọn. Hàm cập nhật lấy trạng thái hiện tại của dữ liệu làm đối số và trả về trạng thái mong muốn mới mà bạn muốn ghi. Nếu một ứng dụng khách khác ghi vào vị trí trước khi giá trị mới của bạn được ghi thành công, thì hàm cập nhật của bạn sẽ được gọi lại với giá trị hiện tại mới và quá trình ghi sẽ được thử lại.

Ví dụ: trong ứng dụng viết blog xã hội mẫu, bạn có thể cho phép người dùng gắn dấu sao và bỏ gắn dấu sao bài đăng và theo dõi số sao một bài đăng đã nhận được như sau:

Java

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!!)
        }
    })
}

Việc sử dụng giao dịch ngăn không cho số lượng sao không chính xác nếu nhiều người dùng gắn dấu sao cho cùng một bài đăng cùng một lúc hoặc khách hàng có dữ liệu cũ. Nếu giao dịch bị từ chối, máy chủ sẽ trả về giá trị hiện tại cho máy khách, giá trị này sẽ chạy lại giao dịch với giá trị được cập nhật. Điều này lặp lại cho đến khi giao dịch được chấp nhận hoặc quá nhiều lần thử đã được thực hiện.

Ghi dữ liệu ngoại tuyến

Nếu máy khách mất kết nối mạng, ứng dụng của bạn sẽ tiếp tục hoạt động bình thường.

Mỗi ứng dụng khách được kết nối với cơ sở dữ liệu Firebase đều duy trì phiên bản nội bộ của riêng bất kỳ dữ liệu hoạt động nào. Khi dữ liệu được ghi, nó sẽ được ghi vào phiên bản cục bộ này trước tiên. Sau đó, máy khách Firebase đồng bộ hóa dữ liệu đó với máy chủ cơ sở dữ liệu từ xa và với các máy khách khác trên cơ sở "nỗ lực hết sức".

Kết quả là, tất cả các lần ghi vào cơ sở dữ liệu sẽ kích hoạt các sự kiện cục bộ ngay lập tức, trước khi bất kỳ dữ liệu nào được ghi vào máy chủ. Điều này có nghĩa là ứng dụng của bạn vẫn đáp ứng bất kể độ trễ của mạng hoặc kết nối.

Khi kết nối được thiết lập lại, ứng dụng của bạn sẽ nhận được tập hợp sự kiện thích hợp để ứng dụng khách đồng bộ hóa với trạng thái máy chủ hiện tại mà không cần phải viết bất kỳ mã tùy chỉnh nào.

Bước tiếp theo