您的用戶端程式碼可以訂閱查詢,在查詢結果變更時取得即時更新。
事前準備
按照網頁、Apple 平台和 Flutter 的說明文件,為專案設定 SDK 生成作業。
- 您必須為所有產生的 SDK 啟用用戶端快取。 具體來說,每個 SDK 設定都必須包含類似下列的宣告:
clientCache: maxAge: 5s storage: ... # Optional.應用程式用戶端必須使用新版的 SQL Connect 核心 SDK:
- Apple:Firebase SQL Connect適用於 Swift 的 SDK 11.12.0 以上版本
- 網頁:JavaScript SDK 12.12.0 以上版本
- Flutter:
firebase_data_connect0.3.0 以上版本
使用 Firebase CLI 15.14.0 以上版本重新產生用戶端 SDK。
訂閱查詢結果
您可以訂閱查詢,以便因應查詢結果的變化。舉例來說,假設您在專案中定義了下列結構定義和作業:
# 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
})
}
如要訂閱 GetMovieById 執行結果的變更:
網頁
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);
網頁 (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 也支援使用 TanStack 進行快取和即時訂閱。在 connector.yaml 檔案中指定 react: true 或 angular: true 時,SQL Connect 會使用 TanStack 為 React 或 Angular 產生繫結。
這些繫結可與 SQL Connect 的內建即時支援功能搭配使用,但只能在某種程度上運作。建議您使用以 TanStack 為基礎的繫結或 SQL Connect 的內建即時支援,但不要同時使用兩者。
請注意,SQL Connect 的即時實作方式相較於 TanStack 繫結,有以下優點:
- 正規化快取:SQL Connect 會實作正規化快取,與查詢層級快取相比,可提升資料一致性,以及記憶體和網路效率。透過正規化快取,如果實體在應用程式的某個區域更新,使用該實體的其他區域也會更新。
- 遠端失效:SQL Connect可遠端使所有已訂閱裝置上的快取實體失效。
如果選擇不使用 TanStack,請從 connector.yaml 檔案中移除 react: true 和 angular: true 設定。
iOS
struct ListMovieView: View {
// QueryRef has the Observable attribute, so its properties will
// automatically trigger updates on changes.
private var queryRef = connector.listMoviesByGenreQuery.ref(genre: "Sci-Fi")
// Store the handle to unsubscribe from query updates.
@State private var querySub: AnyCancellable?
var body: some View {
VStack {
// Use the query results in a View.
ForEach(queryRef.data?.movies ?? [], id: \.self.id) { movie in
Text(movie.title)
}
}
.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 {
querySub?.cancel()
}
}
}
Flutter
匯入專案產生的 SDK:
import 'package:flutter_app/dataconnect_generated/generated.dart';
然後對查詢參照呼叫 subscribe() 方法:
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);
}
});
如要停止更新,可以呼叫 subscription.cancel()。
如上例所示,訂閱查詢後,特定查詢的結果如有變更,您就會收到更新通知。舉例來說,如果另一個用戶端對您訂閱的相同 ID 執行 UpdateMovie 突變,您就會收到更新。
隱含查詢重新整理信號
在上述範例中,您能夠訂閱查詢並取得即時更新,無須對作業進行任何額外修改。具體來說,您不需要指定 UpdateMovie 突變會影響 GetMovieById 查詢的結果。
這是因為 GetMovieById 查詢會從 UpdateMovie 突變隱含取得重新整理訊號。系統會在您可能編寫的查詢和突變子集之間,傳送隱含重新整理信號:
如果查詢是依主鍵進行單一實體查詢,則任何變動 (寫入相同實體,也由主鍵識別) 都會隱含觸發重新整理信號。
_insert和_insertMany_upsert和_upsertMany_update_delete
_deleteMany 和 _updateMany 不會傳送重新整理信號。
在先前的範例中,GetMovieById 查詢會依 ID (movie(id: $id)) 查閱單一電影,而 UpdateMovie 突變會更新依 ID (movie_update(id: $id, ...)) 指定的單一電影,因此查詢可以利用隱含重新整理。
當您以已知值 (例如 Firebase Authentication 使用者的 UID) 做為鍵時,插入和 upsert 作業可能會觸發隱含的重新整理信號。
舉例來說,請考慮下列查詢:
query GetExtendedProfileByUser @auth(level: USER) {
profile(key: { id_expr: "auth.uid" }) {
id
status
photoUrl
socialLink
}
}
查詢會從下列變異隱含接收重新整理信號:
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
}
}
如果查詢或變動較為複雜,您需要指定需要重新整理查詢的條件。如要瞭解如何操作,請參閱下一節。
明確查詢重新整理信號
除了變動作業隱含傳送給查詢的重新整理信號外,您也可以明確指定查詢應接收重新整理信號的時間。方法是在查詢中加入 @refresh 指令。
如果查詢不符合自動重新整理的特定條件 (請參閱上文),就必須使用 @refresh 指令。以下列舉幾個必須加入這項指令的查詢範例:
- 擷取實體清單的查詢
- 對其他資料表執行聯結的查詢
- 匯總查詢
- 使用原生 SQL 查詢
- 使用自訂解析器的查詢
您可以透過下列兩種方式指定重新整理政策:
根據時間間隔
以固定時間間隔重新整理查詢。
舉例來說,假設您的使用者非常活躍,因此電影的累計評分每分鐘都會更新多次,特別是在電影上映後。與其在評分每次變更時重新整理查詢,不如每隔幾秒重新整理查詢,以取得反映可能多項變動累計結果的更新。
# dataconnect/connector/operations.gql
query GetMovieRating($id: UUID!) @auth(level: PUBLIC) @refresh(every: {seconds: 30}) {
movie(id: $id) {
id
averageRating
}
}
執行突變
在執行特定異動時重新整理查詢。這個方法會明確指出哪些突變可能會變更查詢結果。
舉例來說,假設您有一個查詢,可擷取多部電影的資訊,而非特定電影。只要變動更新了任何電影記錄,這項查詢就應重新整理。
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
}
}
您也可以指定 CEL 運算式條件,必須符合該條件,變動才會觸發查詢重新整理。
強烈建議您這麼做。指定條件時越精確,耗用的資料庫資源就越少,應用程式的回應速度也會越快。
舉例來說,假設您有一個查詢,只列出特定類型的電影。只有在變動更新相同類型的電影時,這項查詢才應重新整理:
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
}
}
@refresh 條件中的 CEL 繫結
onMutationExecuted 中的 condition 運算式可存取兩個內容:
request
訂閱查詢的狀態。
| 繫結 | 說明 |
|---|---|
request.variables |
傳遞至查詢的變數 (例如 request.variables.id) |
request.auth.uid |
Firebase Authentication 執行查詢的使用者 UID |
request.auth.token |
執行查詢的使用者 Firebase Authentication 權杖聲明字典 |
mutation
執行的變動狀態。
| 繫結 | 說明 |
|---|---|
mutation.variables |
傳遞至突變的變數 (例如 mutation.variables.movieId) |
mutation.auth.uid |
Firebase Authentication 執行變動的使用者 UID |
mutation.auth.token |
字典,其中包含執行突變的使用者權杖聲明Firebase Authentication |
常見模式
# 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"
多個 @refresh 指令
您可以在查詢中多次指定 @refresh 指令,只要其中一個 @refresh 指令指定的條件符合,就會觸發重新整理。
舉例來說,下列查詢會每 30 秒重新整理一次,並在執行指定突變時重新整理:
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
}
}
參考資料
如需更多範例,請參閱@refresh指令參考資料。