Mã ứng dụng của bạn có thể đăng ký các truy vấn để nhận thông tin cập nhật theo thời gian thực khi kết quả của truy vấn thay đổi.
Trước khi bắt đầu
Thiết lập quá trình tạo SDK cho dự án của bạn như mô tả trong tài liệu dành cho các nền tảng Apple, Android, web, và Flutter.
- Bạn phải bật tính năng lưu vào bộ nhớ đệm phía máy khách cho tất cả SDK đã tạo. Cụ thể, mọi cấu hình SDK phải chứa một khai báo như sau:
clientCache: maxAge: 5s storage: ... # Optional.Ứng dụng khách của bạn phải sử dụng phiên bản mới nhất của SQL Connect SDK cốt lõi:
- Apple: Firebase SQL Connect SDK cho Swift phiên bản 11.12.0 trở lên
- Android: Firebase SQL Connect SDK phiên bản 17.3.0 trở lên (BoM phiên bản 34.14.0 trở lên)
- Web: SDK JavaScript phiên bản 12.12.0 trở lên
- Flutter:
firebase_data_connectphiên bản 0.3.0 trở lên
Tạo lại SDK ứng dụng bằng Firebase CLI phiên bản 15.14.0 trở lên.
Đăng ký kết quả truy vấn
Bạn có thể đăng ký một truy vấn để phản hồi các thay đổi trong kết quả truy vấn. Ví dụ: giả sử bạn có lược đồ và các thao tác sau đây được xác định trong dự án của mình:
# dataconnect/schema/schema.gql
type Movie @table(key: "id") {
id: UUID! @default(expr: "uuidV4()")
title: String!
releaseYear: Int
genre: String
description: String
averageRating: Int
}
# dataconnect/connector/operations.gql
query GetMovieById($id: UUID!) @auth(level: PUBLIC) {
movie(id: $id) {
id
title
releaseYear
genre
description
}
}
mutation UpdateMovie(
$id: UUID!,
$genre: String!,
$description: String!
) {
movie_update(id: $id,
data: {
genre: $genre
description: $description
})
}
Cách đăng ký các thay đổi trong kết quả chạy GetMovieById:
Web
import { subscribe, DataConnectError, QueryResult } from 'firebase/data-connect';
import { getMovieByIdRef, GetMovieByIdData, GetMovieByIdVariables } from '@dataconnect/generated';
const queryRef = getMovieByIdRef({ id: "<MOVIE_ID>" });
// Called when receiving an update.
const onNext = (result: QueryResult<GetMovieByIdData, GetMovieByIdVariables>) => {
console.log("Movie <MOVIE_ID> updated", result);
}
const onError = (err?: DataConnectError) => {
console.error("received error", err);
}
// Called when unsubscribing or when the subscription is automatically released.
const onComplete = () => {
console.log("subscription complete!");
}
const unsubscribe = subscribe(queryRef, onNext, onError, onComplete);
Web (React)
import { subscribe, QueryResult } from 'firebase/data-connect';
import { getMovieByIdRef, GetMovieByIdData, GetMovieByIdVariables } from '@dataconnect/generated';
import { useState, useEffect } from "react";
export const MovieInfo = ({ id: movieId }: { id: string }) => {
const [movieInfo, setMovieInfo] = useState<GetMovieByIdData>();
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
const queryRef = getMovieByIdRef({ id: movieId });
function updateUi(result: QueryResult<GetMovieByIdData, GetMovieByIdVariables>): void {
setMovieInfo(result.data);
setLoading(false);
}
const unsubscribe = subscribe(
queryRef,
updateUi,
(err) => {
setError(err ?? new Error("Unknown error occurred"));
setLoading(false);
}
);
return () => unsubscribe();
}, [movieId]);
if (loading)
return <div>Loading movie details...</div>;
if (error || !movieInfo || !movieInfo.movie)
return <div>Error loading movie details: {error?.message}</div>;
return (
<div>
<h2>{movieInfo.movie.title} ({movieInfo.movie.releaseYear})</h2>
<ul>
<li>Genre: {movieInfo.movie.genre}</li>
<li>Description: {movieInfo.movie.description}</li>
</ul>
</div>
);
};
SQL Connect cũng hỗ trợ tính năng lưu vào bộ nhớ đệm và đăng ký theo thời gian thực bằng
TanStack. Khi bạn chỉ định react: true hoặc angular: true trong tệp
connector.yaml, SQL Connect sẽ tạo các liên kết cho React hoặc Angular bằng TanStack.
Các liên kết này có thể hoạt động cùng với SQL Connect's tính năng hỗ trợ theo thời gian thực tích hợp, nhưng chỉ hoạt động được một cách khó khăn. Bạn nên sử dụng các liên kết dựa trên TanStack hoặc SQL Connect's tính năng hỗ trợ theo thời gian thực tích hợp, nhưng không nên sử dụng cả hai.
Xin lưu ý rằng việc triển khai theo thời gian thực của riêng SQL Connect's có một số ưu điểm so với các liên kết TanStack:
- Lưu vào bộ nhớ đệm được chuẩn hoá: SQL Connect triển khai lưu vào bộ nhớ đệm được chuẩn hoá, giúp cải thiện tính nhất quán của dữ liệu cũng như hiệu quả về bộ nhớ và mạng so với tính năng lưu vào bộ nhớ đệm ở cấp truy vấn. Với tính năng lưu vào bộ nhớ đệm được chuẩn hoá, nếu một thực thể cập nhật ở một khu vực trong ứng dụng của bạn, thì thực thể đó cũng sẽ cập nhật ở các khu vực khác sử dụng thực thể đó.
- Vô hiệu hoá từ xa: SQL Connect có thể vô hiệu hoá từ xa các thực thể được lưu vào bộ nhớ đệm trên tất cả các thiết bị đã đăng ký.
Nếu chọn không sử dụng TanStack, bạn nên xoá các chế độ cài đặt react: true và angular: true khỏi tệp connector.yaml.
iOS
struct MovieDetailsView: View {
// QueryRef has the @Observable annotation, so its properties will
// automatically trigger updates on changes.
// Realtime subscriptions will keep the query results updated with changes.
// Define the ref variable.
// If parameters are known before hand, refs can be initialized here directly
// else they can be initialized in the init for the view, like here
@State private var queryRef: GetMovieByIdQuery.Ref
// Store the handle to unsubscribe from query updates.
// QueryRef can be used in multiple views.
// Each view can separately subscribe / unsubscribe to updates
// When there are no more subscribers to a QueryRef,
// it will cancel automatic updates for that QueryRef.
@State private var querySub: AnyCancellable?
init(movieId: String) {
// initialize the ref with the movieId
queryRef = DataConnect.moviesConnector.getMovieByIdQuery.ref(movieId: movieId)
}
var body: some View {
VStack {
// Use the query results in a View.
if let movie = queryRef.data?.movie {
Text(movie.title)
Text(mpvie.description)
// other details
} else {
// if last fetch/update resulted in an error
if error = queryRef.lastError {
Text("Error loading movie")
} else {
Text("Loading movie ...")
}
}
}
.onAppear {
// Subscribe to the query for updates using the Observable macro.
Task {
do {
querySub = try await queryRef.subscribe().sink { _ in }
} catch {
print("Error subscribing to query: \(error)")
}
}
}
.onDisappear {
// Calling cancel will unsubscribe from receiving updates.
querySub?.cancel()
}
}
}
Android
class ExampleViewModel(
private val movieId: UUID
) : ViewModel() {
private val _uiState = MutableStateFlow<GetMovieByIdQuery.Data.Movie?>(null)
val uiState = _uiState.asStateFlow()
// Subscribe to the query.
private val movieInfoSub = ExampleConnector.instance
.getMovieById.ref(GetMovieByIdQuery.Variables(id = movieId))
.subscribe()
init {
viewModelScope.launch {
movieInfoSub.flow.collect {
// As query results are collected, update the UI state.
val result = it.result.getOrElse { return@collect }
_uiState.update{ result.data.movie }
}
}
}
companion object {
fun provideFactory(movieId: UUID): ViewModelProvider.Factory =
viewModelFactory {
initializer {
ExampleViewModel(movieId = movieId)
}
}
}
}
Flutter
Nhập SDK đã tạo của dự án:
import 'package:flutter_app/dataconnect_generated/generated.dart';
Sau đó, hãy gọi phương thức subscribe() trên một tham chiếu truy vấn:
final queryRef = MovieConnector.instance.getMovieById(id: "<MOVIE_ID>").ref();
final subscription = queryRef.subscribe().listen((result) {
final movie = result.data.movie;
if (movie != null) {
// Execute your logic to update the UI with the refreshed movie information.
updateUi(movie.title);
}
});
Để ngừng cập nhật, bạn có thể gọi subscription.cancel().
Sau khi đăng ký truy vấn như trong ví dụ trước, bạn sẽ nhận được thông tin cập nhật bất cứ khi nào kết quả của truy vấn cụ thể thay đổi. Ví dụ: nếu một ứng dụng khách khác thực thi thay đổi UpdateMovie trên cùng một mã nhận dạng mà bạn đã đăng ký, thì bạn sẽ nhận được thông tin cập nhật.
Tín hiệu làm mới truy vấn ngầm ẩn
Trong ví dụ trước, bạn có thể đăng ký một truy vấn và nhận thông tin cập nhật theo thời gian thực mà không cần sửa đổi thêm các thao tác. Cụ thể, bạn không cần chỉ định rằng thay đổi UpdateMovie có thể ảnh hưởng đến kết quả của truy vấn GetMovieById.
Điều này có thể xảy ra vì truy vấn GetMovieById ngầm ẩn nhận được tín hiệu làm mới từ thay đổi UpdateMovie. Các tín hiệu làm mới ngầm ẩn được gửi giữa một nhóm truy vấn và thay đổi mà bạn có thể viết:
Nếu truy vấn của bạn thực hiện tra cứu một thực thể theo khoá chính, thì mọi thay đổi ghi vào cùng một thực thể, cũng được xác định bằng khoá chính sẽ ngầm ẩn kích hoạt tín hiệu làm mới.
_insertvà_insertMany_upsertvà_upsertMany_update_delete
_deleteMany và _updateMany không gửi tín hiệu làm mới.
Trong ví dụ trước, truy vấn GetMovieById tra cứu một bộ phim theo mã nhận dạng (movie(id: $id)) và thay đổi UpdateMovie cập nhật một bộ phim, được chỉ định theo mã nhận dạng (movie_update(id: $id, ...)), vì vậy, truy vấn có thể tận dụng tính năng làm mới ngầm ẩn.
Các thao tác chèn và chèn/cập nhật có thể kích hoạt tín hiệu làm mới ngầm ẩn khi bạn đang sử dụng một giá trị đã biết, chẳng hạn như UID của người dùng Firebase Authentication.
Ví dụ: hãy xem xét một truy vấn như sau:
query GetExtendedProfileByUser @auth(level: USER) {
profile(key: { id_expr: "auth.uid" }) {
id
status
photoUrl
socialLink
}
}
Truy vấn sẽ ngầm ẩn nhận được tín hiệu làm mới từ một thay đổi như sau:
mutation UpsertExtendedProfile($status: String, $photoUrl: String, $socialLink: String) @auth(level: USER) {
profile_upsert(
data: {
id_expr: "auth.uid"
status: $status
photoUrl: $photoUrl
socialLink: $socialLink
}
) {
id
status
photoUrl
socialLink
}
}
Khi các truy vấn hoặc thay đổi của bạn phức tạp hơn, bạn sẽ cần chỉ định các điều kiện yêu cầu làm mới truy vấn. Hãy tiếp tục đến phần tiếp theo để tìm hiểu cách thực hiện.
Tín hiệu làm mới truy vấn rõ ràng
Ngoài các tín hiệu làm mới được thay đổi ngầm ẩn gửi đến các truy vấn, bạn cũng có thể chỉ định rõ ràng thời điểm một truy vấn sẽ nhận được tín hiệu làm mới. Bạn thực hiện việc này bằng cách chú thích các truy vấn bằng chỉ thị @refresh.
Bạn phải sử dụng chỉ thị @refresh bất cứ khi nào các truy vấn không đáp ứng các tiêu chí cụ thể (xem ở trên) để tự động làm mới. Dưới đây là một số ví dụ về các truy vấn phải bao gồm chỉ thị này:
- Các truy vấn truy xuất danh sách thực thể
- Các truy vấn thực hiện thao tác kết hợp trên các bảng khác
- Các truy vấn tổng hợp
- Các truy vấn sử dụng SQL gốc
- Các truy vấn sử dụng trình phân giải tuỳ chỉnh
Bạn có thể chỉ định chính sách làm mới theo hai cách:
Khoảng thời gian dựa trên thời gian
Làm mới truy vấn theo khoảng thời gian cố định.
Ví dụ: giả sử cơ sở người dùng rất tích cực có thể khiến xếp hạng tích luỹ của một bộ phim được cập nhật nhiều lần mỗi phút, đặc biệt là sau khi bộ phim được phát hành. Thay vì làm mới truy vấn mỗi khi xếp hạng thay đổi, bạn có thể làm mới truy vấn vài giây một lần để nhận thông tin cập nhật phản ánh kết quả tích luỹ của một số thay đổi.
# dataconnect/connector/operations.gql
query GetMovieRating($id: UUID!) @auth(level: PUBLIC) @refresh(every: {seconds: 30}) {
movie(id: $id) {
id
averageRating
}
}
Thực thi thay đổi
Làm mới truy vấn khi một thay đổi cụ thể được thực thi. Cách tiếp cận này cho biết rõ những thay đổi có khả năng thay đổi kết quả của truy vấn.
Ví dụ: giả sử bạn có một truy vấn truy xuất thông tin về nhiều bộ phim thay vì một bộ phim cụ thể. Truy vấn này sẽ làm mới bất cứ khi nào một thay đổi cập nhật bất kỳ bản ghi bộ phim nào.
query ListMovies($offset: Int)
@auth(level: PUBLIC, insecureReason: "Anyone can list all movies.")
@refresh(onMutationExecuted: { operation: "UpdateMovie" }) {
movies(limit: 10, offset: $offset) {
id
title
releaseYear
genre
description
}
}
Bạn cũng có thể chỉ định một điều kiện biểu thức CEL phải được đáp ứng để thay đổi kích hoạt quá trình làm mới truy vấn.
Bạn nên thực hiện việc này. Bạn càng chỉ định điều kiện chính xác thì càng ít tài nguyên cơ sở dữ liệu không cần thiết được sử dụng và ứng dụng của bạn sẽ càng phản hồi nhanh hơn.
Ví dụ: giả sử bạn có một truy vấn chỉ liệt kê các bộ phim thuộc một thể loại được chỉ định. Truy vấn này chỉ làm mới khi một thay đổi cập nhật một bộ phim thuộc cùng thể loại:
query ListMoviesByGenre($genre: String, $offset: Int)
@auth(level: PUBLIC, insecureReason: "Anyone can list movies.")
@refresh(onMutationExecuted: {
operation: "UpdateMovie",
condition: "request.variables.genre == mutation.variables.genre"
}) {
movies(
where: { genre: { eq: $genre } },
limit: 10,
offset: $offset) {
id
title
releaseYear
genre
description
}
}
Liên kết CEL trong các điều kiện @refresh
Biểu thức condition trong onMutationExecuted có quyền truy cập vào hai ngữ cảnh:
request
Trạng thái của truy vấn đang được đăng ký.
| Ép giấy tờ | Mô tả |
|---|---|
request.variables |
Các biến được truyền đến truy vấn (ví dụ: request.variables.id) |
request.auth.uid |
Firebase Authentication UID của người dùng đã thực thi truy vấn |
request.auth.token |
Từ điển các yêu cầu về mã thông báo Firebase Authentication cho người dùng đã thực thi truy vấn |
mutation
Trạng thái của thay đổi đã thực thi.
| Ép giấy tờ | Mô tả |
|---|---|
mutation.variables |
Các biến được truyền đến thay đổi (ví dụ: mutation.variables.movieId) |
mutation.auth.uid |
Firebase Authentication UID của người dùng đã thực thi thay đổi |
mutation.auth.token |
Từ điển các yêu cầu về mã thông báo Firebase Authentication cho người dùng đã thực thi thay đổi |
Các kiểu mẫu phổ biến
# Refresh only when the mutation targets the same entity
"request.variables.id == mutation.variables.id"
# Refresh only when the same user who subscribed makes a change
"request.auth.uid == mutation.auth.uid"
# Refresh when a specific field value matches a condition
"request.auth.uid == mutation.auth.uid && mutation.variables.status == 'PUBLISHED'"
# Refresh when a specific flag is set in the mutation
"mutation.variables.isPublic == true"
Nhiều chỉ thị @refresh
Bạn có thể chỉ định chỉ thị @refresh nhiều lần trên một truy vấn để kích hoạt quá trình làm mới bất cứ khi nào một trong các tiêu chí được chỉ định bởi một trong các chỉ thị @refresh được đáp ứng.
Ví dụ: truy vấn sau đây sẽ làm mới 30 giây một lần cũng như bất cứ khi nào một trong các thay đổi được chỉ định được thực thi:
query ListMovies($offset: Int)
@auth(level: PUBLIC, insecureReason: "Anyone can list all movies.")
@refresh(every: {seconds: 30})
@refresh(onMutationExecuted: { operation: "UpdateMovie" })
@refresh(onMutationExecuted: { operation: "BulkUpdateMovies" }) {
movies(limit: 10, offset: $offset) {
id
title
releaseYear
genre
description
}
}
Tài liệu tham khảo
Hãy xem tài liệu tham khảo về chỉ thị @refresh để biết thêm ví dụ.