SQL Connect से रीयल-टाइम अपडेट पाना

आपका क्लाइंट कोड, क्वेरी की सदस्यता ले सकता है. इससे क्वेरी के नतीजे में बदलाव होने पर, उसे रीयल-टाइम में अपडेट मिल सकते हैं.

शुरू करने से पहले

  • वेब, Apple प्लैटफ़ॉर्म, और Flutter के दस्तावेज़ में बताए गए तरीके से, अपने प्रोजेक्ट के लिए एसडीके जनरेशन सेट अप करें.

    • आपको जनरेट किए गए सभी SDK टूल के लिए, क्लाइंट-साइड कैश मेमोरी की सुविधा चालू करनी होगी. खास तौर पर, हर एसडीके कॉन्फ़िगरेशन में इस तरह का एलान होना चाहिए:
    clientCache:
      maxAge: 5s
      storage: ... # Optional.
    
  • आपके ऐप्लिकेशन क्लाइंट को SQL Connect के कोर एसडीके का नया वर्शन इस्तेमाल करना होगा:

    • Apple: Firebase SQL Connect Swift के लिए SDK का 11.12.0 या इसके बाद का वर्शन
    • वेब: JavaScript SDK का 12.12.0 या इसके बाद का वर्शन
    • Flutter: firebase_data_connect 0.3.0 या इसके बाद का वर्शन
  • Firebase CLI के 15.14.0 या इसके बाद के वर्शन का इस्तेमाल करके, अपने क्लाइंट SDK टूल फिर से जनरेट करें.

क्वेरी के नतीजों की सदस्यता लेना

क्वेरी के नतीजे में होने वाले बदलावों के बारे में सूचना पाने के लिए, क्वेरी की सदस्यता ली जा सकती है. उदाहरण के लिए, मान लें कि आपके प्रोजेक्ट में यह स्कीमा और ये कार्रवाइयां तय की गई हैं:

# 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 को चलाने के बाद मिले नतीजे में हुए बदलावों की सूचना पाने के लिए:

वेब

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);

वेब (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 का इस्तेमाल करके कैश मेमोरी और रीयल-टाइम सदस्यताएं भी मैनेज करता है. connector.yaml फ़ाइल में react: true या angular: true तय करने पर, SQL Connect, TanStack का इस्तेमाल करके React या Angular के लिए बाइंडिंग जनरेट करता है.

ये बाइंडिंग, SQL Connect की रीयल-टाइम सहायता के साथ काम कर सकती हैं. हालांकि, ऐसा करने में कुछ समस्याएं आ सकती हैं. हमारा सुझाव है कि आप TanStack पर आधारित बाइंडिंग या SQL Connect में पहले से मौजूद रीयल टाइम सपोर्ट का इस्तेमाल करें. हालांकि, दोनों का एक साथ इस्तेमाल न करें.

ध्यान दें कि SQL Connect के रीयल-टाइम में लागू होने के कुछ फ़ायदे हैं. ये फ़ायदे, TanStack के बाइंडिंग से ज़्यादा हैं:

  • नॉर्मलाइज़ की गई कैश मेमोरी: SQL Connect, नॉर्मलाइज़ की गई कैश मेमोरी लागू करता है. इससे क्वेरी-लेवल की कैश मेमोरी की तुलना में, डेटा की एकरूपता के साथ-साथ मेमोरी और नेटवर्क की क्षमता भी बेहतर होती है. सामान्य की गई कैश मेमोरी की मदद से, अगर आपके ऐप्लिकेशन के किसी हिस्से में कोई इकाई अपडेट होती है, तो वह इकाई, ऐप्लिकेशन के उन सभी हिस्सों में भी अपडेट हो जाएगी जहां उसका इस्तेमाल किया जाता है.
  • दूर से अमान्य करना: SQL Connect, सदस्यता लेने वाले सभी डिवाइसों पर कैश मेमोरी में सेव की गई इकाइयों को दूर से अमान्य कर सकता है.

अगर आपको TanStack का इस्तेमाल नहीं करना है, तो आपको अपनी connector.yaml फ़ाइल से react: true और angular: true सेटिंग हटानी होंगी.

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

अपने प्रोजेक्ट के जनरेट किए गए एसडीके को इंपोर्ट करें:

import 'package:flutter_app/dataconnect_generated/generated.dart';

इसके बाद, क्वेरी के रेफ़रंस पर subscribe() वाले तरीके को कॉल करें:

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);
  }
});

अपडेट पाने की सुविधा बंद करने के लिए, subscription.cancel() पर कॉल करें.

ऊपर दिए गए उदाहरण की तरह, क्वेरी की सदस्यता लेने के बाद, आपको उस क्वेरी के नतीजे में बदलाव होने पर अपडेट मिलेंगे. उदाहरण के लिए, अगर कोई दूसरा क्लाइंट उसी आईडी पर UpdateMovie म्यूटेशन करता है जिसके लिए आपने सदस्यता ली है, तो आपको अपडेट मिलेगा.

क्वेरी रीफ़्रेश करने के इंप्लिसिट सिग्नल

ऊपर दिए गए उदाहरण में, आपने किसी क्वेरी की सदस्यता ली और आपको रीयल-टाइम अपडेट मिले. इसके लिए, आपको अपने ऑपरेशंस में कोई बदलाव नहीं करना पड़ा. खास तौर पर, आपको यह बताने की ज़रूरत नहीं थी कि UpdateMovie म्यूटेशन, GetMovieById क्वेरी के नतीजे पर असर डाल सकता है.

ऐसा इसलिए हो सकता है, क्योंकि UpdateMovie म्यूटेशन से GetMovieById क्वेरी को रीफ़्रेश होने का सिग्नल मिलता है. इम्प्लिसिट रीफ़्रेश सिग्नल, क्वेरी और म्यूटेशन के उस सबसेट के बीच भेजे जाते हैं जिन्हें लिखा जा सकता है:

अगर आपकी क्वेरी, प्राइमरी कुंजी के हिसाब से एक इकाई की जानकारी ढूंढती है, तो उसी इकाई में डेटा लिखने वाला कोई भी म्यूटेशन, जिसे उसकी प्राइमरी कुंजी से भी पहचाना जाता है, अपने-आप रीफ़्रेश सिग्नल ट्रिगर करेगा.

  • _insert और _insertMany
  • _upsert और _upsertMany
  • _update
  • _delete

_deleteMany और _updateMany रीफ़्रेश सिग्नल नहीं भेजते हैं.

पिछले उदाहरण में, GetMovieById क्वेरी, आईडी (movie(id: $id)) के हिसाब से किसी एक फ़िल्म को खोजती है. वहीं, UpdateMovie म्यूटेशन, आईडी (movie_update(id: $id, ...)) के हिसाब से किसी एक फ़िल्म को अपडेट करता है. इसलिए, क्वेरी में अपने-आप रीफ़्रेश होने की सुविधा का इस्तेमाल किया जा सकता है.

जब किसी जानी-पहचानी वैल्यू, जैसे कि Firebase Authentication उपयोगकर्ता के यूआईडी के आधार पर डेटा डाला जाता है और उसे अपडेट किया जाता है, तो इससे रीफ़्रेश करने के सिग्नल अपने-आप ट्रिगर हो सकते हैं.

उदाहरण के लिए, इस तरह की क्वेरी देखें:

query GetExtendedProfileByUser @auth(level: USER) {
  profile(key: { id_expr: "auth.uid" }) {
    id
    status
    photoUrl
    socialLink
  }
}

क्वेरी को इस तरह के म्यूटेशन से, रीफ़्रेश करने का सिग्नल अपने-आप मिल जाएगा:

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
  }
}

क्वेरी या म्यूटेशन ज़्यादा जटिल होने पर, आपको उन शर्तों के बारे में बताना होगा जिनके लिए क्वेरी को रीफ़्रेश करना ज़रूरी है. इसका तरीका जानने के लिए, अगले सेक्शन पर जाएं.

क्वेरी रीफ़्रेश करने के सिग्नल

क्वेरी में बदलाव होने पर, रीफ़्रेश सिग्नल अपने-आप भेजे जाते हैं. हालांकि, यह भी तय किया जा सकता है कि किसी क्वेरी को रीफ़्रेश सिग्नल कब मिलना चाहिए. इसके लिए, अपनी क्वेरी में @refresh डायरेक्टिव का इस्तेमाल करके एनोटेशन जोड़ें.

@refresh डायरेक्टिव का इस्तेमाल करना तब ज़रूरी होता है, जब आपकी क्वेरी, अपने-आप रीफ़्रेश होने की सुविधा के लिए तय की गई खास शर्तों (ऊपर देखें) को पूरा नहीं करती हैं. इस डायरेक्टिव को शामिल करने वाली क्वेरी के कुछ उदाहरण यहां दिए गए हैं:

  • ऐसी क्वेरी जिनसे इकाइयों की सूचियां मिलती हैं
  • अन्य टेबल पर जॉइन करने वाली क्वेरी
  • एग्रीगेशन क्वेरी
  • नेटिव एसक्यूएल का इस्तेमाल करके की गई क्वेरी
  • कस्टम रिज़ॉल्वर का इस्तेमाल करने वाली क्वेरी

रीफ़्रेश करने की नीति को दो तरीकों से तय किया जा सकता है:

समय के हिसाब से अंतराल

क्वेरी को तय किए गए समय अंतराल पर रीफ़्रेश करें.

उदाहरण के लिए, मान लीजिए कि आपके ऐप्लिकेशन पर उपयोगकर्ताओं की संख्या बहुत ज़्यादा है. ऐसे में, किसी फ़िल्म की रिलीज़ के बाद, हर मिनट में उसकी कुल रेटिंग कई बार अपडेट हो सकती है. रेटिंग में बदलाव होने पर, हर बार क्वेरी को रीफ़्रेश करने के बजाय, क्वेरी को हर कुछ सेकंड में रीफ़्रेश किया जा सकता है. इससे आपको ऐसे अपडेट मिलेंगे जिनमें कई म्यूटेशन के कुल नतीजे दिखेंगे.

# dataconnect/connector/operations.gql

query GetMovieRating($id: UUID!) @auth(level: PUBLIC) @refresh(every: {seconds: 30}) {
  movie(id: $id) {
    id
    averageRating
  }
}

म्यूटेशन को लागू करना

किसी खास म्यूटेशन के लागू होने पर क्वेरी को रीफ़्रेश करें. इस तरीके से यह साफ़ तौर पर पता चलता है कि किन म्यूटेशन से क्वेरी के नतीजे में बदलाव हो सकता है.

उदाहरण के लिए, मान लें कि आपके पास एक ऐसी क्वेरी है जो किसी एक फ़िल्म के बजाय, कई फ़िल्मों के बारे में जानकारी देती है. जब भी कोई म्यूटेशन, फ़िल्म के किसी रिकॉर्ड को अपडेट करता है, तब इस क्वेरी को रीफ़्रेश किया जाना चाहिए.

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
  }
}

इसके अलावा, सीईएल एक्सप्रेशन की ऐसी शर्त भी तय की जा सकती है जिसे पूरा करने पर, क्वेरी रीफ़्रेश करने के लिए म्यूटेशन ट्रिगर होगा.

हमारा सुझाव है कि आप ऐसा ज़रूर करें. शर्त तय करते समय, जितनी सटीक जानकारी दी जाएगी, डेटाबेस के उतने ही कम संसाधनों का इस्तेमाल होगा. साथ ही, आपका ऐप्लिकेशन उतना ही तेज़ी से काम करेगा.

उदाहरण के लिए, मान लें कि आपके पास एक ऐसी क्वेरी है जिसमें सिर्फ़ किसी खास शैली की फ़िल्में दिखाई गई हैं. यह क्वेरी सिर्फ़ तब रीफ़्रेश होनी चाहिए, जब म्यूटेशन से एक ही शैली की किसी फ़िल्म को अपडेट किया जाता है:

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 शर्तों में सीईएल बाइंडिंग

onMutationExecuted में मौजूद condition एक्सप्रेशन के पास दो कॉन्टेक्स्ट का ऐक्सेस होता है:

request

क्वेरी की सदस्यता की स्थिति.

बाइंडिंग ब्यौरा
request.variables क्वेरी में पास किए गए वैरिएबल (उदाहरण के लिए, request.variables.id)
request.auth.uid Firebase Authentication क्वेरी चलाने वाले उपयोगकर्ता का यूआईडी
request.auth.token क्वेरी करने वाले उपयोगकर्ता के लिए Firebase Authentication टोकन के दावों की डिक्शनरी
mutation

बदलाव की स्थिति, जिसे लागू किया गया है.

बाइंडिंग ब्यौरा
mutation.variables म्यूटेशन को पास किए गए वैरिएबल (जैसे, mutation.variables.movieId)
mutation.auth.uid Firebase Authentication म्यूटेशन करने वाले उपयोगकर्ता का यूआईडी
mutation.auth.token म्यूटेशन करने वाले उपयोगकर्ता के लिए Firebase Authentication टोकन के दावों की डिक्शनरी
सामान्य पैटर्न
# 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"

एक से ज़्यादा @refresh डायरेक्टिव

किसी क्वेरी पर @refresh डायरेक्टिव को कई बार तय किया जा सकता है. इससे, @refresh डायरेक्टिव में बताए गए किसी भी मानदंड के पूरा होने पर, क्वेरी अपने-आप रीफ़्रेश हो जाएगी.

उदाहरण के लिए, यहां दी गई क्वेरी हर 30 सेकंड में रीफ़्रेश होगी. साथ ही, जब भी बताए गए म्यूटेशन में से कोई एक म्यूटेशन लागू होगा, तब भी यह रीफ़्रेश होगी:

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
  }
}

संदर्भ

ज़्यादा उदाहरणों के लिए, @refresh डायरेक्टिव का रेफ़रंस देखें.