Tu código de cliente puede suscribirse a consultas para obtener actualizaciones en tiempo real cuando cambie el resultado de la consulta.
Antes de comenzar
Configura la generación del SDK para tu proyecto como se describe en la documentación para la Web, las plataformas de Apple y Flutter.
- Debes habilitar el almacenamiento en caché del cliente para todos los SDK generados. En particular, cada configuración del SDK debe contener una declaración como la siguiente:
clientCache: maxAge: 5s storage: ... # Optional.Los clientes de tu app deben usar una versión reciente del SQL Connect SDK principal:
- Apple: SDK Firebase SQL Connect para Swift versión 11.12.0 o posterior
- Web: SDK de JavaScript versión 12.12.0 o posterior
- Flutter:
firebase_data_connectversión 0.3.0 o posterior
Vuelve a generar los SDK de cliente con la versión 15.14.0 de Firebase CLI o una posterior.
Cómo suscribirse a los resultados de la consulta
Puedes suscribirte a una consulta para responder a los cambios en el resultado de la consulta. Por ejemplo, supongamos que tienes el siguiente esquema y las siguientes operaciones definidas en tu proyecto:
# 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
})
}
Para suscribirte a los cambios en el resultado de la ejecución de GetMovieById, haz lo siguiente:
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 también admite el almacenamiento en caché y las suscripciones en tiempo real con
TanStack. Cuando especificas react: true o angular: true en tu
connector.yaml archivo, SQL Connect genera
vinculaciones para React o Angular con TanStack.
Estas vinculaciones pueden funcionar junto con la compatibilidad integrada en tiempo real de SQL Connect, pero solo con cierta dificultad. Te recomendamos que uses las vinculaciones basadas en TanStack o SQL Connect's la compatibilidad integrada en tiempo real, pero no ambas.
Ten en cuenta que la implementación en tiempo real de SQL Connect's tiene algunas ventajas sobre las vinculaciones de TanStack:
- Almacenamiento en caché normalizado: SQL Connect implementa el almacenamiento en caché normalizado, lo que mejora la coherencia de los datos, así como la eficiencia de la memoria y la red en comparación con el almacenamiento en caché a nivel de la consulta. Con el almacenamiento en caché normalizado, si una entidad se actualiza en un área de tu app, también se actualizará en otras áreas que usen esa entidad.
- Invalidación remota: SQL Connect puede invalidar de forma remota las entidades almacenadas en caché en todos los dispositivos suscritos.
Si decides no usar TanStack, debes quitar la configuración react: true y angular: true de tu archivo 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
Importa el SDK generado de tu proyecto:
import 'package:flutter_app/dataconnect_generated/generated.dart';
Luego, llama al método subscribe() en una referencia de consulta:
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);
}
});
Para detener las actualizaciones, puedes llamar a subscription.cancel().
Una vez que te suscribas a la consulta como en el ejemplo anterior, recibirás actualizaciones cada vez que cambie el resultado de la consulta específica. Por ejemplo, si otro cliente ejecuta la mutación UpdateMovie en el mismo ID al que te suscribiste, recibirás una actualización.
Indicadores de actualización de consultas implícitos
En el ejemplo anterior, pudiste suscribirte a una consulta y obtener actualizaciones en tiempo real sin ninguna modificación adicional en tus operaciones. En particular, no necesitaste especificar que la mutación UpdateMovie puede afectar el resultado de la consulta GetMovieById.
Esto es posible porque la consulta GetMovieById obtiene implícitamente un indicador de actualización de la mutación UpdateMovie. Los indicadores de actualización implícitos se envían entre un subconjunto de las consultas y mutaciones que podrías escribir:
Si tu consulta realiza una búsqueda de una sola entidad por clave principal, cualquier mutación que escriba en la misma entidad, también identificada por su clave principal activará implícitamente un indicador de actualización.
_inserty_insertMany_upserty_upsertMany_update_delete
_deleteMany y _updateMany no envían indicadores de actualización.
En el ejemplo anterior, la consulta GetMovieById busca una sola película por ID (movie(id: $id)) y la mutación UpdateMovie actualiza una sola película, especificada por ID (movie_update(id: $id, ...)), por lo que la consulta puede aprovechar la actualización implícita.
Las operaciones de inserción y upsert pueden activar indicadores de actualización implícitos cuando usas un valor conocido, como el UID de un usuario Firebase Authentication.
Por ejemplo, considera una consulta como la siguiente:
query GetExtendedProfileByUser @auth(level: USER) {
profile(key: { id_expr: "auth.uid" }) {
id
status
photoUrl
socialLink
}
}
La consulta recibiría implícitamente un indicador de actualización de una mutación como la siguiente:
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
}
}
Cuando tus consultas o mutaciones sean más complicadas, deberás especificar las condiciones que requieren una actualización de la consulta. Continúa con la siguiente sección para obtener más información.
Indicadores de actualización de consultas explícitos
Además de los indicadores de actualización que las mutaciones envían implícitamente a las consultas, también puedes especificar de forma explícita cuándo una consulta debe recibir un indicador de actualización. Para ello, anota tus consultas con la directiva @refresh.
Se requiere el uso de la directiva @refresh siempre que tus consultas no cumplan con los criterios específicos (consulta más arriba) para la actualización automática. Algunos ejemplos de consultas que deben incluir esta directiva son los siguientes:
- Consultas que recuperan listas de entidades
- Consultas que realizan uniones en otras tablas
- Consultas de agregación
- Consultas que usan SQL nativo
- Consultas que usan resolutores personalizados
Puedes especificar una política de actualización de dos maneras:
Intervalos basados en el tiempo
Actualiza la consulta en un intervalo fijo.
Por ejemplo, supongamos que tu base de usuarios muy activa puede hacer que la calificación acumulativa de una película se actualice muchas veces por minuto, en particular después del lanzamiento de una película. En lugar de actualizar la consulta cada vez que cambia la calificación, puedes actualizarla cada pocos segundos para obtener actualizaciones que reflejen el resultado acumulativo de varias mutaciones.
# dataconnect/connector/operations.gql
query GetMovieRating($id: UUID!) @auth(level: PUBLIC) @refresh(every: {seconds: 30}) {
movie(id: $id) {
id
averageRating
}
}
Ejecución de mutaciones
Actualiza la consulta cuando se ejecuta una mutación específica. Este enfoque hace explícito qué mutaciones tienen el potencial de cambiar el resultado de la consulta.
Por ejemplo, supongamos que tienes una consulta que recupera información sobre varias películas en lugar de una específica. Esta consulta debe actualizarse cada vez que una mutación actualice cualquiera de los registros de películas.
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
}
}
También puedes especificar una condición de expresión CEL que se debe cumplir para que la mutación active una actualización de la consulta.
Se recomienda hacerlo. Cuanto más preciso puedas ser cuando especifiques la condición, menos recursos innecesarios de la base de datos se consumirán y más sensible será tu app.
Por ejemplo, supongamos que tienes una consulta que muestra películas solo en un género especificado. Esta consulta solo debe actualizarse cuando una mutación actualiza una película del mismo género:
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
}
}
Vinculaciones de CEL en condiciones @refresh
La expresión condition en onMutationExecuted tiene acceso a dos contextos:
request
Es el estado de la consulta a la que se suscribe.
| Vinculación | Descripción |
|---|---|
request.variables |
Variables que se pasan a la consulta (por ejemplo, request.variables.id) |
request.auth.uid |
Firebase Authentication UID del usuario que ejecutó la consulta |
request.auth.token |
Diccionario de Firebase Authentication declaraciones de token para el usuario que ejecutó la consulta |
mutation
Es el estado de la mutación que se ejecutó.
| Vinculación | Descripción |
|---|---|
mutation.variables |
Variables que se pasan a la mutación (p.ej., mutation.variables.movieId) |
mutation.auth.uid |
Firebase Authentication UID de Firebase Authentication del usuario que ejecutó la mutación |
mutation.auth.token |
Diccionario de declaraciones de token Firebase Authentication para el usuario que ejecutó la mutación |
Patrones comunes
# 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"
Varias directivas @refresh
Puedes especificar la directiva @refresh varias veces en una consulta para activar una actualización cada vez que se cumpla alguno de los criterios especificados por una de las directivas @refresh.
Por ejemplo, la siguiente consulta se actualizará cada 30 segundos y cada vez que se ejecute una de las mutaciones especificadas:
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
}
}
Reference
Consulta la referencia de la directiva @refresh para obtener más ejemplos.