รับข้อมูลอัปเดตแบบเรียลไทม์จาก SQL Connect

โค้ดไคลเอ็นต์สามารถสมัครรับข้อมูลการค้นหาเพื่อรับข้อมูลอัปเดตแบบเรียลไทม์เมื่อผลการค้นหาเปลี่ยนแปลง

ก่อนเริ่มต้น

  • ตั้งค่าการสร้าง SDK สำหรับโปรเจ็กต์ตามที่อธิบายไว้ในเอกสารประกอบสำหรับ เว็บ, แพลตฟอร์ม Apple และ Flutter

    • คุณต้องเปิดใช้การแคชฝั่งไคลเอ็นต์สำหรับ SDK ทั้งหมดที่สร้างขึ้น โดยเฉพาะอย่างยิ่ง การกำหนดค่า SDK ทุกรายการต้องมีการประกาศลักษณะต่อไปนี้
    clientCache:
      maxAge: 5s
      storage: ... # Optional.
    
  • ไคลเอ็นต์แอปต้องใช้ SDK หลักของ SQL Connect เวอร์ชันล่าสุด

    • Apple: Firebase SQL Connect SDK สำหรับ Swift เวอร์ชัน 11.12.0 ขึ้นไป
    • เว็บ: JavaScript SDK เวอร์ชัน 12.12.0 ขึ้นไป
    • Flutter: firebase_data_connect เวอร์ชัน 0.3.0 ขึ้นไป
  • สร้าง SDK ไคลเอ็นต์ใหม่โดยใช้ Firebase CLI เวอร์ชัน 15.14.0 ขึ้นไป

การสมัครรับข้อมูลผลการค้นหา

คุณสามารถสมัครรับข้อมูลการค้นหาเพื่อตอบสนองต่อการเปลี่ยนแปลงในผลการค้นหา ตัวอย่างเช่น สมมติว่าคุณมีสคีมาและการดำเนินการต่อไปนี้ที่กำหนดไว้ในโปรเจ็กต์

# 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 เมื่อคุณระบุ react: true หรือ angular: true ในไฟล์ connector.yaml ของคุณ SQL Connect จะสร้างการเชื่อมโยงสำหรับ React หรือ Angular โดยใช้ TanStack

การเชื่อมโยงเหล่านี้สามารถทำงานควบคู่ไปกับการรองรับแบบเรียลไทม์ในตัวของ SQL Connect ได้ แต่จะทำได้ค่อนข้าง ยาก เราขอแนะนำให้คุณใช้การเชื่อมโยงที่อิงตาม TanStack หรือ SQL Connectการรองรับแบบเรียลไทม์ในตัวของ's แต่ อย่างใดอย่างหนึ่ง

โปรดทราบว่าการใช้งานแบบเรียลไทม์ของ SQL Connect's เองมีข้อดีบางประการเหนือการเชื่อมโยง TanStack ดังนี้

  • การแคชแบบปกติ: SQL Connect ใช้ การแคชแบบปกติ, ซึ่งช่วยปรับปรุงความสอดคล้องของข้อมูล รวมถึงประสิทธิภาพของหน่วยความจำและเครือข่าย เมื่อเทียบกับการแคชระดับการค้นหา การแคชแบบปกติจะทำให้เอนทิตีอัปเดตในส่วนหนึ่งของแอปและอัปเดตในส่วนอื่นๆ ที่ใช้เอนทิตีนั้นด้วย
  • การทำให้เป็นโมฆะจากระยะไกล: SQL Connect สามารถทำให้เอนทิตีที่แคชไว้ เป็นโมฆะจากระยะไกลในอุปกรณ์ทั้งหมดที่สมัครรับข้อมูล

หากเลือกที่จะไม่ใช้ TanStack คุณควรนำการตั้งค่า react: true และ angular: true ออกจากไฟล์ connector.yaml

iOS

struct MovieDetailsView: View {
    // QueryRef has the @Observable annotation, so its properties will
    // automatically trigger updates on changes.
    // Realtime subscriptions will keep the query results updated with changes.

    // Define the ref variable.
    // If parameters are known before hand, refs can be initialized here directly
    // else they can be initialized in the init for the view, like here
    @State private var queryRef: GetMovieByIdQuery.Ref

    // Store the handle to unsubscribe from query updates.
    // QueryRef can be used in multiple views.
    // Each view can separately subscribe / unsubscribe to updates
    // When there are no more subscribers to a QueryRef,
    // it will cancel automatic updates for that QueryRef.
    @State private var querySub: AnyCancellable?

    init(movieId: String) {
      // initialize the ref with the movieId
      queryRef = DataConnect.moviesConnector.getMovieByIdQuery.ref(movieId: movieId)
    }

    var body: some View {
        VStack {
            // Use the query results in a View.
            if let movie = queryRef.data?.movie {
              Text(movie.title)
              Text(mpvie.description)
              // other details
            } else {
              // if last fetch/update resulted in an error
              if error = queryRef.lastError {
                Text("Error loading movie")
              } else {
                Text("Loading movie ...")
              }
            }
        }
        .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 {
          // Calling cancel will unsubscribe from receiving updates.
          querySub?.cancel()
        }
    }
}

Flutter

นำเข้า 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, ...) ดังนั้นการค้นหาจึงใช้ประโยชน์จากการรีเฟรชโดยนัยได้

การดำเนินการแทรกและอัปเดตสามารถทริกเกอร์สัญญาณรีเฟรชโดยนัยได้เมื่อคุณ ใช้ค่าที่ทราบ เช่น UID ของผู้ใช้ 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
  }
}

เมื่อการค้นหาหรือการกลายพันธุ์มีความซับซ้อนมากขึ้น คุณจะต้องระบุเงื่อนไขที่ต้องมีการรีเฟรชการค้นหา โปรดดูวิธีดำเนินการในส่วนถัดไป

สัญญาณรีเฟรชการค้นหาอย่างชัดแจ้ง

นอกเหนือจากสัญญาณรีเฟรชที่การกลายพันธุ์ส่งไปยังการค้นหาโดยนัยแล้ว คุณยังระบุอย่างชัดแจ้งได้ด้วยว่าการค้นหาควรได้รับสัญญาณรีเฟรชเมื่อใด โดยใส่คำอธิบายประกอบในการค้นหาด้วย Directive @refresh

คุณต้องใช้ Directive @refresh ทุกครั้งที่การค้นหาไม่เป็นไปตามเกณฑ์เฉพาะ (ดูด้านบน) สำหรับการรีเฟรชอัตโนมัติ ตัวอย่างการค้นหาที่ต้องมี Directive นี้ ได้แก่

  • การค้นหาที่ดึงข้อมูลรายการเอนทิตี
  • การค้นหาที่ทำการรวมตารางอื่นๆ
  • การค้นหาการรวบรวม
  • การค้นหาที่ใช้ SQL ดั้งเดิม
  • การค้นหาที่ใช้ ตัวแก้ปัญหาที่กำหนดเอง

คุณระบุนโยบายรีเฟรชได้ 2 วิธีดังนี้

ช่วงเวลาตามเวลา

รีเฟรชการค้นหาตามช่วงเวลาที่กำหนด

ตัวอย่างเช่น ฐานผู้ใช้ที่ใช้งานอยู่มากอาจทำให้การให้คะแนนสะสมของภาพยนตร์อัปเดตหลายครั้งทุกนาที โดยเฉพาะอย่างยิ่งหลังจากที่ภาพยนตร์ออกฉาย แทนที่จะรีเฟรชการค้นหาทุกครั้งที่การให้คะแนนเปลี่ยนแปลง คุณอาจรีเฟรชการค้นหาทุกๆ 2-3 วินาทีเพื่อรับข้อมูลอัปเดตที่แสดงผลสะสมของการกลายพันธุ์หลายรายการ

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

การเชื่อมโยง CEL ในเงื่อนไข @refresh

นิพจน์ condition ใน onMutationExecuted มีสิทธิ์เข้าถึง 2 บริบท ได้แก่

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"

Directive @refresh หลายรายการ

คุณสามารถระบุ Directive @refresh หลายครั้งในการค้นหาเพื่อทริกเกอร์การรีเฟรชเมื่อใดก็ตามที่มีเกณฑ์ใดเกณฑ์หนึ่งที่ระบุโดย Directive @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
  }
}

ข้อมูลอ้างอิง

ดูตัวอย่างเพิ่มเติมได้ที่ข้อมูลอ้างอิง Directive @refresh