Cách đọc/ghi (read/write) dữ liệu vào firebase realtime database trên Web

Bài Học, Firebase


Cách lấy về 1 tham chiếu tới database (database reference)

Để đọc/ghi (read/write) dữ liệu data từ database, bạn cần khởi tạo 1 đối tượng instance từ firebase.database.Reference :

// Khởi tạo 1 tham chiếu tới database
var database = firebase.database();


Cách đọc/ghi (reading/writing) data

Bài viết này sẽ đề cập tới các vấn đề cơ bản của việc lấy ra dữ liệu data và cách sắp xếp và thực hiện lọc firebase data.

Firebase data được lấy ra bằng cách đính vào hàm listener (lắng nghe sự kiện) vào 1 tham chiếufirebase.database.Reference. Hàm listener được kích hoạt khi dữ liệu data được khởi tạo hay bất cứ khi nào dữ liệu data này thay đổi.

Chú ý: Mặc định, việc đọc/ghi (read/write) vào database bị giới hạn, chỉ cho phép những users nào đã được xác thực mới có quyền read/write vào. Khi mới bắt đầu làm quen với firebase, thì bạn có thể cấu hình các luật rules này để cho truy xuất write/read không bị hạn chế vào database theo link configure your rules for public access . Cấu hình này sẽ làm cho database của bạn bị mở ra cho tất cả mọi người, ngay cả với những người không sử dụng app của bạn, Do vậy bạn cần đảm bảo chắc chắn giới hạn lại quyền truy cập database khi bạn thiết lập lại chế độ xác thực authentication.

Cách write data cơ bản

Với các thao tác write cơ bản, bạn có thể sử dụng phương thức set() để lưu data vào 1 tham chiếu database cụ thể. Ta xét ví dụ về 1 ứng dụng social blogging application có thể thêm mới 1 user bằng cách thực hiện set() như sau:

function writeUserData(userId, name, email, imageUrl) {
  firebase.database().ref('users/' + userId).set({
    username: name,
    email: email,
    profile_picture : imageUrl
  });
}

Phương thức set() sẽ ghi đè data tại node có vị trí users/$userId, và lưu ý nó ghi đè tất cả các child nodes của node này nếu có.

Cách lắng nghe sự kiện value events

Để đọc dữ liệu data của 1 node tại 1 vị trí path và lắng nghe sự kiện khi giá trị value của nó thay đổi, bạn sử dụng phương thức on() hoặc once() của tham chiếufirebase.database.Reference để lắng nghe sự kiện.

Sự kiện (Event)Mục đích sử dụng
valueĐọc (read) dữ liệu và lắng nghe sự thay đổi giá trị của node và toàn bộ child node của nó tại 1 vị trí path

Bạn có thể sử dụng sự kiện value event để đọc giá trị data của 1 node tại 1 vị trị path cụ thể. Sự kiện này sẽ được kích hoạt khi giá trị data tại node hay giá trị của 1 trong các child node của nó thay đổi. Hàm event callback nhận tham số là 1 snapshot (trạng thái giá trị data vào thời điểm sự kiện xảy ra) bao gồm giá trị data tại node (chú ý có giá trị data này bao gồm tất cả các giá trị của child nodes của nó). Nếu node không chứa dữ liệu data, thì snapshot sẽ trả về false khi bạn thực hiện gọi phương thức exists()và trả về null khi bạn thực hiện gọi phương thức value()

Chú ý: valueevent của 1 node được kích hoạt vào mọi lúc giá trị data thay đổi, bao gồm cả những thay đổi xảy ra ở các child nodes của nó. Để giới hạn kích thước của snapshots, bạn chỉ nên lắng nghe sự kiện value event ở node có độ sâu cấp thấp nhất khi thực sự cần thiết để nắm bắt được sự thay đổi của data. Ví dụ, việc lắng nghe value event tại gốc root của database là việc không nên làm.

Ta xét ví dụ dưới để hình dung ra cách mà ứng dụng social blogging application lấy về số sao star của 1 bài viết post từ database:

var starCountRef = firebase.database().ref('posts/' + postId + '/starCount');
starCountRef.on('value', function(snapshot) {
  updateStarCount(postElement, snapshot.val());
});

Hàm lắng nghe listener nhận 1 snapshot làm tham số đầu vào, nó chứa dữ liệu data của node tại 1 vị trí xác định, tại thời điểm mà sự kiện event xảy ra. Bạn có thể lấy về data trong snapshot bằng gọi phương thức val().

Cách đọc data một lần duy nhất

Trong 1 vài trường hợp, bạn rất có thể muốn lấy về 1 snapshot của data mà không cần phải lắng nghe sự kiện data bị thay đổi, giống như việc bạn khởi tạo ra 1 UI element mà bạn không mong muốn nó thay đổi trạng thái. Bạn có thể sử dụng phương thức once() để đơn giản hóa xử lý trong trường hợp này: Sự kiện chỉ kích hoạt duy nhất 1 lần mà không thực hiện kích hoạt lại nữa.

Điều này rất hữu dụng cho data chỉ cần nạp vào duy nhất đúng 1 lần và không kỳ vọng data sẽ thay đổi. Ví dụ, trong ứng dụng blogging app ở ví dụ trước, ta sử dụng phương thức once() để nạp vào thông tin user profile khi họ bắt đầu viết 1 bài post mới:

var userId = firebase.auth().currentUser.uid;
return firebase.database().ref('/users/' + userId).once('value').then(function(snapshot) {
  var username = (snapshot.val() && snapshot.val().username) || 'Anonymous';
  // ...
});

Cách thực hiện update và detele data

Cách update fields

Giả định bạn muốn write data vào 1 vài node con xác định của 1 node mà không muốn phải ghi đè vào các child nodes khác, thì bạn phải sử dụng phương thức update()

Khi gọi phương thức update(), bạn có thể update các giá trị child values (giá trị values của các node con) với độ sâu cao hơn bằng cách định nghĩa 1 vị trí path làm khóa key. Nếu data được lưu trữ tại nhiều vị trí (để scale tốt hơn), thì bạn có thể phải update toàn bộ những node đó bằng cách sử dụng data fan-out.

Ta xét ví dụ, 1 ứng dụng social blogging app có chức năng tạo ra bài viết post và cùng lúc phải update bài post này vào danh sách bài post mới nhất và danh sách feed hoạt động mới nhất của user tạo ra post đó:

function writeNewPost(uid, username, picture, title, body) {
  // Dữ liệu 1 bài post.
  var postData = {
    author: username,
    uid: uid,
    body: body,
    title: title,
    starCount: 0,
    authorPic: picture
  };

  // Tạo khóa key cho bài post mới.
  var newPostKey = firebase.database().ref().child('posts').push().key;

  // Ghi dữ liệu data của bài post mới đồng thời vào danh sách posts list và danh sách post list của user
  var updates = {};
  updates['/posts/' + newPostKey] = postData;
  updates['/user-posts/' + uid + '/' + newPostKey] = postData;

  return firebase.database().ref().update(updates);
}

Ví dụ này sử dụng phương thức push() để tạo ra 1 post trong node mà chứa tất cả các posts dành cho tất cả users tại vị trí /posts/$postid và đồng thời lấy về khóa key. Khóa key cũng có thể sử dụng để tạo ra 1 bài post nằm trong danh sách các bài posts của user tại vị trí /user-posts/$userid/$postid

Bằng cách sử dụng các vị trí path này, bạn có thể thực hiện update đồng thời tới nhiều vị trí khác nhau trong cây JSON tree với duy nhất 1 lời gọi update(), tương tự như ví dụ trên cũng tạo post mới ở cả 2 vị trí. Thao tác update đồng thời này là atomic nên kết quả là mọi updates đều thành công hoặc đều thất bại.

Thêm vào completion callback

Nếu bạn muốn biết khi nào dữ liệu data của bạn đã được xử lý thành công, thì bạn có thể thêm vào 1 hàm completion callback. Cả 2 phương thức set()updtae() đều nhận thêm 1 tham số là completion callback, hàm callback này sẽ được gọi lại khi thao tác ghi vào database thành công. Nếu lời gọi không thành công, thì callback sẽ nhận tham số là đối tượng lỗi error, để xác định nguyên nhân xảy ra lỗi:

  firebase.database().ref('users/' + userId).set({
    username: name,
    email: email,
    profile_picture : imageUrl
  }, function(error) {
    if (error) {
      // The write failed...
    } else {
      // Data saved successfully!
    }
  });
}

Delete data

Cách đơn giản nhất để delete data là gọi phương thức remove() tại vị trí node muốn xóa.

Bạn cũng có thể xóa bằng cách gán null vào giá trị value đối với các thao tác xử lý write như set() hay update(). Bạn có thể sử dụng kĩ thuật này với update() để xóa nhiều child node trong 1 lời gọi api duy nhất.

Cách nhận về Promise

Để biết khi nào dữ liệu data đã được xử lý thành công tới Firebase Realtime Database trên server, bạn có thể sử dụng 1 Promise . Cả 2 phương thức set()update() đều trả về 1 Promise, bạn có thể sử dụng nó để biết khi nào write thành công vào database.

Cách tắt lắng nghe listeners

Hàm callbacks có thể được xóa bỏ bằng cách sử dụng phương thức off(). Bạn có thể xóa đi 1 listener bằng cách truyền nó vào làm tham số của off(). Gọi phương thức off() tại 1 node có vị trí xác định mà không truyền vào tham số để xóa bỏ tất cả các listener tại node có vị trí đó.

Gọi phương thức off() ở node cha thì sẽ không tự động xóa bỏ listerners được đăng ký bởi child nodes của nó; phương thức off() sẽ phải thực hiện gọi nhiều lần trên các child listerners để xóa callback ở các child nodes.

Cách lưu data bảo toàn sử dụng transactions

Khi thực hiện xử lý dữ liệu data rất có thể xảy ra trường hợp dữ liệu bị hỏng, mất mát bởi các thao tác sửa đổi song song đồng thời, ví dụ như thao tác đếm tự tăng, bạn có thể sử dụng thao tác xử lý transaction operation để khắc phục tình trạng này. Thao tác xử lý này giống như hàm update và có nhận 1 tham số là completion callback. Hàm update này nhận trạng thái state hiện tại của dữ liệu data làm tham số và trả về trạng thái state mới mà bạn muốn write. Nếu client khác muốn write vào cùng 1 node có cùng vị trí, trước khi giá trị value mới được write thành công, thì hàm update sẽ được gọi lại với trạng thái value mới và thao tác write sẽ được thực hiện lại.

Ví dụ, trong ứng dụng social blogging app, bạn có thể cho phép users đánh dấu sao hoặc bỏ dấu sao để bình chọn cho bài viết post và muốn lưu vết lại, xem có bao nhiêu sao được bình chọn cho bài post như sau:

function toggleStar(postRef, uid) {
  postRef.transaction(function(post) {
    if (post) {
      if (post.stars && post.stars[uid]) {
        post.starCount--;
        post.stars[uid] = null;
      } else {
        post.starCount++;
        if (!post.stars) {
          post.stars = {};
        }
        post.stars[uid] = true;
      }
    }
    return post;
  });
}

Bằng cách sử dụng transaction, sẽ ngăn chặn việc đếm sao bình chọn trở nên không chính xác khi có nhiều users cùng lúc bình chọn cùng 1 bài post vào cùng 1 thời điểm. Nếu transaction bị từ chối, thì server sẽ trả về giá trị value hiện tại xuống client, và sẽ thực hiện chạy lại transaction với giá trị value đã được update. Quy trình xử lý này sẽ được lặp lại cho tới khi nào transaction được chấp nhận hay bạn thực hiện hủy transaction đó.

Chú ý: Bởi vì hàm update có thể được gọi nhiều lần, nên bạn có thể phải tính tới việc xử lý khi data null. Ngay cả khi đã có tồn tại data trên remote database, rất có thể nó không được cache tại local khi hàm transaction thực hiện chạy, kết quả sẽ là giá trị value khởi tạo là null.

Write data offline

Nếu client mất kết nối network, ứng dụng app của bạn sẽ vẫn hoạt động đúng chức năng.

Mọi client kết nối tới firebase database đều bảo toàn lưu trữ các phiên bản nội tại ở ngay chính client của bất cứ active data nào. Khi data được thực hiện write, thì chính là nó được write tại local trước tiên. Sau đó firebase client thực hiện đồng bộ data này với remote database ở trên server 1 cách tối ưu nhất.

Do vậy kết quả là toàn bộ xử lý writes tới database sẽ kích hoạt sự kiện events ở local ngay lập tức, trước cả khi bất cứ data nào được write ở server. Điều này có nghĩa là app của bạn phản hồi gần như ngay lập tức mà không cần quan tâm tới độ trễ của network.

Khi kết nối mạng được thiết lập lại, ứng dụng app của bạn sẽ nhận được 1 tập các sự kiện events thích hợp, do vậy client thực hiện đồng bộ với trạng thái state hiện tại ở server, mà không cần phải viết thêm bất cứ đoạn code nào cả.

Chú ý: Firebase Realtime Database Web APIs không lưu trữ dữ liệu data offline bên ngoài phiên session. Để xử lý writes có thể lưu trữ ở server, thì trang web page phải không được đóng trước khi data được write trên server.

Nguồn: https://firebase.google.com/docs/database/web/read-and-write


Trả lời

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *