SQL Connect'ten anlık güncellemeler alma

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_connect 0.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.

  • _insert ve _insertMany
  • _upsert ve _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.