يمكن لرمز العميل الاشتراك في طلبات البحث لتلقّي تعديلات في الوقت الفعلي عند تغيُّر نتيجة طلب البحث.
قبل البدء
يمكنك إعداد إنشاء حزمة تطوير البرامج (SDK) لمشروعك كما هو موضّح في مستندات الويب ومنصّات Apple وFlutter.
- يجب تفعيل ميزة التخزين المؤقت من جهة العميل لجميع حزم تطوير البرامج (SDK) التي تم إنشاؤها. على وجه التحديد، يجب أن يحتوي كل إعداد لحزمة تطوير البرامج (SDK) على إعلان على النحو التالي:
clientCache: maxAge: 5s storage: ... # Optional.يجب أن تستخدِم تطبيقات العميل إصدارًا حديثًا من الـ SQL Connect الأساسية لتطوير البرامج (SDK):
- Apple: الإصدار 11.12.0 أو إصدار أحدث من حزمة Firebase SQL Connect SDK لنظام Swift
- الويب: الإصدار 12.12.0 أو إصدار أحدث من حزمة JavaScript SDK
- Flutter: الإصدار 0.3.0 أو إصدار أحدث من
firebase_data_connect
أعِد إنشاء حزم تطوير البرامج (SDK) للعميل باستخدام الإصدار 15.14.0 أو إصدار أحدث من Firebase CLI.
الاشتراك في نتائج طلب البحث
يمكنك الاشتراك في طلب بحث للاستجابة للتغييرات في نتيجة طلب البحث. على سبيل المثال، لنفترض أنّ لديك المخطط والعمليات التالية المحدّدة في مشروعك:
# 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's ميزة الدعم المضمّن في الوقت الفعلي، ولكن مع بعض الصعوبة. ننصحك باستخدام الروابط المستندة إلى 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, ...))، لذا يمكن لطلب البحث الاستفادة من ميزة إعادة التحميل الضمنية.
يمكن أن تؤدي عمليات الإدراج والتعديل إلى إرسال إشارات إعادة تحميل ضمنية عند استخدام قيمة معروفة، مثل رقم تعريف مستخدم 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 عندما لا تستوفي طلبات البحث المعايير المحدّدة (راجِع أعلاه) لإعادة التحميل التلقائية. في ما يلي بعض الأمثلة على طلبات البحث التي يجب أن تتضمّن هذا التوجيه:
- طلبات البحث التي تسترد قوائم الكيانات
- طلبات البحث التي تُجري عمليات ربط على جداول أخرى
- طلبات بحث التجميع
- طلبات البحث التي تستخدِم لغة 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
}
}
روابط CEL في شروط @refresh
يمكن لتعبير condition في onMutationExecuted الوصول إلى سياقَين:
request
حالة طلب البحث الذي يتم الاشتراك فيه.
| الربط | الوصف |
|---|---|
request.variables |
المتغيّرات التي تم تمريرها إلى طلب البحث (على سبيل المثال، request.variables.id) |
request.auth.uid |
Firebase Authentication رقم تعريف مستخدم Firebase Authentication للمستخدم الذي نفّذ طلب البحث |
request.auth.token |
قاموس بمطالبات رمز Firebase Authentication للمستخدم الذي نفّذ طلب البحث |
mutation
حالة عملية التغيير التي تم تنفيذها.
| الربط | الوصف |
|---|---|
mutation.variables |
المتغيّرات التي تم تمريرها إلى عملية التغيير (على سبيل المثال، mutation.variables.movieId) |
mutation.auth.uid |
Firebase Authentication رقم تعريف مستخدم 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 لمزيد من الأمثلة.