Các ứng dụng Firebase vẫn hoạt động ngay cả khi ứng dụng của bạn tạm thời mất kết nối mạng. Ngoài ra, Firebase còn cung cấp các công cụ để lưu trữ dữ liệu cục bộ, quản lý trạng thái hiện diện và xử lý độ trễ.
Khả năng lưu trữ dài lâu trên ổ đĩa
Các ứng dụng Firebase tự động xử lý các trường hợp gián đoạn mạng tạm thời. Dữ liệu được lưu vào bộ nhớ đệm sẽ có sẵn khi ngoại tuyến và Firebase sẽ gửi lại mọi thao tác ghi khi kết nối mạng được khôi phục.
Khi bạn bật tính năng lưu trữ dài lâu trên ổ đĩa, ứng dụng sẽ ghi dữ liệu cục bộ vào thiết bị để ứng dụng có thể duy trì trạng thái khi ngoại tuyến, ngay cả khi người dùng hoặc hệ điều hành khởi động lại ứng dụng.
Bạn có thể bật tính năng lưu trữ dài lâu trên ổ đĩa chỉ bằng một dòng mã.
FirebaseDatabase.instance.setPersistenceEnabled(true);
Hành vi lưu trữ dài lâu
Bằng cách bật tính năng lưu trữ dài lâu, mọi dữ liệu mà ứng dụng khách Cơ sở dữ liệu theo thời gian thực của Firebase sẽ đồng bộ hoá khi trực tuyến sẽ được lưu trữ dài lâu trên ổ đĩa và có sẵn khi ngoại tuyến, ngay cả khi người dùng hoặc hệ điều hành khởi động lại ứng dụng. Điều này có nghĩa là ứng dụng của bạn hoạt động như khi trực tuyến bằng cách sử dụng dữ liệu cục bộ được lưu trữ trong bộ nhớ đệm. Lệnh gọi lại của trình nghe sẽ tiếp tục kích hoạt cho các bản cập nhật cục bộ.
Ứng dụng khách Cơ sở dữ liệu theo thời gian thực của Firebase tự động duy trì một hàng đợi gồm tất cả các thao tác ghi được thực hiện khi ứng dụng của bạn ngoại tuyến. Khi bật tính năng lưu trữ dài lâu, hàng đợi này cũng được lưu trữ dài lâu trên ổ đĩa để tất cả các thao tác ghi của bạn đều có sẵn khi người dùng hoặc hệ điều hành khởi động lại ứng dụng. Khi ứng dụng kết nối lại, tất cả các thao tác sẽ được gửi đến máy chủ Cơ sở dữ liệu theo thời gian thực của Firebase.
Nếu ứng dụng của bạn sử dụng Xác thực Firebase, thì ứng dụng khách Cơ sở dữ liệu theo thời gian thực của Firebase sẽ lưu trữ dài lâu mã thông báo xác thực của người dùng trong các lần khởi động lại ứng dụng. Nếu mã thông báo xác thực hết hạn khi ứng dụng của bạn ngoại tuyến, thì ứng dụng khách sẽ tạm dừng các thao tác ghi cho đến khi ứng dụng xác thực lại người dùng, nếu không, các thao tác ghi có thể không thành công do quy tắc bảo mật.
Luôn cập nhật dữ liệu
Cơ sở dữ liệu theo thời gian thực của Firebase đồng bộ hoá và lưu trữ một bản sao cục bộ của dữ liệu cho các trình nghe đang hoạt động. Ngoài ra, bạn có thể duy trì trạng thái đồng bộ hoá cho các vị trí cụ thể.
final scoresRef = FirebaseDatabase.instance.ref("scores");
scoresRef.keepSynced(true);
Ứng dụng khách Cơ sở dữ liệu theo thời gian thực của Firebase tự động tải dữ liệu xuống tại các vị trí này và duy trì trạng thái đồng bộ hoá ngay cả khi tham chiếu không có trình nghe đang hoạt động. Bạn có thể tắt lại tính năng đồng bộ hoá bằng dòng mã sau.
scoresRef.keepSynced(false);
Theo mặc định, 10 MB dữ liệu đã đồng bộ hoá trước đó sẽ được lưu vào bộ nhớ đệm. Dung lượng này sẽ đủ cho hầu hết các ứng dụng. Nếu bộ nhớ đệm vượt quá kích thước đã định cấu hình, thì Cơ sở dữ liệu theo thời gian thực của Firebase sẽ xoá dữ liệu được sử dụng gần đây nhất. Dữ liệu được duy trì ở trạng thái đồng bộ hoá sẽ không bị xoá khỏi bộ nhớ đệm.
Truy vấn dữ liệu ngoại tuyến
Cơ sở dữ liệu theo thời gian thực của Firebase lưu trữ dữ liệu được trả về từ một truy vấn để sử dụng khi ngoại tuyến. Đối với các truy vấn được tạo khi ngoại tuyến, Cơ sở dữ liệu theo thời gian thực của Firebase vẫn hoạt động đối với dữ liệu đã tải trước đó. Nếu dữ liệu được yêu cầu chưa tải, thì Cơ sở dữ liệu theo thời gian thực của Firebase sẽ tải dữ liệu từ bộ nhớ đệm cục bộ. Khi kết nối mạng có sẵn trở lại, dữ liệu sẽ tải và phản ánh truy vấn.
Ví dụ: mã này truy vấn 4 mục gần đây nhất trong cơ sở dữ liệu điểm số:
final scoresRef = FirebaseDatabase.instance.ref("scores");
scoresRef.orderByValue().limitToLast(4).onChildAdded.listen((event) {
debugPrint("The ${event.snapshot.key} dinosaur's score is ${event.snapshot.value}.");
});
Giả sử người dùng mất kết nối, chuyển sang chế độ ngoại tuyến và khởi động lại ứng dụng. Khi vẫn ở chế độ ngoại tuyến, ứng dụng sẽ truy vấn 2 mục gần đây nhất từ cùng một vị trí. Truy vấn này sẽ trả về thành công 2 mục gần đây nhất vì ứng dụng đã tải tất cả 4 mục trong truy vấn ở trên.
scoresRef.orderByValue().limitToLast(2).onChildAdded.listen((event) {
debugPrint("The ${event.snapshot.key} dinosaur's score is ${event.snapshot.value}.");
});
Trong ví dụ trước, ứng dụng khách Cơ sở dữ liệu theo thời gian thực của Firebase sẽ kích hoạt các sự kiện "đã thêm phần tử con" cho 2 con khủng long có điểm số cao nhất bằng cách sử dụng bộ nhớ đệm được lưu trữ dài lâu. Tuy nhiên, ứng dụng này sẽ không kích hoạt sự kiện "giá trị" vì ứng dụng chưa bao giờ thực thi truy vấn đó khi trực tuyến.
Nếu ứng dụng yêu cầu 6 mục gần đây nhất khi ngoại tuyến, thì ứng dụng sẽ nhận được các sự kiện "đã thêm phần tử con" cho 4 mục được lưu vào bộ nhớ đệm ngay lập tức. Khi thiết bị kết nối lại, ứng dụng khách Cơ sở dữ liệu theo thời gian thực của Firebase sẽ đồng bộ hoá với máy chủ và nhận được 2 sự kiện "đã thêm phần tử con" cuối cùng và sự kiện "giá trị" cho ứng dụng.
Xử lý giao dịch ngoại tuyến
Mọi giao dịch được thực hiện khi ứng dụng ngoại tuyến đều được đưa vào hàng đợi. Sau khi ứng dụng kết nối lại với mạng, các giao dịch sẽ được gửi đến máy chủ Cơ sở dữ liệu theo thời gian thực.
Cơ sở dữ liệu theo thời gian thực của Firebase có nhiều tính năng để xử lý các tình huống ngoại tuyến và kết nối mạng. Phần còn lại của hướng dẫn này áp dụng cho ứng dụng của bạn cho dù bạn đã bật tính năng lưu trữ dài lâu hay chưa.
Quản lý trạng thái hiện diện
Trong các ứng dụng theo thời gian thực, việc phát hiện thời điểm ứng dụng khách kết nối và ngắt kết nối thường rất hữu ích. Ví dụ: bạn có thể muốn đánh dấu người dùng là "ngoại tuyến" khi ứng dụng khách của họ ngắt kết nối.
Ứng dụng khách Cơ sở dữ liệu Firebase cung cấp các thành phần cơ bản đơn giản mà bạn có thể dùng để ghi vào cơ sở dữ liệu khi một ứng dụng khách ngắt kết nối khỏi máy chủ Cơ sở dữ liệu Firebase. Các bản cập nhật này xảy ra cho dù ứng dụng khách ngắt kết nối một cách sạch sẽ hay không, vì vậy, bạn có thể dựa vào các bản cập nhật này để dọn dẹp dữ liệu ngay cả khi kết nối bị gián đoạn hoặc ứng dụng khách gặp sự cố. Bạn có thể thực hiện tất cả các thao tác ghi, bao gồm cả việc đặt, cập nhật và xoá, khi ngắt kết nối.
Dưới đây là một ví dụ đơn giản về việc ghi dữ liệu khi ngắt kết nối bằng cách sử dụng thành phần cơ bản onDisconnect:
final presenceRef = FirebaseDatabase.instance.ref("disconnectmessage");
// Write a string when this client loses connection
presenceRef.onDisconnect().set("I disconnected!");
Cách hoạt động của onDisconnect
Khi bạn thiết lập một thao tác onDisconnect(), thao tác đó sẽ tồn tại trên máy chủ Cơ sở dữ liệu theo thời gian thực của Firebase. Máy chủ kiểm tra tính bảo mật để đảm bảo người dùng có thể thực hiện sự kiện ghi được yêu cầu và thông báo cho ứng dụng của bạn nếu sự kiện đó không hợp lệ. Sau đó, máy chủ sẽ giám sát kết nối. Nếu tại bất kỳ thời điểm nào, kết nối hết thời gian chờ hoặc bị ứng dụng khách Cơ sở dữ liệu theo thời gian thực đóng chủ động, thì máy chủ sẽ kiểm tra tính bảo mật lần thứ hai (để đảm bảo thao tác vẫn hợp lệ) rồi kích hoạt sự kiện.
try {
await presenceRef.onDisconnect().remove();
} catch (error) {
debugPrint("Could not establish onDisconnect event: $error");
}
Bạn cũng có thể huỷ sự kiện onDisconnect bằng cách gọi .cancel():
final onDisconnectRef = presenceRef.onDisconnect();
onDisconnectRef.set("I disconnected");
// ...
// some time later when we change our minds
// ...
onDisconnectRef.cancel();
Phát hiện trạng thái kết nối
Đối với nhiều tính năng liên quan đến trạng thái hiện diện, ứng dụng của bạn nên biết thời điểm trực tuyến hoặc ngoại tuyến. Cơ sở dữ liệu theo thời gian thực của Firebase cung cấp một vị trí đặc biệt tại /.info/connected được cập nhật mỗi khi trạng thái kết nối của ứng dụng khách Cơ sở dữ liệu theo thời gian thực của Firebase thay đổi. Dưới đây là một ví dụ:
final connectedRef = FirebaseDatabase.instance.ref(".info/connected");
connectedRef.onValue.listen((event) {
final connected = event.snapshot.value as bool? ?? false;
if (connected) {
debugPrint("Connected.");
} else {
debugPrint("Not connected.");
}
});
/.info/connected là một giá trị boolean không được đồng bộ hoá giữa các ứng dụng khách Cơ sở dữ liệu theo thời gian thực vì giá trị này phụ thuộc vào trạng thái của ứng dụng khách. Nói cách khác, nếu một ứng dụng khách đọc /.info/connected là false, thì điều này không đảm bảo rằng một ứng dụng khách riêng biệt cũng sẽ đọc là false.
Xử lý độ trễ
Dấu thời gian của máy chủ
Máy chủ Cơ sở dữ liệu theo thời gian thực của Firebase cung cấp một cơ chế để chèn dấu thời gian được tạo trên máy chủ dưới dạng dữ liệu. Tính năng này, kết hợp với onDisconnect, cung cấp một cách dễ dàng để ghi lại một cách đáng tin cậy thời điểm một ứng dụng khách Cơ sở dữ liệu theo thời gian thực ngắt kết nối:
final userLastOnlineRef =
FirebaseDatabase.instance.ref("users/joe/lastOnline");
userLastOnlineRef.onDisconnect().set(ServerValue.timestamp);
Độ lệch đồng hồ
Mặc dù ServerValue.timestamp chính xác hơn nhiều và được ưu tiên cho hầu hết các thao tác đọc/ghi, nhưng đôi khi, bạn có thể ước tính độ lệch đồng hồ của ứng dụng khách so với máy chủ của Cơ sở dữ liệu theo thời gian thực của Firebase. Bạn có thể đính kèm lệnh gọi lại vào vị trí /.info/serverTimeOffset để lấy giá trị (tính bằng mili giây) mà ứng dụng khách Cơ sở dữ liệu theo thời gian thực của Firebase thêm vào thời gian được báo cáo cục bộ (thời gian epoch tính bằng mili giây) để ước tính thời gian của máy chủ. Xin lưu ý rằng độ chính xác của độ lệch này có thể bị ảnh hưởng bởi độ trễ mạng, vì vậy, độ lệch này chủ yếu hữu ích để phát hiện các sai lệch lớn (> 1 giây) về thời gian đồng hồ.
final offsetRef = FirebaseDatabase.instance.ref(".info/serverTimeOffset");
offsetRef.onValue.listen((event) {
final offset = event.snapshot.value as num? ?? 0.0;
final estimatedServerTimeMs =
DateTime.now().millisecondsSinceEpoch + offset;
});
Ứng dụng trạng thái hiện diện mẫu
Bằng cách kết hợp các thao tác ngắt kết nối với tính năng giám sát trạng thái kết nối và dấu thời gian của máy chủ, bạn có thể xây dựng một hệ thống trạng thái hiện diện của người dùng. Trong hệ thống này, mỗi người dùng lưu trữ dữ liệu tại một vị trí cơ sở dữ liệu để cho biết ứng dụng khách Cơ sở dữ liệu theo thời gian thực có trực tuyến hay không. Ứng dụng khách đặt vị trí này thành true khi trực tuyến và dấu thời gian khi ngắt kết nối. Dấu thời gian này cho biết thời điểm cuối cùng người dùng được chỉ định trực tuyến.
Xin lưu ý rằng ứng dụng của bạn nên đưa các thao tác ngắt kết nối vào hàng đợi trước khi đánh dấu người dùng là trực tuyến để tránh mọi tình huống tương tranh trong trường hợp kết nối mạng của ứng dụng khách bị mất trước khi cả hai lệnh có thể được gửi đến máy chủ.
// Since I can connect from multiple devices, we store each connection
// instance separately any time that connectionsRef's value is null (i.e.
// has no children) I am offline.
final myConnectionsRef =
FirebaseDatabase.instance.ref("users/joe/connections");
// Stores the timestamp of my last disconnect (the last time I was seen online)
final lastOnlineRef =
FirebaseDatabase.instance.ref("/users/joe/lastOnline");
final connectedRef = FirebaseDatabase.instance.ref(".info/connected");
connectedRef.onValue.listen((event) {
final connected = event.snapshot.value as bool? ?? false;
if (connected) {
final con = myConnectionsRef.push();
// When this device disconnects, remove it.
con.onDisconnect().remove();
// When I disconnect, update the last time I was seen online.
lastOnlineRef.onDisconnect().set(ServerValue.timestamp);
// Add this device to my connections list.
// This value could contain info about the device or a timestamp too.
con.set(true);
}
});