รับข้อมูลอัปเดตแบบเรียลไทม์จาก 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การสนับสนุนแบบเรียลไทม์ในตัว แต่ไม่ใช่ทั้ง 2 อย่าง

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

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

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

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

นำเข้า 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

ซึ่งเป็นไปได้เนื่องจากคำค้นหา 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 เนทีฟ
  • การค้นหาที่ใช้ตัวแก้ไขที่กำหนดเอง

คุณระบุนโยบายการรีเฟรชได้ 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"

คำสั่ง @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การอ้างอิงคำสั่ง