Ihr Clientcode kann Abfragen abonnieren, um Echtzeitaktualisierungen zu erhalten, wenn sich das Ergebnis der Abfrage ändert.
Hinweis
Richten Sie die SDK-Generierung für Ihr Projekt ein, wie in der Dokumentation für Web, Apple-Plattformen und Flutter beschrieben.
- Sie müssen das clientseitige Caching für alle generierten SDKs aktivieren. Jede SDK-Konfiguration muss eine Deklaration wie die folgende enthalten:
clientCache: maxAge: 5s storage: ... # Optional.Ihre App-Clients müssen eine aktuelle Version des SQL Connect Core SDK verwenden:
- Apple: Firebase SQL Connect SDK für Swift, Version 11.12.0 oder höher
- Web: JavaScript SDK, Version 12.12.0 oder höher
- Flutter:
firebase_data_connect, Version 0.3.0 oder höher
Generieren Sie Ihre Client-SDKs mit Version 15.14.0 oder höher der Firebase CLI neu.
Abfrageergebnisse abonnieren
Sie können eine Abfrage abonnieren, um auf Änderungen im Abfrageergebnis zu reagieren. Angenommen, Sie haben das folgende Schema und die folgenden Vorgänge in Ihrem Projekt definiert:
# 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
})
}
So abonnieren Sie Änderungen am Ergebnis der Ausführung von 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 unterstützt auch das Caching und Echtzeitabos mit
TanStack. Wenn Sie react: true oder angular: true in Ihrer
connector.yaml Datei angeben, generiert SQL Connect
Bindungen für React oder Angular mit TanStack.
Diese Bindungen können zusammen mit der integrierten Echtzeitunterstützung von SQL Connect's verwendet werden, aber nur mit einigen Schwierigkeiten. Wir empfehlen, entweder die TanStack-basierten Bindungen oder SQL Connect's integrierte Echtzeitunterstützung zu verwenden, aber nicht beides.
Die eigene Echtzeitimplementierung von SQL Connect's hat einige Vorteile gegenüber den TanStack-Bindungen:
- Normalisiertes Caching: SQL Connect implementiert normalisiertes Caching, wodurch die Datenkonsistenz sowie die Speicher- und Netzwerkeffizienz verbessert werden im Vergleich zum Caching auf Abfrageebene. Wenn eine Entität in einem Bereich Ihrer App aktualisiert wird, wird sie auch in anderen Bereichen aktualisiert, in denen diese Entität verwendet wird.
- Remote-Ungültigmachung: SQL Connect kann zwischengespeicherte Entitäten auf allen abonnierten Geräten remote ungültig machen.
Wenn Sie TanStack nicht verwenden möchten, entfernen Sie die Einstellungen react: true und angular: true aus der Datei 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
Importieren Sie das generierte SDK Ihres Projekts:
import 'package:flutter_app/dataconnect_generated/generated.dart';
Rufen Sie dann die Methode subscribe() für eine Abfragereferenz auf:
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);
}
});
Wenn Sie keine Aktualisierungen mehr erhalten möchten, können Sie subscription.cancel() aufrufen.
Sobald Sie die Abfrage wie im vorherigen Beispiel abonniert haben, erhalten Sie Aktualisierungen, wenn sich das Ergebnis der jeweiligen Abfrage ändert. Wenn beispielsweise ein anderer Client die Mutation UpdateMovie für dieselbe ID ausführt, die Sie abonniert haben, erhalten Sie eine Aktualisierung.
Implizite Aktualisierungssignale für Abfragen
Im vorherigen Beispiel konnten Sie eine Abfrage abonnieren und Echtzeitaktualisierungen erhalten, ohne zusätzliche Änderungen an Ihren Vorgängen vornehmen zu müssen. Insbesondere mussten Sie nicht angeben, dass sich die Mutation UpdateMovie auf das Ergebnis der Abfrage GetMovieById auswirken kann.
Das ist möglich, weil die Abfrage GetMovieById implizit ein Aktualisierungssignal von der Mutation UpdateMovie erhält. Implizite Aktualisierungssignale werden zwischen einer Teilmenge der Abfragen und Mutationen gesendet, die Sie möglicherweise schreiben:
Wenn Ihre Abfrage eine Suche nach einer einzelnen Entität anhand des Primärschlüssels ausführt, löst jede Mutation , die in dieselbe Entität schreibt und ebenfalls anhand ihres Primär schlüssels identifiziert wird , implizit ein Aktualisierungssignal aus.
_insertund_insertMany_upsertund_upsertMany_update_delete
_deleteMany und _updateMany senden keine Aktualisierungssignale.
Im vorherigen Beispiel sucht die Abfrage GetMovieById einen einzelnen Film anhand der ID (movie(id: $id)) und die Mutation UpdateMovie aktualisiert einen einzelnen Film, der anhand der ID angegeben wird (movie_update(id: $id, ...)). Die Abfrage kann also implizite Aktualisierungen nutzen.
Einfüge- und Upsert-Vorgänge können implizite Aktualisierungssignale auslösen, wenn Sie einen bekannten Wert verwenden, z. B. die UID eines Firebase Authentication Nutzers.
Betrachten Sie beispielsweise eine Abfrage wie die folgende:
query GetExtendedProfileByUser @auth(level: USER) {
profile(key: { id_expr: "auth.uid" }) {
id
status
photoUrl
socialLink
}
}
Die Abfrage würde implizit ein Aktualisierungssignal von einer Mutation wie der folgenden erhalten:
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
}
}
Wenn Ihre Abfragen oder Mutationen komplexer sind, müssen Sie die Bedingungen angeben, die eine Aktualisierung der Abfrage erfordern. Im nächsten Abschnitt erfahren Sie, wie das geht.
Explizite Aktualisierungssignale für Abfragen
Zusätzlich zu den Aktualisierungssignalen, die implizit von Mutationen an Abfragen gesendet werden, können Sie auch explizit angeben, wann eine Abfrage ein Aktualisierungssignal erhalten soll. Dazu versehen Sie Ihre Abfragen mit der Direktive @refresh.
Die Direktive @refresh ist immer dann erforderlich, wenn Ihre Abfragen die spezifischen Kriterien (siehe oben) für die automatische Aktualisierung nicht erfüllen. Beispiele für Abfragen, die diese Direktive enthalten müssen:
- Abfragen, die Listen von Entitäten abrufen
- Abfragen, die Joins mit anderen Tabellen ausführen
- Aggregationsabfragen
- Abfragen mit nativem SQL
- Abfragen mit benutzerdefinierten Resolvern
Sie haben zwei Möglichkeiten, eine Aktualisierungsrichtlinie anzugeben:
Zeitbasierte Intervalle
Aktualisieren Sie die Abfrage in einem festen Zeitintervall.
Angenommen, Ihre sehr aktive Nutzerbasis kann dazu führen, dass die kumulative Bewertung eines Films mehrmals pro Minute aktualisiert wird, insbesondere nach der Veröffentlichung des Films. Anstatt die Abfrage jedes Mal zu aktualisieren, wenn sich die Bewertung ändert, können Sie sie stattdessen alle paar Sekunden aktualisieren, um Aktualisierungen zu erhalten, die das kumulative Ergebnis von möglicherweise mehreren Mutationen widerspiegeln.
# dataconnect/connector/operations.gql
query GetMovieRating($id: UUID!) @auth(level: PUBLIC) @refresh(every: {seconds: 30}) {
movie(id: $id) {
id
averageRating
}
}
Ausführung von Mutationen
Aktualisieren Sie die Abfrage, wenn eine bestimmte Mutation ausgeführt wird. Bei diesem Ansatz wird explizit angegeben, welche Mutationen das Ergebnis der Abfrage ändern können.
Angenommen, Sie haben eine Abfrage, die Informationen zu mehreren Filmen anstelle eines bestimmten Films abruft. Diese Abfrage sollte aktualisiert werden, wenn eine Mutation einen der Filmdatensätze aktualisiert hat.
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
}
}
Sie können auch eine CEL-Ausdrucksbedingung angeben, die erfüllt sein muss, damit die Mutation eine Aktualisierung der Abfrage auslöst.
Das ist sehr empfehlenswert. Je genauer Sie die Bedingung angeben können, desto weniger unnötige Datenbankressourcen werden verbraucht und desto reaktionsschneller ist Ihre App.
Angenommen, Sie haben eine Abfrage, in der nur Filme eines bestimmten Genres aufgeführt sind. Diese Abfrage sollte nur aktualisiert werden, wenn eine Mutation einen Film im selben Genre aktualisiert:
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
}
}
CEL-Bindungen in @refresh-Bedingungen
Der Ausdruck condition in onMutationExecuted hat Zugriff auf zwei Kontexte:
request
Der Status der Abfrage, die abonniert wird.
| Bindung | Beschreibung |
|---|---|
request.variables |
An die Abfrage übergebene Variablen (z. B. request.variables.id) |
request.auth.uid |
Firebase Authentication UID des Nutzers, der die Abfrage ausgeführt hat |
request.auth.token |
Wörterbuch mit Firebase Authentication Tokenansprüchen für den Nutzer, der die Abfrage ausgeführt hat |
mutation
Der Status der ausgeführten Mutation.
| Bindung | Beschreibung |
|---|---|
mutation.variables |
An die Mutation übergebene Variablen (z.B. mutation.variables.movieId) |
mutation.auth.uid |
Firebase Authentication UID des Nutzers, der die Mutation ausgeführt hat |
mutation.auth.token |
Wörterbuch mit Firebase Authentication Tokenansprüchen für den Nutzer, der die Mutation ausgeführt hat |
Häufige Muster
# 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"
Mehrere @refresh-Direktiven
Sie können die Direktive @refresh mehrmals in einer Abfrage angeben, um eine Aktualisierung auszulösen, wenn eines der von einer der @refresh-Direktiven angegebenen Kriterien erfüllt ist.
Die folgende Abfrage wird beispielsweise alle 30 Sekunden und immer dann aktualisiert, wenn eine der angegebenen Mutationen ausgeführt wird:
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
}
}
Referenz
Weitere Beispiele finden Sie in der Referenz zur Direktive @refresh.