Votre code client peut s'abonner à des requêtes pour obtenir des mises à jour en temps réel lorsque le résultat de la requête change.
Avant de commencer
Configurez la génération du SDK pour votre projet, comme décrit dans la documentation pour le Web, les plates-formes Apple et Flutter.
- Vous devez activer la mise en cache côté client pour tous les SDK générés. Plus précisément, chaque configuration de SDK doit contenir une déclaration semblable à la suivante :
clientCache: maxAge: 5s storage: ... # Optional.Les clients de votre application doivent utiliser une version récente du SQL Connect SDK principal :
- Apple : Firebase SQL Connect SDK pour Swift version 11.12.0 ou ultérieure
- Web : SDK JavaScript version 12.12.0 ou ultérieure
- Flutter :
firebase_data_connectversion 0.3.0 ou ultérieure
Régénérez vos SDK clients à l'aide de la version 15.14.0 de la CLI Firebase ou d'une version ultérieure.
S'abonner aux résultats des requêtes
Vous pouvez vous abonner à une requête pour répondre aux modifications apportées au résultat de la requête. Par exemple, supposons que vous ayez défini le schéma et les opérations suivants dans votre projet :
# 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
})
}
Pour vous abonner aux modifications apportées au résultat de l'exécution de 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 est également compatible avec la mise en cache et les abonnements en temps réel à l'aide de
TanStack. Lorsque vous spécifiez react: true ou angular: true dans votre
connector.yaml fichier, SQL Connect génère
des liaisons pour React ou Angular à l'aide de TanStack.
Ces liaisons peuvent fonctionner avec SQL Connect's prise en charge intégrée en temps réel, mais avec quelques difficultés. Nous vous recommandons d'utiliser les liaisons basées sur TanStack ou SQL Connect's prise en charge intégrée en temps réel, mais pas les deux.
Notez que l'implémentation en temps réel de SQL Connect's présente certains avantages par rapport aux liaisons TanStack :
- Mise en cache normalisée : SQL Connect implémente la mise en cache normalisée, ce qui améliore la cohérence des données, ainsi que l'efficacité de la mémoire et du réseau par rapport à la mise en cache au niveau de la requête. Avec la mise en cache normalisée, si une entité est mise à jour dans une zone de votre application, elle sera également mise à jour dans d'autres zones qui utilisent cette entité.
- Invalidation à distance : SQL Connect peut invalider à distance les entités mises en cache sur tous les appareils abonnés.
Si vous choisissez de ne pas utiliser TanStack, vous devez supprimer les paramètres react: true et angular: true de votre fichier 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
Importez le SDK généré de votre projet :
import 'package:flutter_app/dataconnect_generated/generated.dart';
Appelez ensuite la méthode subscribe() sur une référence de requête :
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);
}
});
Pour arrêter les mises à jour, vous pouvez appeler subscription.cancel().
Une fois que vous êtes abonné à la requête comme dans l'exemple précédent, vous recevrez des mises à jour chaque fois que le résultat de la requête spécifique change. Par exemple, si un autre client exécute la mutation UpdateMovie sur le même ID auquel vous êtes abonné, vous recevrez une mise à jour.
Signaux d'actualisation de requête implicites
Dans l'exemple précédent, vous avez pu vous abonner à une requête et obtenir des mises à jour en temps réel sans aucune modification supplémentaire de vos opérations. En particulier, vous n'avez pas eu besoin de spécifier que la mutation UpdateMovie peut affecter le résultat de la requête GetMovieById.
Cela est possible, car la requête GetMovieById reçoit implicitement un signal d'actualisation de la mutation UpdateMovie. Les signaux d'actualisation implicites sont envoyés entre un sous-ensemble des requêtes et des mutations que vous pouvez écrire :
Si votre requête effectue une recherche d'entité unique par clé primaire, toute mutation qui écrit dans la même entité, également identifiée par sa clé primaire, déclenchera implicitement un signal d'actualisation.
_insertet_insertMany_upsertet_upsertMany_update_delete
_deleteMany et _updateMany n'envoient pas de signaux d'actualisation.
Dans l'exemple précédent, la requête GetMovieById recherche un seul film par ID (movie(id: $id)) et la mutation UpdateMovie met à jour un seul film, spécifié par ID (movie_update(id: $id, ...)) ; la requête peut donc tirer parti de l'actualisation implicite.
Les opérations d'insertion et d'upsert peuvent déclencher des signaux d'actualisation implicites lorsque vous utilisez une valeur connue, telle que l'UID d'un utilisateur Firebase Authentication.
Prenons l'exemple d'une requête comme celle-ci :
query GetExtendedProfileByUser @auth(level: USER) {
profile(key: { id_expr: "auth.uid" }) {
id
status
photoUrl
socialLink
}
}
La requête recevra implicitement un signal d'actualisation d'une mutation comme celle-ci :
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
}
}
Lorsque vos requêtes ou mutations sont plus complexes, vous devez spécifier les conditions qui nécessitent une actualisation de la requête. Pour savoir comment procéder, passez à la section suivante.
Signaux d'actualisation de requête explicites
En plus des signaux d'actualisation envoyés implicitement par les mutations aux requêtes, vous pouvez également spécifier explicitement quand une requête doit recevoir un signal d'actualisation. Pour ce faire, annotez vos requêtes avec la directive @refresh.
L'utilisation de la directive @refresh est obligatoire lorsque vos requêtes ne répondent pas aux critères spécifiques (voir ci-dessus) pour l'actualisation automatique. Voici quelques exemples de requêtes qui doivent inclure cette directive :
- Requêtes qui récupèrent des listes d'entités
- Requêtes qui effectuent des jointures sur d'autres tables
- Requêtes d'agrégation
- Requêtes utilisant le SQL natif
- Requêtes utilisant des résolveurs personnalisés
Vous pouvez spécifier une stratégie d'actualisation de deux manières :
Intervalles basés sur le temps
Actualisez la requête à un intervalle fixe.
Par exemple, supposons que votre base d'utilisateurs très actifs puisse entraîner la mise à jour de la note cumulative d'un film plusieurs fois par minute, en particulier après sa sortie. Au lieu d'actualiser la requête chaque fois que la note change, vous pouvez l'actualiser toutes les quelques secondes pour obtenir des mises à jour reflétant le résultat cumulé de plusieurs mutations potentielles.
# dataconnect/connector/operations.gql
query GetMovieRating($id: UUID!) @auth(level: PUBLIC) @refresh(every: {seconds: 30}) {
movie(id: $id) {
id
averageRating
}
}
Exécution de la mutation
Actualisez la requête lorsqu'une mutation spécifique est exécutée. Cette approche indique explicitement quelles mutations sont susceptibles de modifier le résultat de la requête.
Supposons, par exemple, que vous ayez une requête qui récupère des informations sur plusieurs films au lieu d'un seul. Cette requête doit être actualisée chaque fois qu'une mutation a mis à jour l'un des enregistrements de 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
}
}
Vous pouvez également spécifier une condition d'expression CEL qui doit être remplie pour que la mutation déclenche une actualisation de la requête.
Nous vous recommandons vivement de le faire. Plus vous serez précis lorsque vous spécifierez la condition, moins vous consommerez de ressources de base de données inutiles et plus votre application sera réactive.
Supposons, par exemple, que vous ayez une requête qui ne répertorie que les films d'un genre spécifié. Cette requête ne doit être actualisée que lorsqu'une mutation met à jour un film du même genre :
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
}
}
Liaisons CEL dans les conditions @refresh
L'expression condition dans onMutationExecuted a accès à deux contextes :
request
État de la requête à laquelle vous êtes abonné.
| Liaison | Description |
|---|---|
request.variables |
Variables transmises à la requête (par exemple, request.variables.id) |
request.auth.uid |
Firebase Authentication UID de l'utilisateur qui a exécuté la requête |
request.auth.token |
Dictionnaire des revendications de jeton Firebase Authentication pour l'utilisateur qui a exécuté la requête |
mutation
État de la mutation qui a été exécutée.
| Liaison | Description |
|---|---|
mutation.variables |
Variables transmises à la mutation (par exemple, mutation.variables.movieId) |
mutation.auth.uid |
Firebase Authentication UID de l'utilisateur qui a exécuté la mutation |
mutation.auth.token |
Dictionnaire des revendications de jeton Firebase Authentication pour l'utilisateur qui a exécuté la mutation |
Schémas courants
# 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"
Plusieurs directives @refresh
Vous pouvez spécifier la directive @refresh plusieurs fois sur une requête pour déclencher une actualisation chaque fois que l'un des critères spécifiés par l'une des directives @refresh est rempli.
Par exemple, la requête suivante sera actualisée toutes les 30 secondes et chaque fois que l'une des mutations spécifiées est exécutée :
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éférence
Pour obtenir d'autres exemples, consultez la référence de la directive @refresh.