Kod klienta może subskrybować zapytania, aby otrzymywać aktualizacje w czasie rzeczywistym, gdy zmieni się wynik zapytania.
Zanim zaczniesz
Skonfiguruj generowanie pakietu SDK dla projektu zgodnie z opisem w dokumentacji dotyczącej internetu, platform Apple i Fluttera.
- Musisz włączyć buforowanie po stronie klienta we wszystkich wygenerowanych pakietach SDK. Każda konfiguracja pakietu SDK musi zawierać deklarację podobną do tej:
clientCache: maxAge: 5s storage: ... # Optional.Klienci aplikacji muszą korzystać z aktualnej wersji SQL Connectgłównego pakietu SDK:
- Apple: Firebase SQL Connect pakiet SDK dla Swift w wersji 11.12.0 lub nowszej
- Internet: pakiet JavaScript SDK w wersji 12.12.0 lub nowszej
- Flutter:
firebase_data_connectwersja 0.3.0 lub nowsza
Wygeneruj ponownie pakiety SDK klienta, używając wiersza poleceń Firebase w wersji 15.14.0 lub nowszej.
Subskrybowanie wyników zapytania
Możesz zasubskrybować zapytanie, aby reagować na zmiany w jego wynikach. Załóżmy na przykład, że w projekcie masz zdefiniowane te schematy i operacje:
# 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
})
}
Aby zasubskrybować zmiany w wyniku działania funkcji GetMovieById:
Sieć
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);
Internet (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 obsługuje też zapisywanie w pamięci podręcznej i subskrypcje w czasie rzeczywistym za pomocą TanStack. Jeśli w pliku connector.yaml określisz react: true lub angular: true, SQL Connect wygeneruje powiązania dla Reacta lub Angulara za pomocą TanStack.
Te powiązania mogą działać razem z wbudowaną obsługą w czasie rzeczywistym w SQL Connect, ale tylko z pewnymi trudnościami. Zalecamy używanie albo powiązań opartych na TanStack, albo wbudowanej obsługi czasu rzeczywistego w SQL Connect, ale nie obu tych rozwiązań naraz.
Pamiętaj, że własna implementacja SQL Connect w czasie rzeczywistym ma pewne zalety w porównaniu z powiązaniami TanStack:
- Znormalizowane buforowanie: SQL Connect implementuje znormalizowane buforowanie, które poprawia spójność danych oraz wydajność pamięci i sieci w porównaniu z buforowaniem na poziomie zapytań. Dzięki znormalizowanemu buforowaniu, jeśli element zostanie zaktualizowany w jednym obszarze aplikacji, zostanie też zaktualizowany w innych obszarach, które go używają.
- Zdalne unieważnianie: SQL Connect może zdalnie unieważniać zapisane w pamięci podręcznej jednostki na wszystkich subskrybowanych urządzeniach.
Jeśli nie chcesz korzystać z TanStack, usuń ustawienia react: true i angular: true z pliku 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
Zaimportuj wygenerowany pakiet SDK projektu:
import 'package:flutter_app/dataconnect_generated/generated.dart';
Następnie wywołaj metodę subscribe() na odwołaniu do zapytania:
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);
}
});
Aby zatrzymać aktualizacje, możesz zadzwonić pod numer subscription.cancel().
Gdy zasubskrybujesz zapytanie, jak w poprzednim przykładzie, będziesz otrzymywać aktualizacje za każdym razem, gdy zmieni się wynik tego zapytania. Jeśli na przykład inny klient wykona mutację UpdateMovie na tym samym identyfikatorze, do którego subskrybujesz, otrzymasz aktualizację.
Sygnały odświeżania zapytań niejawnych
W poprzednim przykładzie udało Ci się zasubskrybować zapytanie i otrzymywać aktualizacje w czasie rzeczywistym bez wprowadzania dodatkowych modyfikacji w operacjach. W szczególności nie musisz określać, że mutacja UpdateMovie może wpływać na wynik zapytania GetMovieById.
Jest to możliwe, ponieważ zapytanie GetMovieById niejawnie otrzymuje sygnał odświeżania z mutacji UpdateMovie. Sygnały niejawnego odświeżania są wysyłane
między podzbiorem zapytań i mutacji, które możesz napisać:
Jeśli zapytanie wykonuje wyszukiwanie pojedynczego elementu według klucza podstawowego, każda mutacja, która zapisuje dane w tym samym elemencie również identyfikowanym przez klucz podstawowy, będzie niejawnie wywoływać sygnał odświeżania.
_inserti_insertMany_upserti_upsertMany_update_delete
_deleteMany i _updateMany nie wysyłają sygnałów odświeżania.
W poprzednim przykładzie zapytanie GetMovieById wyszukuje jeden film według identyfikatora (movie(id: $id)), a mutacja UpdateMovie aktualizuje jeden film określony przez identyfikator (movie_update(id: $id, ...)), więc zapytanie może korzystać z niejawnego odświeżania.
Operacje wstawiania i wstawiania lub aktualizowania mogą wywoływać sygnały niejawnego odświeżania, gdy używasz znanego klucza, np. Firebase Authentication identyfikatora użytkownika.
Rozważmy na przykład takie zapytanie:
query GetExtendedProfileByUser @auth(level: USER) {
profile(key: { id_expr: "auth.uid" }) {
id
status
photoUrl
socialLink
}
}
Zapytanie otrzymałoby niejawny sygnał odświeżania z mutacji takiej jak ta:
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
}
}
Jeśli Twoje zapytania lub mutacje są bardziej skomplikowane, musisz określić warunki, które wymagają odświeżenia zapytania. W następnej sekcji dowiesz się, jak to zrobić.
Sygnały jawnego odświeżania zapytań
Oprócz sygnałów odświeżania, które są niejawnie wysyłane przez mutacje do zapytań, możesz też jawnie określić, kiedy zapytanie powinno otrzymać sygnał odświeżania. W tym celu dodaj do zapytań dyrektywę @refresh.
Użycie dyrektywy @refresh jest wymagane, gdy zapytania nie spełniają określonych kryteriów (patrz wyżej) automatycznego odświeżania. Przykłady zapytań, które muszą zawierać tę dyrektywę:
- Zapytania, które pobierają listy encji
- Zapytania, które wykonują złączenia z innymi tabelami
- Zapytania agregujące
- Zapytania z użyciem natywnego SQL
- Zapytania korzystające z niestandardowych resolverów
Zasady odświeżania możesz określić na 2 sposoby:
Przedziały czasowe
Odświeżanie zapytania w określonych odstępach czasu.
Załóżmy na przykład, że bardzo aktywna baza użytkowników może powodować, że łączna ocena filmu jest aktualizowana wiele razy na minutę, zwłaszcza po premierze. Zamiast odświeżać zapytanie za każdym razem, gdy zmienia się ocena, możesz odświeżać je co kilka sekund, aby otrzymywać aktualizacje odzwierciedlające skumulowany wynik potencjalnie kilku zmian.
# dataconnect/connector/operations.gql
query GetMovieRating($id: UUID!) @auth(level: PUBLIC) @refresh(every: {seconds: 30}) {
movie(id: $id) {
id
averageRating
}
}
Wykonywanie mutacji
Odświeżanie zapytania po wykonaniu określonej mutacji. Dzięki temu wyraźnie widać, które mutacje mogą zmienić wynik zapytania.
Załóżmy na przykład, że masz zapytanie, które pobiera informacje o wielu filmach, a nie o jednym konkretnym. To zapytanie powinno się odświeżać za każdym razem, gdy mutacja zaktualizuje którykolwiek z rekordów filmu.
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
}
}
Możesz też określić warunek wyrażenia CEL, który musi być spełniony, aby mutacja spowodowała odświeżenie zapytania.
Zdecydowanie zalecamy wykonanie tej czynności. Im dokładniej określisz warunek, tym mniej niepotrzebnych zasobów bazy danych zostanie wykorzystanych i tym szybciej będzie działać aplikacja.
Załóżmy na przykład, że masz zapytanie, które wyświetla tylko filmy z określonego gatunku. To zapytanie powinno być odświeżane tylko wtedy, gdy mutacja zaktualizuje film z tego samego gatunku:
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
}
}
Powiązania CEL w warunkach @refresh
Wyrażenie condition w onMutationExecuted ma dostęp do 2 kontekstów:
request
Stan zapytania, do którego subskrybujesz.
| Powiązanie | Opis |
|---|---|
request.variables |
Zmienne przekazywane do zapytania (np. request.variables.id) |
request.auth.uid |
Firebase Authentication Identyfikator UID użytkownika, który wykonał zapytanie. |
request.auth.token |
Słownik Firebase Authentication roszczeń dotyczących tokena użytkownika, który wykonał zapytanie. |
mutation
Stan wykonanej mutacji.
| Powiązanie | Opis |
|---|---|
mutation.variables |
Zmienne przekazywane do mutacji (np. mutation.variables.movieId) |
mutation.auth.uid |
Firebase Authentication UID użytkownika, który wykonał mutację |
mutation.auth.token |
Słownik Firebase Authentication roszczeń tokena użytkownika, który wykonał mutację. |
Typowe wzorce
# 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"
Wiele dyrektyw @refresh
Dyrektywę @refresh możesz określić w zapytaniu wiele razy, aby odświeżanie było wywoływane, gdy zostanie spełnione którekolwiek z kryteriów określonych przez jedną z dyrektyw @refresh.
Na przykład to zapytanie będzie odświeżane co 30 sekund, a także za każdym razem, gdy zostanie wykonana jedna z określonych mutacji:
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
}
}
Źródła wiedzy
Więcej przykładów znajdziesz w @refreshdokumentacji dyrektywy.