Kode klien Anda dapat berlangganan kueri untuk mendapatkan pembaruan real-time saat hasil kueri berubah.
Sebelum memulai
Siapkan pembuatan SDK untuk project Anda seperti yang dijelaskan dalam dokumentasi untuk web, platform Apple, dan Flutter.
- Anda harus mengaktifkan penyimpanan dalam cache sisi klien untuk semua SDK yang dihasilkan. Secara khusus, setiap konfigurasi SDK harus berisi pernyataan seperti berikut:
clientCache: maxAge: 5s storage: ... # Optional.Klien aplikasi Anda harus menggunakan SDK inti SQL Connect versi terbaru:
- Apple: Firebase SQL Connect SDK untuk Swift versi 11.12.0 atau yang lebih baru
- Web: JavaScript SDK versi 12.12.0 atau yang lebih baru
- Flutter:
firebase_data_connectversi 0.3.0 atau yang lebih baru
Buat ulang SDK klien Anda menggunakan Firebase CLI versi 15.14.0 atau yang lebih baru.
Berlangganan hasil kueri
Anda dapat berlangganan kueri untuk merespons perubahan pada hasil kueri. Misalnya, Anda memiliki skema dan operasi berikut yang ditentukan dalam project Anda:
# 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
})
}
Untuk berlangganan perubahan pada hasil menjalankan 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 juga mendukung caching dan langganan real-time menggunakan
TanStack. Saat Anda menentukan react: true atau angular: true dalam file
connector.yaml, SQL Connect akan membuat
binding untuk React atau Angular menggunakan TanStack.
Binding ini dapat berfungsi bersama dukungan real-time bawaan SQL Connect, tetapi hanya dengan beberapa kesulitan. Sebaiknya gunakan binding berbasis TanStack atau dukungan real time bawaan SQL Connect, tetapi jangan keduanya.
Perhatikan bahwa penerapan real-time SQL Connect sendiri memiliki beberapa keunggulan dibandingkan binding TanStack:
- Penyimpanan dalam cache yang dinormalisasi: SQL Connect mengimplementasikan penyimpanan dalam cache yang dinormalisasi, yang meningkatkan konsistensi data serta efisiensi memori dan jaringan dibandingkan dengan penyimpanan dalam cache tingkat kueri. Dengan penayangan cache yang dinormalisasi, jika entitas diperbarui di satu area aplikasi, entitas tersebut juga akan diperbarui di area lain yang menggunakan entitas tersebut.
- Pembatalan validasi dari jarak jauh: SQL Connect dapat membatalkan validasi entitas yang di-cache dari jarak jauh di semua perangkat yang berlangganan.
Jika Anda memilih untuk tidak menggunakan TanStack, Anda harus menghapus setelan react: true dan
angular: true dari file connector.yaml.
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
Mengimpor SDK yang dihasilkan project Anda:
import 'package:flutter_app/dataconnect_generated/generated.dart';
Kemudian, panggil metode subscribe() pada referensi kueri:
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);
}
});
Untuk menghentikan update, Anda dapat memanggil subscription.cancel().
Setelah berlangganan kueri seperti dalam contoh sebelumnya, Anda akan mendapatkan
update setiap kali hasil kueri tertentu berubah. Misalnya, jika
klien lain menjalankan mutasi UpdateMovie pada ID yang sama dengan yang Anda daftarkan, Anda akan mendapatkan update.
Sinyal pemuatan ulang kueri implisit
Dalam contoh sebelumnya, Anda dapat berlangganan kueri dan mendapatkan
update real-time tanpa modifikasi tambahan pada operasi Anda. Secara
khusus, Anda tidak perlu menentukan bahwa mutasi UpdateMovie dapat
memengaruhi hasil kueri GetMovieById.
Hal ini dapat terjadi karena kueri GetMovieById secara implisit mendapatkan sinyal
pembaruan dari mutasi UpdateMovie. Sinyal refresh implisit dikirim
di antara subset kueri dan mutasi yang mungkin Anda tulis:
Jika kueri Anda melakukan pencarian satu entity menurut kunci utama, maka mutasi apa pun yang menulis ke entity yang sama, yang juga diidentifikasi oleh kunci utamanya akan secara implisit memicu sinyal pembaruan.
_insertdan_insertMany_upsertdan_upsertMany_update_delete
_deleteMany dan _updateMany tidak mengirim sinyal refresh.
Pada contoh sebelumnya, kueri GetMovieById mencari satu film menurut ID
(movie(id: $id)) dan mutasi UpdateMovie memperbarui satu film,
yang ditentukan oleh ID (movie_update(id: $id, ...)), sehingga kueri dapat memanfaatkan
refresh implisit.
Operasi penyisipan dan upsert dapat memicu sinyal refresh implisit saat Anda menggunakan nilai yang diketahui, seperti UID pengguna Firebase Authentication.
Misalnya, pertimbangkan kueri seperti berikut:
query GetExtendedProfileByUser @auth(level: USER) {
profile(key: { id_expr: "auth.uid" }) {
id
status
photoUrl
socialLink
}
}
Kueri akan secara implisit menerima sinyal refresh dari mutasi seperti berikut:
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
}
}
Jika kueri atau mutasi Anda lebih rumit, Anda harus menentukan kondisi yang memerlukan refresh kueri. Lanjutkan ke bagian berikutnya untuk mempelajari caranya.
Sinyal pemuatan ulang kueri eksplisit
Selain sinyal pembaruan yang dikirim secara implisit oleh mutasi ke kueri, Anda juga dapat menentukan secara eksplisit kapan kueri harus menerima sinyal pembaruan. Anda dapat melakukannya dengan menganotasi kueri menggunakan direktif @refresh.
Penggunaan direktif @refresh diperlukan setiap kali kueri Anda tidak memenuhi kriteria spesifik (lihat di atas) untuk refresh otomatis. Beberapa contoh kueri yang harus menyertakan direktif ini meliputi:
- Kueri yang mengambil daftar entity
- Kueri yang melakukan penggabungan pada tabel lain
- Kueri Agregasi
- Kueri menggunakan SQL native
- Kueri menggunakan resolver kustom
Anda dapat menentukan kebijakan refresh dengan dua cara:
Interval berbasis waktu
Memuat ulang kueri pada interval waktu tetap.
Misalnya, anggaplah basis pengguna yang sangat aktif dapat menyebabkan rating kumulatif film diperbarui berkali-kali setiap menit, terutama setelah rilis film. Daripada memuat ulang kueri setiap kali rating berubah, Anda dapat memuat ulang kueri setiap beberapa detik untuk mendapatkan pembaruan yang mencerminkan hasil kumulatif dari beberapa mutasi.
# dataconnect/connector/operations.gql
query GetMovieRating($id: UUID!) @auth(level: PUBLIC) @refresh(every: {seconds: 30}) {
movie(id: $id) {
id
averageRating
}
}
Eksekusi mutasi
Memuat ulang kueri saat mutasi tertentu dijalankan. Pendekatan ini secara eksplisit menunjukkan mutasi mana yang berpotensi mengubah hasil kueri.
Misalnya, Anda memiliki kueri yang mengambil informasi tentang beberapa film, bukan satu film tertentu. Kueri ini harus diperbarui setiap kali mutasi telah memperbarui salah satu rekaman film.
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
}
}
Anda juga dapat menentukan kondisi ekspresi CEL yang harus dipenuhi agar mutasi memicu refresh kueri.
Tindakan ini sangat direkomendasikan. Makin presisi Anda saat menentukan kondisi, makin sedikit resource database yang tidak perlu akan digunakan, dan makin responsif aplikasi Anda.
Misalnya, Anda memiliki kueri yang mencantumkan film hanya dalam genre tertentu. Kueri ini hanya boleh diperbarui saat mutasi memperbarui film dalam genre yang sama:
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
}
}
Binding CEL dalam kondisi @refresh
Ekspresi condition di onMutationExecuted memiliki akses ke dua konteks:
request
Status kueri yang sedang dilanggan.
| Binding | Deskripsi |
|---|---|
request.variables |
Variabel yang diteruskan ke kueri (misalnya, request.variables.id) |
request.auth.uid |
Firebase Authentication UID pengguna yang menjalankan kueri |
request.auth.token |
Kamus klaim token Firebase Authentication untuk pengguna yang menjalankan kueri |
mutation
Status mutasi yang dieksekusi.
| Binding | Deskripsi |
|---|---|
mutation.variables |
Variabel yang diteruskan ke mutasi (misalnya, mutation.variables.movieId) |
mutation.auth.uid |
Firebase Authentication UID pengguna yang menjalankan mutasi |
mutation.auth.token |
Kamus klaim token Firebase Authentication untuk pengguna yang menjalankan mutasi |
Pola Umum
# 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"
Beberapa direktif @refresh
Anda dapat menentukan direktif @refresh beberapa kali pada kueri untuk memicu
pembaruan setiap kali salah satu kriteria yang ditentukan oleh salah satu
direktif @refresh terpenuhi.
Misalnya, kueri berikut akan di-refresh setiap 30 detik dan setiap kali salah satu mutasi yang ditentukan dijalankan:
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
}
}
Referensi
Lihat referensi direktif @refresh untuk contoh lainnya.