Müşteri kodunuz, sorgu sonuçları değiştiğinde gerçek zamanlı güncellemeler almak için sorgulara abone olabilir.
Başlamadan önce
Web, Apple platformları ve Flutter ile ilgili dokümanlarda açıklandığı şekilde projeniz için SDK oluşturmayı ayarlayın.
- Oluşturduğunuz tüm SDK'lar için istemci tarafı önbelleğe almayı etkinleştirmeniz gerekir. Özellikle, her SDK yapılandırması aşağıdaki gibi bir beyan içermelidir:
clientCache: maxAge: 5s storage: ... # Optional.Uygulama istemcileriniz, SQL Connect çekirdek SDK'sının yeni bir sürümünü kullanmalıdır:
- Apple: Firebase SQL Connect SDK'sının 11.12.0 veya daha yeni bir Swift sürümü
- Web: JavaScript SDK'sı 12.12.0 veya daha yeni bir sürüm
- Flutter:
firebase_data_connect0.3.0 veya daha yeni bir sürüm
Firebase CLI'nın 15.14.0 veya daha yeni bir sürümünü kullanarak istemci SDK'larınızı yeniden oluşturun.
Sorgu sonuçlarına abone olma
Sorgu sonucundaki değişikliklere yanıt vermek için sorguya abone olabilirsiniz. Örneğin, projenizde aşağıdaki şema ve işlemlerin tanımlandığını varsayalım:
# 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
})
}
GetMovieById çalıştırılması sonucundaki değişikliklere abone olmak için:
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, TanStack kullanılarak önbelleğe alma ve gerçek zamanlı abonelikleri de destekler. connector.yaml dosyanızda react: true veya angular: true belirttiğinizde SQL Connect, TanStack'i kullanarak React veya Angular için bağlamalar oluşturur.
Bu bağlamalar, SQL Connect'nın yerleşik gerçek zamanlı desteğiyle birlikte çalışabilir ancak yalnızca bazı zorluklarla. TanStack tabanlı bağlamaları veya SQL Connect'nın yerleşik gerçek zamanlı desteğini kullanmanızı öneririz ancak ikisini birden kullanmamanızı tavsiye ederiz.
SQL Connect'ın kendi gerçek zamanlı uygulamasının TanStack bağlamalarına göre bazı avantajları olduğunu unutmayın:
- Normalleştirilmiş önbelleğe alma: SQL Connect, sorgu düzeyinde önbelleğe almaya kıyasla veri tutarlılığının yanı sıra bellek ve ağ verimliliğini artıran normalleştirilmiş önbelleğe alma özelliğini uygular. Normalleştirilmiş önbelleğe alma sayesinde, bir öğe uygulamanızın bir alanında güncellenirse bu öğeyi kullanan diğer alanlarda da güncellenir.
- Uzaktan geçersiz kılma: SQL Connect, tüm abone olunan cihazlarda önbelleğe alınmış öğeleri uzaktan geçersiz kılabilir.
TanStack'i kullanmamayı seçerseniz react: true ve angular: true ayarlarını connector.yaml dosyanızdan kaldırmanız gerekir.
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
Projenizin oluşturulan SDK'sını içe aktarın:
import 'package:flutter_app/dataconnect_generated/generated.dart';
Ardından, bir sorgu referansında subscribe() yöntemini çağırın:
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);
}
});
Güncellemeleri durdurmak için subscription.cancel() numaralı telefonu arayabilirsiniz.
Önceki örnekte olduğu gibi sorguya abone olduğunuzda, belirli sorgunun sonucu her değiştiğinde güncellemeler alırsınız. Örneğin, başka bir istemci, abone olduğunuz aynı kimlikte UpdateMovie mutasyonunu yürütürse güncelleme alırsınız.
Örtülü sorgu yenileme sinyalleri
Önceki örnekte, işlemlerinizde herhangi bir ek değişiklik yapmadan bir sorguya abone olabilir ve gerçek zamanlı güncellemeler alabilirsiniz. Özellikle, UpdateMovie mutasyonunun GetMovieById sorgusunun sonucunu etkileyebileceğini belirtmeniz gerekmiyordu.
Bu durum, GetMovieById sorgusunun UpdateMovie mutasyonundan örtülü olarak yenileme sinyali almasından kaynaklanır. Örtülü yenileme sinyalleri, yazabileceğiniz sorguların ve mutasyonların bir alt kümesi arasında gönderilir:
Sorgunuz birincil anahtara göre tek öğe araması yapıyorsa aynı öğeye yazan tüm değişiklikler (birincil anahtarıyla da tanımlanır) yenileme sinyalini dolaylı olarak tetikler.
_insertve_insertMany_upsertve_upsertMany_update_delete
_deleteMany ve _updateMany yenileme sinyalleri göndermez.
Önceki örnekte, GetMovieById sorgusu kimliğe (movie(id: $id)) göre tek bir filme bakar ve UpdateMovie mutasyonu, kimliğe (movie_update(id: $id, ...)) göre belirtilen tek bir filmi günceller. Bu nedenle sorgu, örtülü yenilemeden yararlanabilir.
Ekleme ve güncelleme işlemleri, bilinen bir değeri (ör. Firebase Authentication kullanıcının UID'si) temel aldığınızda örtülü yenileme sinyallerini tetikleyebilir.
Örneğin, aşağıdaki gibi bir sorguyu ele alalım:
query GetExtendedProfileByUser @auth(level: USER) {
profile(key: { id_expr: "auth.uid" }) {
id
status
photoUrl
socialLink
}
}
Sorgu, aşağıdaki gibi bir mutasyondan örtülü olarak yenileme sinyali alır:
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
}
}
Sorgularınız veya mutasyonlarınız daha karmaşık olduğunda, sorgu yenileme gerektiren koşulları belirtmeniz gerekir. Nasıl yapılacağını öğrenmek için sonraki bölüme geçin.
Açık sorgu yenileme sinyalleri
Sorgulardaki değişikliklerle örtülü olarak gönderilen yenileme sinyallerine ek olarak, bir sorgunun ne zaman yenileme sinyali alması gerektiğini de açıkça belirtebilirsiniz. Bunu, sorgularınıza @refresh yönergesiyle açıklama ekleyerek yapabilirsiniz.
Sorgularınız otomatik yenileme için belirli ölçütleri (yukarıya bakın) karşılamadığında @refresh yönergesinin kullanılması gerekir. Bu yönergenin eklenmesi gereken sorgulara ilişkin bazı örnekler:
- Varlık listelerini alan sorgular
- Diğer tablolarda birleştirme işlemi yapan sorgular
- Toplama sorguları
- Yerel SQL kullanan sorgular
- Özel çözümleyicileri kullanan sorgular
Yenileme politikasını iki şekilde belirtebilirsiniz:
Zamana dayalı aralıklar
Sorguyu sabit bir zaman aralığında yenileyin.
Örneğin, çok aktif bir kullanıcı tabanınızın, özellikle bir filmin yayınlanmasından sonra, bir filmin kümülatif puanının her dakika birçok kez güncellenmesine neden olabileceğini varsayalım. Puan her değiştiğinde sorguyu yenilemek yerine, birkaç saniyede bir yenileyerek olası birkaç mutasyonun kümülatif sonucunu yansıtan güncellemeler alabilirsiniz.
# dataconnect/connector/operations.gql
query GetMovieRating($id: UUID!) @auth(level: PUBLIC) @refresh(every: {seconds: 30}) {
movie(id: $id) {
id
averageRating
}
}
Mutasyon yürütme
Belirli bir mutasyon yürütüldüğünde sorguyu yenileyin. Bu yaklaşım, hangi mutasyonların sorgunun sonucunu değiştirme potansiyeline sahip olduğunu açıkça belirtir.
Örneğin, belirli bir film yerine birden fazla film hakkında bilgi alan bir sorgunuz olduğunu varsayalım. Bu sorgu, bir mutasyon film kayıtlarından herhangi birini güncellediğinde yenilenmelidir.
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
}
}
Ayrıca, değişikliğin sorgu yenilemeyi tetiklemesi için karşılanması gereken bir CEL ifadesi koşulu da belirtebilirsiniz.
Bu işlemi yapmanız önemle tavsiye edilir. Koşulu belirtirken ne kadar hassas olursanız o kadar az gereksiz veritabanı kaynağı tüketilir ve uygulamanız o kadar hızlı yanıt verir.
Örneğin, yalnızca belirli bir türdeki filmleri listeleyen bir sorgunuz olduğunu varsayalım. Bu sorgu yalnızca aynı türdeki bir filmde değişiklik yapıldığında yenilenmelidir:
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
}
}
@refresh koşullarındaki CEL bağlamaları
onMutationExecuted içindeki condition ifadesi iki bağlama erişebilir:
request
Abone olunan sorgunun durumu.
| Bağlama | Açıklama |
|---|---|
request.variables |
Sorguya iletilen değişkenler (örneğin, request.variables.id) |
request.auth.uid |
Firebase Authentication Sorguyu yürüten kullanıcının UID'si |
request.auth.token |
Sorguyu yürüten kullanıcı için Firebase Authentication jeton talepleri sözlüğü |
mutation
Yürütülen mutasyonun durumu.
| Bağlama | Açıklama |
|---|---|
mutation.variables |
Mutasyona iletilen değişkenler (ör. mutation.variables.movieId) |
mutation.auth.uid |
Firebase Authentication Mutasyonu yürüten kullanıcının UID'si |
mutation.auth.token |
Değişikliği yapan kullanıcı için Firebase Authentication jeton talepleri sözlüğü |
Yaygın Kalıplar
# 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"
Birden çok @refresh yönergesi
@refresh yönergelerinden biri tarafından belirtilen ölçütlerden herhangi biri karşılandığında yenilemeyi tetiklemek için bir sorguda @refresh yönergesini birden çok kez belirtebilirsiniz.
Örneğin, aşağıdaki sorgu her 30 saniyede bir ve belirtilen mutasyonlardan biri yürütüldüğünde yenilenir:
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
}
}
Referans
Daha fazla örnek için @refresh yönerge referansına bakın.