يمكن لرمز العميل الاشتراك في طلبات البحث لتلقّي إشعارات في الوقت الفعلي عند تغيُّر نتيجة طلب البحث.
قبل البدء
اضبط إعدادات إنشاء حزمة تطوير البرامج (SDK) لمشروعك على النحو الموضّح في مستندات الويب ومنصات Apple وFlutter.
- يجب تفعيل التخزين المؤقت من جهة العميل لجميع حِزم SDK التي تم إنشاؤها. على وجه التحديد، يجب أن يحتوي كل إعداد لحزمة SDK على بيان على النحو التالي:
clientCache: maxAge: 5s storage: ... # Optional.يجب أن يستخدم عملاء تطبيقك إصدارًا حديثًا من حزمة SDK الأساسية الخاصة بـ "SQL Connect":
- Apple: الإصدار 11.12.0 أو إصدار أحدث من حزمة تطوير البرامج (SDK) للغة SwiftFirebase SQL Connect
- على الويب: الإصدار 12.12.0 أو إصدار أحدث من حزمة JavaScript SDK
- Flutter: الإصدار 0.3.0 أو إصدار أحدث من
firebase_data_connect
أعِد إنشاء حِزم تطوير البرامج (SDK) الخاصة بالعميل باستخدام الإصدار 15.14.0 من واجهة سطر الأوامر (CLI) لمنصة Firebase أو إصدار أحدث.
الاشتراك في نتائج طلب البحث
يمكنك الاشتراك في طلب بحث للردّ على التغييرات في نتيجة طلب البحث. على سبيل المثال، لنفترض أنّ لديك المخطط التالي والعمليات المحدّدة في مشروعك:
# 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، ولكن ليس كليهما.
يُرجى العِلم أنّ التنفيذ الخاص بمكتبة 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 يتلقّى ضمنيًا إشارة إعادة تحميل من عملية التغيير 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 المعرّف الفريد للمستخدم الذي نفّذ طلب البحث |
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 لمزيد من الأمثلة.