SQL Connect থেকে রিয়েল-টাইম আপডেট পান

কোয়েরির ফলাফল পরিবর্তিত হলে রিয়েল-টাইম আপডেট পাওয়ার জন্য আপনার ক্লায়েন্ট কোড কোয়েরিগুলোতে সাবস্ক্রাইব করতে পারে।

শুরু করার আগে

  • ওয়েব , অ্যাপল প্ল্যাটফর্ম এবং ফ্লাটারের ডকুমেন্টেশনে বর্ণিত পদ্ধতি অনুসরণ করে আপনার প্রোজেক্টের জন্য SDK জেনারেশন সেট আপ করুন।

    • আপনার তৈরি করা সমস্ত SDK-এর জন্য আপনাকে অবশ্যই ক্লায়েন্ট-সাইড ক্যাশিং সক্রিয় করতে হবে। বিশেষত, প্রতিটি SDK কনফিগারেশনে নিম্নলিখিতের মতো একটি ঘোষণা থাকতে হবে:
    clientCache:
      maxAge: 5s
      storage: ... # Optional.
    
  • আপনার অ্যাপ ক্লায়েন্টদের অবশ্যই SQL Connect কোর SDK-এর একটি সাম্প্রতিক সংস্করণ ব্যবহার করতে হবে:

    • অ্যাপল: সুইফট-এর জন্য Firebase SQL Connect এসডিকে সংস্করণ ১১.১২.০ বা নতুন
    • ওয়েব: জাভাস্ক্রিপ্ট এসডিকে সংস্করণ ১২.১২.০ বা নতুন
    • ফ্লাটার: 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);

ওয়েব (রিঅ্যাক্ট)

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 এর বিল্ট-ইন রিয়েল-টাইম সাপোর্ট ব্যবহার করুন, কিন্তু উভয়ই নয়

উল্লেখ্য যে, TanStack বাইন্ডিংয়ের তুলনায় SQL Connect এর নিজস্ব রিয়েল-টাইম বাস্তবায়নের কিছু সুবিধা রয়েছে:

  • নর্মালাইজড ক্যাশিং: SQL Connect নর্মালাইজড ক্যাশিং প্রয়োগ করে, যা কোয়েরি-লেভেল ক্যাশিংয়ের তুলনায় ডেটার সামঞ্জস্যতা এবং মেমরি ও নেটওয়ার্কের কার্যকারিতা উন্নত করে। নর্মালাইজড ক্যাশিংয়ের ফলে, আপনার অ্যাপের কোনো একটি অংশে কোনো এনটিটি আপডেট হলে, সেই এনটিটি ব্যবহারকারী অন্যান্য অংশেও তা আপডেট হয়ে যাবে।
  • দূরবর্তী বাতিলকরণ: SQL Connect সাবস্ক্রাইব করা সমস্ত ডিভাইসে দূরবর্তীভাবে ক্যাশ করা এনটিটিগুলোকে বাতিল করতে পারে।

আপনি যদি TanStack ব্যবহার না করার সিদ্ধান্ত নেন, তাহলে আপনার connector.yaml ফাইল থেকে react: true এবং angular: true সেটিংগুলো সরিয়ে ফেলুন।

আইওএস

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

ফ্লাটার

আপনার প্রোজেক্টের তৈরি করা SDK ইম্পোর্ট করুন:

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 কোয়েরির ফলাফলকে প্রভাবিত করতে পারে।

এটি সম্ভব কারণ GetMovieById কোয়েরিটি UpdateMovie মিউটেশন থেকে পরোক্ষভাবে একটি রিফ্রেশ সিগন্যাল পায়। আপনি যে ধরনের কোয়েরি এবং মিউটেশন লিখতে পারেন, তার একটি উপসেটের মধ্যে পরোক্ষ রিফ্রেশ সিগন্যাল পাঠানো হয়:

যদি আপনার কোয়েরি প্রাইমারি কী ব্যবহার করে কোনো একটিমাত্র এনটিটি খুঁজে বের করে , তাহলে প্রাইমারি কী দ্বারা চিহ্নিত একই এনটিটিতে ডেটা লেখার জন্য ব্যবহৃত যেকোনো মিউটেশন স্বয়ংক্রিয়ভাবে একটি রিফ্রেশ সিগন্যাল চালু করবে।

  • _insert এবং _insertMany
  • _upsert এবং _upsertMany
  • _update
  • _delete

_deleteMany এবং _updateMany রিফ্রেশ সিগন্যাল পাঠায় না।

পূর্ববর্তী উদাহরণে, GetMovieById কোয়েরিটি আইডি দ্বারা একটি একক মুভি খুঁজে বের করে ( movie(id: $id) ) এবং UpdateMovie মিউটেশনটি আইডি দ্বারা নির্দিষ্ট একটি একক মুভি আপডেট করে ( movie_update(id: $id, ...) ), ফলে কোয়েরিটি ইমপ্লিসিট রিফ্রেশের সুবিধা নিতে পারে।

যখন আপনি কোনো পরিচিত মান, যেমন Firebase Authentication ব্যবহারকারীর UID, ব্যবহার করে ডেটা এন্ট্রি করেন, তখন ইনসার্ট এবং আপসার্ট অপারেশনগুলো পরোক্ষ রিফ্রেশ সিগন্যাল ট্রিগার করতে পারে।

উদাহরণস্বরূপ, নিম্নলিখিতের মতো একটি কোয়েরি বিবেচনা করুন:

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 ডিরেক্টিভটি ব্যবহার করা আবশ্যক। যেসব কোয়েরিতে এই ডিরেক্টিভটি অবশ্যই অন্তর্ভুক্ত করতে হবে তার কিছু উদাহরণ হলো:

  • যে কোয়েরিগুলো এনটিটিগুলোর তালিকা পুনরুদ্ধার করে
  • অন্যান্য টেবিলের সাথে জয়েন সম্পাদনকারী কোয়েরি
  • সমষ্টিগত কোয়েরি
  • নেটিভ SQL ব্যবহার করে কোয়েরি
  • কাস্টম রিজলভার ব্যবহার করে কোয়েরি

আপনি দুইভাবে একটি রিফ্রেশ পলিসি নির্দিষ্ট করতে পারেন:

সময়-ভিত্তিক ব্যবধান

একটি নির্দিষ্ট সময় অন্তর কোয়েরিটি রিফ্রেশ করুন।

উদাহরণস্বরূপ, ধরুন আপনার অত্যন্ত সক্রিয় ব্যবহারকারীদের কারণে একটি সিনেমার মোট রেটিং প্রতি মিনিটে বহুবার আপডেট হতে পারে, বিশেষ করে সিনেমাটি মুক্তি পাওয়ার পর। প্রতিবার রেটিং পরিবর্তনের সাথে সাথে কোয়েরি রিফ্রেশ করার পরিবর্তে, আপনি প্রতি কয়েক সেকেন্ড পর পর কোয়েরিটি রিফ্রেশ করতে পারেন, যাতে সম্ভাব্য একাধিক পরিবর্তনের মোট ফলাফল প্রতিফলিত করে এমন আপডেট পাওয়া যায়।

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

আপনি একটি CEL এক্সপ্রেশন শর্তও নির্দিষ্ট করতে পারেন, যা মিউটেশনটি কোয়েরি রিফ্রেশ ট্রিগার করার জন্য অবশ্যই পূরণ করতে হবে।

এমনটা করার জন্য জোরালোভাবে সুপারিশ করা হচ্ছে। শর্তটি নির্দিষ্ট করার সময় আপনি যত বেশি সুনির্দিষ্ট হতে পারবেন, তত কম অপ্রয়োজনীয় ডাটাবেস রিসোর্স ব্যবহৃত হবে এবং আপনার অ্যাপ তত বেশি রেসপন্সিভ হবে।

উদাহরণস্বরূপ, ধরুন আপনার এমন একটি কোয়েরি আছে যা শুধুমাত্র একটি নির্দিষ্ট জনরার সিনেমাগুলো তালিকাভুক্ত করে। এই কোয়েরিটি কেবল তখনই রিফ্রেশ হওয়া উচিত যখন কোনো মিউটেশন একই জনরার কোনো সিনেমা আপডেট করে:

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 শর্তে CEL বাইন্ডিং

onMutationExecuted এর condition এক্সপ্রেশনটি দুটি কনটেক্সট অ্যাক্সেস করতে পারে:

request

যে কোয়েরিটি সাবস্ক্রাইব করা হচ্ছে তার অবস্থা।

বাঁধাই বর্ণনা
request.variables কোয়েরিতে পাঠানো ভেরিয়েবল (উদাহরণস্বরূপ, request.variables.id )
request.auth.uid যে ব্যবহারকারী কোয়েরিটি সম্পাদন করেছেন তার Firebase Authentication UID।
request.auth.token যে ব্যবহারকারী কোয়েরিটি সম্পাদন করেছেন, তার জন্য Firebase Authentication টোকেন ক্লেইমগুলোর ডিকশনারি।
mutation

সম্পাদিত রূপান্তরটির অবস্থা।

বাঁধাই বর্ণনা
mutation.variables মিউটেশনে পাঠানো ভেরিয়েবল (যেমন, mutation.variables.movieId )
mutation.auth.uid যে ব্যবহারকারী মিউটেশনটি সম্পাদন করেছেন তার Firebase Authentication UID।
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 ডিরেক্টিভগুলোর কোনো একটির দ্বারা নির্দিষ্ট করা শর্তগুলোর কোনো একটি পূরণ হয়।

উদাহরণস্বরূপ, নিম্নলিখিত কোয়েরিটি প্রতি ৩০ সেকেন্ড পর পর এবং সেইসাথে যখনই নির্দিষ্ট মিউটেশনগুলির মধ্যে কোনো একটি কার্যকর করা হবে, তখন রিফ্রেশ হবে:

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 ডিরেক্টিভের রেফারেন্স দেখুন।