1- قبل البدء
في هذا الدرس التطبيقي حول الترميز، ستتعرّف على بعض أساسيات Firebase لإنشاء تطبيقات Flutter للأجهزة الجوّالة على Android وiOS.
المتطلبات الأساسية
- التعرّف على Flutter
- حزمة تطوير البرامج (SDK) من Flutter
- محرِّر نصوص من اختيارك
ما ستتعرّف عليه
- كيفية إنشاء تطبيق للردّ على دعوات الأحداث والدردشة في دفتر الضيوف على أجهزة Android وiOS والويب وmacOS باستخدام Flutter
- كيفية مصادقة المستخدمين باستخدام Firebase Authentication ومزامنة البيانات مع Firestore
المتطلبات
أيّ من الأجهزة التالية:
- جهاز Android أو iOS متصل بالكمبيوتر ويعمل في وضع المطوّر
- محاكي iOS (يتطلب أدوات Xcode)
- محاكي Android (يتطلب الإعداد في استوديو Android)
ستحتاج أيضًا إلى ما يلي:
- متصفّح من اختيارك، مثل Google Chrome
- بيئة تطوير متكاملة أو محرِّر نص من اختيارك تم ضبطه باستخدام المكوّنات الإضافية Dart وFlutter، مثل استوديو Android أو Visual Studio Code
- أحدث إصدار
stable
من Flutter أوbeta
إذا كنت تحب استخدام الإصدارات التجريبية - حساب Google لإنشاء مشروعك على Firebase وإدارته
- سجّلت
Firebase
CLI الدخول إلى حسابك على Google.
2- الحصول على نموذج الرمز
نزِّل الإصدار الأول من مشروعك من GitHub:
- من سطر الأوامر، استنسِخ مستودع GitHub في الدليل
flutter-codelabs
:
git clone https://github.com/flutter/codelabs.git flutter-codelabs
يحتوي الدليل flutter-codelabs
على رمز مجموعة من ورشات عمل رموز برمجية. يمكن العثور على رمز هذا الدليل التعليمي في الدليل flutter-codelabs/firebase-get-to-know-flutter
. يحتوي الدليل على سلسلة من اللقطات التي توضّح الشكل الذي يجب أن يظهر به مشروعك في نهاية كل خطوة. على سبيل المثال، أنت في الخطوة الثانية.
- ابحث عن الملفات المطابقة لتنفيذ الخطوة الثانية:
cd flutter-codelabs/firebase-get-to-know-flutter/step_02
إذا أردت التقديم أو الاطّلاع على الشكل الذي يجب أن يظهر به العنصر بعد خطوة معيّنة، ابحث في الدليل الذي يحمل اسم الخطوة التي تهمّك.
استيراد التطبيق النموذجي
- افتح دليل
flutter-codelabs/firebase-get-to-know-flutter/step_02
أو استورِده في بيئة تطوير البرامج المتكاملة (IDE) المفضّلة لديك. يحتوي هذا الدليل على الرمز البرمجي الأوّلي للدرس التطبيقي حول الترميز، والذي يتألف من تطبيق Flutter Meetup غير القابل للاستخدام بعد.
تحديد موقع الملفات التي تحتاج إلى معالجة
الرمز البرمجي في هذا التطبيق موزّع على أدلة متعددة. يسهّل هذا التقسيم للوظائف العمل لأنّه يجمع الرموز البرمجية حسب الوظيفة.
- حدِّد موقع الملفات التالية:
lib/main.dart
: يحتوي هذا الملف على نقطة الدخول الرئيسية وأداة التطبيق المصغّرة.lib/home_page.dart
: يحتوي هذا الملف على التطبيق المصغّر للصفحة الرئيسية.lib/src/widgets.dart
: يحتوي هذا الملف على مجموعة من التطبيقات المصغّرة للمساعدة في توحيد نمط التطبيق. وهي تشكّل شاشة التطبيق المشغِّل.lib/src/authentication.dart
: يحتوي هذا الملف على عملية تنفيذ جزئية للمصادقة مع مجموعة من التطبيقات المصغّرة لإنشاء تجربة تسجيل دخول للمستخدمين في المصادقة المستندة إلى البريد الإلكتروني في Firebase. لم يتم استخدام هذه التطبيقات المصغّرة لمسار المصادقة في التطبيق المبدئي بعد، ولكن يمكنك إضافتها قريبًا.
يمكنك إضافة ملفات إضافية حسب الحاجة لإنشاء باقي أجزاء التطبيق.
مراجعة ملف lib/main.dart
يستفيد هذا التطبيق من حزمة google_fonts
لجعل Roboto الخط التلقائي في جميع أجزاء التطبيق. يمكنك استكشاف fonts.google.com واستخدام الخطوط التي تعثر عليها هناك في أجزاء مختلفة من التطبيق.
يمكنك استخدام التطبيقات المصغّرة المساعِدة من ملف lib/src/widgets.dart
بتنسيق Header
وParagraph
وIconAndDetail
. تزيل هذه التطبيقات المصغّرة الرموز المكرّرة لتقليل الازدحام في تنسيق الصفحة الموضّح في HomePage
. ويؤدي ذلك أيضًا إلى توفير مظهر وأسلوب متسقَين.
إليك شكل تطبيقك على Android وiOS والويب وmacOS:
3- إنشاء مشروع Firebase وضبطه
إنّ عرض معلومات الحدث مفيد للضيوف، ولكنّه ليس مفيدًا جدًا لأي شخص بمفرده. عليك إضافة بعض الوظائف الديناميكية إلى التطبيق. ولإجراء ذلك، عليك ربط Firebase بتطبيقك. وللبدء باستخدام Firebase، عليك إنشاء مشروع على Firebase وضبطه.
إنشاء مشروع على Firebase
- سجِّل الدخول إلى Firebase.
- في وحدة التحكّم، انقر على إضافة مشروع أو إنشاء مشروع.
- في حقل اسم المشروع، أدخِل Firebase-Flutter-Codelab، ثم انقر على متابعة.
- انقر على خيارات إنشاء المشروع. اقبل بنود Firebase، إذا طُلب منك ذلك، ولكن تخطّى إعداد "إحصاءات Google" لأنّك لن تستخدمها لهذا التطبيق.
لمزيد من المعلومات عن مشاريع Firebase، يُرجى الاطّلاع على مقالة فهم مشاريع Firebase.
يستخدم التطبيق منتجات Firebase التالية المتوفّرة لتطبيقات الويب:
- المصادقة: تتيح للمستخدمين تسجيل الدخول إلى تطبيقك.
- Firestore: تحفظ هذه الخدمة البيانات المنظَّمة في السحابة الإلكترونية وتتلقّى إشعارات فورية عند تغيُّر البيانات.
- قواعد أمان Firebase: تُستخدم لتأمين قاعدة بياناتك.
وتتطلّب بعض هذه المنتجات إعدادًا خاصًا أو عليك تفعيلها في وحدة تحكّم Firebase.
تفعيل مصادقة تسجيل الدخول باستخدام البريد الإلكتروني
- في لوحة نظرة عامة على المشروع في وحدة تحكُّم Firebase، وسِّع قائمة الإصدار.
- انقر على المصادقة > البدء > طريقة تسجيل الدخول > البريد الإلكتروني/كلمة المرور > تفعيل > حفظ.
إعداد Firestore
يستخدم تطبيق الويب Firestore لحفظ رسائل المحادثات واستلام رسائل محادثات جديدة.
في ما يلي كيفية إعداد Firestore في مشروعك على Firebase:
- في اللوحة اليمنى من "وحدة تحكّم Firebase"، وسِّع الإنشاء، ثم اختَر قاعدة بيانات Firestore.
- انقر على إنشاء قاعدة بيانات.
- اترك رقم تعريف قاعدة البيانات مضبوطًا على
(default)
. - اختَر موقعًا لقاعدة بياناتك، ثم انقر على التالي.
بالنسبة إلى التطبيق الحقيقي، عليك اختيار موقع قريب من المستخدمين. - انقر على البدء في وضع الاختبار. اقرأ بيان إخلاء المسؤولية عن قواعد الأمان.
في وقت لاحق من هذا الدليل التعليمي، ستضيف قواعد أمان لتأمين بياناتك. لا توزِّع تطبيقًا علنًا أو تعرضه بدون إضافة قواعد أمان لقاعدة بياناتك. - انقر على إنشاء.
4. ضبط إعدادات Firebase
لاستخدام Firebase مع Flutter، عليك إكمال المهام التالية لضبط مشروع Flutter لاستخدام مكتبات FlutterFire
بشكلٍ صحيح:
- أضِف تبعيات
FlutterFire
إلى مشروعك. - سجِّل المنصة المطلوبة في مشروع Firebase.
- نزِّل ملف الإعدادات الخاص بالمنصة، ثم أضِفه إلى الرمز.
في الدليل الرئيسي لتطبيقك المكتوب باستخدام Flutter، تتوفّر الأدلة الفرعية android
وios
وmacos
وweb
التي تحتوي على ملفات الضبط الخاصة بالنظام الأساسي لكل من iOS وAndroid على التوالي.
ضبط التبعيات
عليك إضافة مكتبات FlutterFire
لمنتجات Firebase التي تستخدمها في هذا التطبيق: Authentication وFirestore.
- من سطر الأوامر، أضِف التبعيات التالية:
$ flutter pub add firebase_core
حزمة firebase_core
هي الرمز البرمجي الشائع المطلوب لجميع مكوّنات Firebase Flutter الإضافية.
$ flutter pub add firebase_auth
تتيح حزمة firebase_auth
الدمج مع ميزة "المصادقة".
$ flutter pub add cloud_firestore
تتيح حزمة cloud_firestore
الوصول إلى مساحة تخزين بيانات Firestore.
$ flutter pub add provider
توفّر حزمة firebase_ui_auth
مجموعة من التطبيقات المصغّرة والأدوات لزيادة سرعة المطوّرين من خلال عمليات المصادقة.
$ flutter pub add firebase_ui_auth
لقد أضفت الحِزم المطلوبة، ولكن عليك أيضًا ضبط مشاريع iOS وAndroid وmacOS وWeb Runner لاستخدام Firebase بشكلٍ مناسب. يمكنك أيضًا استخدام حزمة provider
التي تتيح فصل منطق النشاط التجاري عن منطق العرض.
تثبيت واجهة سطر أوامر FlutterFire
تعتمد أداة FlutterFire CLI على أداة Firebase CLI الأساسية.
- ثبِّت واجهة برمجة تطبيقات Firebase على جهازك إذا لم يسبق لك إجراء ذلك.
- ثبِّت واجهة سطر أوامر FlutterFire:
$ dart pub global activate flutterfire_cli
بعد التثبيت، يصبح الطلب flutterfire
متاحًا على مستوى العالم.
ضبط إعدادات تطبيقاتك
تستخرج واجهة برمجة التطبيقات (CLI) المعلومات من مشروعك على Firebase وتطبيقات المشروع المحدّدة لإنشاء جميع الإعدادات لمنصّة معيّنة.
في جذر تطبيقك، نفِّذ الأمر configure
:
$ flutterfire configure
يرشدك أمر الضبط خلال العمليات التالية:
- اختَر مشروعًا على Firebase استنادًا إلى ملف
.firebaserc
أو من "وحدة تحكُّم Firebase". - تحديد الأنظمة الأساسية التي تريد ضبط الإعدادات لها، مثل Android وiOS وmacOS والويب
- حدِّد تطبيقات Firebase التي تريد استخراج الإعدادات منها. يحاول واجهة برمجة التطبيقات تلقائيًا مطابقة تطبيقات Firebase استنادًا إلى إعدادات مشروعك الحالية.
- أنشئ ملف
firebase_options.dart
في مشروعك.
ضبط إعدادات نظام التشغيل macOS
تُنشئ أداة Flutter على نظام التشغيل macOS تطبيقات محصورة تمامًا في مساحة تخزين مؤقت. بما أنّ هذا التطبيق مدمج مع الشبكة للتواصل مع خوادم Firebase، عليك ضبط إعدادات تطبيقك باستخدام امتيازات عميل الشبكة.
macos/Runner/DebugProfile.entitlements
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
<!-- Add the following two lines -->
<key>com.apple.security.network.client</key>
<true/>
</dict>
</plist>
macos/Runner/Release.entitlements
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<!-- Add the following two lines -->
<key>com.apple.security.network.client</key>
<true/>
</dict>
</plist>
لمزيد من المعلومات، يُرجى الاطّلاع على توافق Flutter مع أجهزة الكمبيوتر المكتبي.
5- إضافة وظيفة الردّ على الدعوة
بعد إضافة Firebase إلى التطبيق، يمكنك إنشاء زر RSVP يسجِّل المستخدمين من خلال المصادقة. بالنسبة إلى التطبيقات المتوافقة مع Android وiOS والويب، تتوفّر حِزم FirebaseUI Auth
مُعدّة مسبقًا، ولكن عليك إنشاء هذه الميزة لتطبيق Flutter.
يتضمّن المشروع الذي استردته سابقًا مجموعة من التطبيقات المصغّرة التي تُنفِّذ واجهة المستخدم لمعظم عملية المصادقة. يمكنك تنفيذ منطق النشاط التجاري لدمج ميزة "المصادقة" مع التطبيق.
إضافة منطق النشاط التجاري باستخدام حزمة Provider
استخدِم حزمة provider
لإتاحة عنصر مركزي لحالة التطبيق في شجرة التطبيقات التي تتضمّن تطبيقات Flutter المصغّرة:
- أنشئ ملفًا جديدًا باسم
app_state.dart
يتضمّن المحتوى التالي:
lib/app_state.dart
import 'package:firebase_auth/firebase_auth.dart'
hide EmailAuthProvider, PhoneAuthProvider;
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_ui_auth/firebase_ui_auth.dart';
import 'package:flutter/material.dart';
import 'firebase_options.dart';
class ApplicationState extends ChangeNotifier {
ApplicationState() {
init();
}
bool _loggedIn = false;
bool get loggedIn => _loggedIn;
Future<void> init() async {
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform);
FirebaseUIAuth.configureProviders([
EmailAuthProvider(),
]);
FirebaseAuth.instance.userChanges().listen((user) {
if (user != null) {
_loggedIn = true;
} else {
_loggedIn = false;
}
notifyListeners();
});
}
}
تعرِض عبارات import
حِزم Firebase Core وAuth، وتسحب حزمة provider
التي توفّر عنصر حالة التطبيق في جميع شجرة التطبيقات المصغّرة، وتتضمّن تطبيقات مصغّرة للمصادقة من حزمة firebase_ui_auth
.
يتحمّل عنصر حالة التطبيق ApplicationState
هذه المسؤولية الرئيسية في هذه الخطوة، وهي تنبيه شجرة التطبيقات المصغّرة بأنّه تم إجراء تعديل على حالة المصادقة.
لا تستخدِم مقدّم خدمة إلّا لإعلام التطبيق بحالة تسجيل دخول المستخدم. للسماح للمستخدم بتسجيل الدخول، استخدِم واجهات المستخدم التي تقدّمها حزمة firebase_ui_auth
، وهي طريقة رائعة لبدء تشغيل شاشات تسجيل الدخول في تطبيقاتك بسرعة.
دمج خطوات المصادقة
- عدِّل عمليات الاستيراد في أعلى ملف
lib/main.dart
:
lib/main.dart
import 'package:firebase_ui_auth/firebase_ui_auth.dart'; // new
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart'; // new
import 'package:google_fonts/google_fonts.dart';
import 'package:provider/provider.dart'; // new
import 'app_state.dart'; // new
import 'home_page.dart';
- اربط حالة التطبيق بإعداد التطبيق، ثم أضِف مسار المصادقة إلى
HomePage
:
lib/main.dart
void main() {
// Modify from here...
WidgetsFlutterBinding.ensureInitialized();
runApp(ChangeNotifierProvider(
create: (context) => ApplicationState(),
builder: ((context, child) => const App()),
));
// ...to here.
}
يؤدي التعديل على الدالة main()
إلى جعل حزمة موفِّر البيانات مسؤولة عن إنشاء مثيل لعنصر حالة التطبيق باستخدام التطبيق المصغّر ChangeNotifierProvider
. يتم استخدام فئة provider
هذه تحديدًا لأنّ عنصر حالة التطبيق ينشئ فئة ChangeNotifier
، ما يتيح لحزمة provider
معرفة وقت إعادة عرض التطبيقات المصغّرة التابعة.
- عدِّل تطبيقك للتعامل مع التنقّل إلى الشاشات المختلفة التي يوفّرها لك FirebaseUI، وذلك عن طريق إنشاء إعدادات
GoRouter
:
lib/main.dart
// Add GoRouter configuration outside the App class
final _router = GoRouter(
routes: [
GoRoute(
path: '/',
builder: (context, state) => const HomePage(),
routes: [
GoRoute(
path: 'sign-in',
builder: (context, state) {
return SignInScreen(
actions: [
ForgotPasswordAction(((context, email) {
final uri = Uri(
path: '/sign-in/forgot-password',
queryParameters: <String, String?>{
'email': email,
},
);
context.push(uri.toString());
})),
AuthStateChangeAction(((context, state) {
final user = switch (state) {
SignedIn state => state.user,
UserCreated state => state.credential.user,
_ => null
};
if (user == null) {
return;
}
if (state is UserCreated) {
user.updateDisplayName(user.email!.split('@')[0]);
}
if (!user.emailVerified) {
user.sendEmailVerification();
const snackBar = SnackBar(
content: Text(
'Please check your email to verify your email address'));
ScaffoldMessenger.of(context).showSnackBar(snackBar);
}
context.pushReplacement('/');
})),
],
);
},
routes: [
GoRoute(
path: 'forgot-password',
builder: (context, state) {
final arguments = state.uri.queryParameters;
return ForgotPasswordScreen(
email: arguments['email'],
headerMaxExtent: 200,
);
},
),
],
),
GoRoute(
path: 'profile',
builder: (context, state) {
return ProfileScreen(
providers: const [],
actions: [
SignedOutAction((context) {
context.pushReplacement('/');
}),
],
);
},
),
],
),
],
);
// end of GoRouter configuration
// Change MaterialApp to MaterialApp.router and add the routerConfig
class App extends StatelessWidget {
const App({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp.router(
title: 'Firebase Meetup',
theme: ThemeData(
buttonTheme: Theme.of(context).buttonTheme.copyWith(
highlightColor: Colors.deepPurple,
),
primarySwatch: Colors.deepPurple,
textTheme: GoogleFonts.robotoTextTheme(
Theme.of(context).textTheme,
),
visualDensity: VisualDensity.adaptivePlatformDensity,
useMaterial3: true,
),
routerConfig: _router, // new
);
}
}
ولكل شاشة نوع إجراء مختلف مرتبط بها استنادًا إلى الحالة الجديدة لمسار المصادقة. بعد معظم تغييرات الحالة في المصادقة، يمكنك إعادة التوجيه إلى شاشة مفضّلة، سواء كانت الشاشة الرئيسية أو شاشة مختلفة، مثل الملف الشخصي.
- في طريقة الإنشاء لفئة
HomePage
، أدمج حالة التطبيق مع التطبيق المصغّرAuthFunc
:
lib/home_page.dart
import 'package:firebase_auth/firebase_auth.dart' // new
hide EmailAuthProvider, PhoneAuthProvider; // new
import 'package:flutter/material.dart'; // new
import 'package:provider/provider.dart'; // new
import 'app_state.dart'; // new
import 'src/authentication.dart'; // new
import 'src/widgets.dart';
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Firebase Meetup'),
),
body: ListView(
children: <Widget>[
Image.asset('assets/codelab.png'),
const SizedBox(height: 8),
const IconAndDetail(Icons.calendar_today, 'October 30'),
const IconAndDetail(Icons.location_city, 'San Francisco'),
// Add from here
Consumer<ApplicationState>(
builder: (context, appState, _) => AuthFunc(
loggedIn: appState.loggedIn,
signOut: () {
FirebaseAuth.instance.signOut();
}),
),
// to here
const Divider(
height: 8,
thickness: 1,
indent: 8,
endIndent: 8,
color: Colors.grey,
),
const Header("What we'll be doing"),
const Paragraph(
'Join us for a day full of Firebase Workshops and Pizza!',
),
],
),
);
}
}
يمكنك إنشاء مثيل للتطبيق المصغّر AuthFunc
وتغليفه في تطبيق مصغّر Consumer
. Widget المستهلك هو الطريقة المعتادة لاستخدام حزمة provider
لإعادة إنشاء جزء من الشجرة عند تغيير حالة التطبيق. التطبيق المصغّر AuthFunc
هو التطبيقات المصغّرة التكميلية التي تختبرها.
اختبار عملية المصادقة
- في التطبيق، انقر على الزر ردّ على الدعوة لبدء
SignInScreen
.
- أدخِل عنوان بريد إلكتروني. إذا سبق لك التسجيل، سيطلب منك النظام إدخال كلمة مرور. وإذا لم يكن الأمر كذلك، سيطلب منك النظام إكمال نموذج التسجيل.
- أدخِل كلمة مرور تتضمن أقل من ستة أحرف للتحقّق من مسار معالجة الأخطاء. إذا كنت مسجّلاً، ستظهر لك كلمة المرور بدلاً من ذلك.
- أدخِل كلمات مرور غير صحيحة للتحقّق من مسار معالجة الأخطاء.
- أدخِل كلمة المرور الصحيحة. تظهر لك تجربة تسجيل الدخول التي تتيح للمستخدم إمكانية تسجيل الخروج.
6- كتابة الرسائل في Firestore
من الرائع معرفة أنّ المستخدمين سيزورون موقعك الإلكتروني، ولكن عليك منح الضيوف نشاطًا آخر في التطبيق. ماذا لو كان بإمكانهم ترك رسائل في دفتر ضيوف؟ يمكنهم مشاركة سبب حماسهم للحضور أو الأشخاص الذين يأملون مقابلتهم.
لتخزين رسائل المحادثة التي يكتبها المستخدمون في التطبيق، يمكنك استخدام Firestore.
نموذج البيانات
Firestore هي قاعدة بيانات NoSQL، ويتم تقسيم البيانات المخزّنة في قاعدة البيانات إلى مجموعات ومستندات وحقول ومجموعات فرعية. يمكنك تخزين كل رسالة من المحادثة كمستند في مجموعة guestbook
، وهي مجموعة من المستوى الأعلى.
إضافة رسائل إلى Firestore
في هذا القسم، يمكنك إضافة وظيفة تتيح للمستخدمين كتابة الرسائل في قاعدة البيانات. أولاً، تضيف حقل نموذج وزر إرسال، ثم تضيف الرمز الذي يربط هذه العناصر بقاعدة البيانات.
- أنشئ ملفًا جديدًا باسم
guest_book.dart
، وأضِف تطبيقًا مصغّرًاGuestBook
يتضمّن حالة لإنشاء عناصر واجهة المستخدم الخاصة بحقل الرسالة وزر الإرسال:
lib/guest_book.dart
import 'dart:async';
import 'package:flutter/material.dart';
import 'src/widgets.dart';
class GuestBook extends StatefulWidget {
const GuestBook({required this.addMessage, super.key});
final FutureOr<void> Function(String message) addMessage;
@override
State<GuestBook> createState() => _GuestBookState();
}
class _GuestBookState extends State<GuestBook> {
final _formKey = GlobalKey<FormState>(debugLabel: '_GuestBookState');
final _controller = TextEditingController();
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Form(
key: _formKey,
child: Row(
children: [
Expanded(
child: TextFormField(
controller: _controller,
decoration: const InputDecoration(
hintText: 'Leave a message',
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Enter your message to continue';
}
return null;
},
),
),
const SizedBox(width: 8),
StyledButton(
onPressed: () async {
if (_formKey.currentState!.validate()) {
await widget.addMessage(_controller.text);
_controller.clear();
}
},
child: Row(
children: const [
Icon(Icons.send),
SizedBox(width: 4),
Text('SEND'),
],
),
),
],
),
),
);
}
}
هناك نقطتان مثيرتان للاهتمام هنا. أولاً، يمكنك إنشاء نموذج حتى تتمكّن من التحقّق من أنّ الرسالة تحتوي على محتوى فعلي وعرض رسالة خطأ للمستخدم في حال عدم توفّر أي محتوى. للتحقّق من صحة نموذج، يمكنك الوصول إلى حالة النموذج من خلال النقر على GlobalKey
. لمزيد من المعلومات عن المفاتيح وكيفية استخدامها، اطّلِع على حالات استخدام المفاتيح.
يُرجى أيضًا ملاحظة طريقة عرض التطبيقات المصغّرة، حيث يتوفّر لديك Row
يتضمّن TextFormField
وStyledButton
، ويحتوي على Row
. يُرجى العلم أيضًا أنّ TextFormField
مُدرَج في أداة Expanded
، ما يفرض على TextFormField
ملء أي مساحة إضافية في الصف. لفهم سبب الحاجة إلى ذلك بشكل أفضل، اطّلِع على فهم القيود.
بعد أن أصبح لديك تطبيق مصغّر يتيح للمستخدم إدخال بعض النصوص لإضافتها إلى "دفتر الضيوف"، عليك عرضه على الشاشة.
- عدِّل نص
HomePage
لإضافة السطرَين التاليَين في نهاية عناصرListView
:
const Header("What we'll be doing"),
const Paragraph(
'Join us for a day full of Firebase Workshops and Pizza!',
),
// Add the following two lines.
const Header('Discussion'),
GuestBook(addMessage: (message) => print(message)),
على الرغم من أنّ هذا كافٍ لعرض التطبيق المصغّر، إلا أنّه غير كافٍ لإجراء أي إجراء مفيد. يمكنك تعديل هذا الرمز قريبًا ليصبح وظيفيًا.
معاينة التطبيق
عندما ينقر المستخدم على إرسال، يتم تشغيل مقتطف الرمز التالي. وتضيف هذه الدالة محتوى حقل إدخال الرسالة إلى مجموعة guestbook
في قاعدة البيانات. على وجه التحديد، تضيف طريقة addMessageToGuestBook
محتوى الرسالة إلى مستند جديد باستخدام معرّف يتم إنشاؤه تلقائيًا في مجموعة guestbook
.
يُرجى العِلم أنّ FirebaseAuth.instance.currentUser.uid
هو إشارة إلى المعرّف الفريد الذي يتم إنشاؤه تلقائيًا والذي تمنحه ميزة "المصادقة" لجميع المستخدمين الذين سجّلوا الدخول.
- في ملف
lib/app_state.dart
، أضِف طريقةaddMessageToGuestBook
. يمكنك ربط هذه الميزة بواجهة المستخدم في الخطوة التالية.
lib/app_state.dart
import 'package:cloud_firestore/cloud_firestore.dart'; // new
import 'package:firebase_auth/firebase_auth.dart'
hide EmailAuthProvider, PhoneAuthProvider;
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_ui_auth/firebase_ui_auth.dart';
import 'package:flutter/material.dart';
import 'firebase_options.dart';
class ApplicationState extends ChangeNotifier {
// Current content of ApplicationState elided ...
// Add from here...
Future<DocumentReference> addMessageToGuestBook(String message) {
if (!_loggedIn) {
throw Exception('Must be logged in');
}
return FirebaseFirestore.instance
.collection('guestbook')
.add(<String, dynamic>{
'text': message,
'timestamp': DateTime.now().millisecondsSinceEpoch,
'name': FirebaseAuth.instance.currentUser!.displayName,
'userId': FirebaseAuth.instance.currentUser!.uid,
});
}
// ...to here.
}
ربط واجهة المستخدم بقاعدة البيانات
لديك واجهة مستخدم يمكن للمستخدم من خلالها إدخال النص الذي يريد إضافته إلى دفتر الضيوف، ولديك الرمز البرمجي لإضافة الإدخال إلى Firestore. ما عليك الآن سوى ربط الجهازَين.
- في ملف
lib/home_page.dart
، أجرِ التغيير التالي على التطبيق المصغّرHomePage
:
lib/home_page.dart
import 'package:firebase_auth/firebase_auth.dart'
hide EmailAuthProvider, PhoneAuthProvider;
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'app_state.dart';
import 'guest_book.dart'; // new
import 'src/authentication.dart';
import 'src/widgets.dart';
class HomePage extends StatelessWidget {
const HomePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Firebase Meetup'),
),
body: ListView(
children: <Widget>[
Image.asset('assets/codelab.png'),
const SizedBox(height: 8),
const IconAndDetail(Icons.calendar_today, 'October 30'),
const IconAndDetail(Icons.location_city, 'San Francisco'),
Consumer<ApplicationState>(
builder: (context, appState, _) => AuthFunc(
loggedIn: appState.loggedIn,
signOut: () {
FirebaseAuth.instance.signOut();
}),
),
const Divider(
height: 8,
thickness: 1,
indent: 8,
endIndent: 8,
color: Colors.grey,
),
const Header("What we'll be doing"),
const Paragraph(
'Join us for a day full of Firebase Workshops and Pizza!',
),
// Modify from here...
Consumer<ApplicationState>(
builder: (context, appState, _) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (appState.loggedIn) ...[
const Header('Discussion'),
GuestBook(
addMessage: (message) =>
appState.addMessageToGuestBook(message),
),
],
],
),
),
// ...to here.
],
),
);
}
}
لقد استبدلت السطرَين اللذين أضفتهما في بداية هذه الخطوة بالتطبيق الكامل. يمكنك استخدام Consumer<ApplicationState>
مرة أخرى لإتاحة حالة التطبيق لجزء الشجرة الذي تعرضه. يتيح لك ذلك التفاعل مع مستخدم يُدخل رسالة في واجهة المستخدم وينشرها في قاعدة البيانات. في القسم التالي، يمكنك اختبار ما إذا كانت الرسائل المُضافة منشورة في قاعدة البيانات.
اختبار إرسال الرسائل
- سجِّل الدخول إلى التطبيق إذا لزم الأمر.
- أدخِل رسالة، مثل
Hey there!
، ثم انقر على إرسال.
يُسجِّل هذا الإجراء الرسالة في قاعدة بيانات Firestore. ومع ذلك، لا تظهر الرسالة في تطبيق Flutter الفعلي لأنّك لا تزال بحاجة إلى تنفيذ استرداد البيانات، وهو ما يمكنك تنفيذه في الخطوة التالية. ومع ذلك، في لوحة بيانات قاعدة البيانات في وحدة تحكّم Firebase، يمكنك الاطّلاع على رسالتك المُضافة في مجموعة guestbook
. إذا أرسلت المزيد من الرسائل، ستضيف المزيد من المستندات إلى مجموعة guestbook
. على سبيل المثال، راجِع مقتطف الرمز التالي:
7- قراءة الرسائل
من الرائع أنّه يمكن للضيوف كتابة رسائل في قاعدة البيانات، ولكن لا يمكنهم الاطّلاع عليها في التطبيق بعد. حان الوقت لحلّ هذه المشكلة.
مزامنة الرسائل
لعرض الرسائل، عليك إضافة مستمعين يتم تفعيلهم عند تغيير البيانات، ثم إنشاء عنصر واجهة مستخدم يعرض الرسائل الجديدة. يمكنك إضافة رمز إلى حالة التطبيق يستمع إلى الرسائل المُضافة حديثًا من التطبيق.
- أنشئ ملفًا جديدًا
guest_book_message.dart
، وأضِف الفئة التالية لعرض عرض منظَّم للبيانات التي تخزّنها في Firestore.
lib/guest_book_message.dart
class GuestBookMessage {
GuestBookMessage({required this.name, required this.message});
final String name;
final String message;
}
- في ملف
lib/app_state.dart
، أضِف عمليات الاستيراد التالية:
lib/app_state.dart
import 'dart:async'; // new
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart'
hide EmailAuthProvider, PhoneAuthProvider;
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_ui_auth/firebase_ui_auth.dart';
import 'package:flutter/material.dart';
import 'firebase_options.dart';
import 'guest_book_message.dart'; // new
- في قسم
ApplicationState
الذي تحدِّد فيه الحالة ووظائف الحصول على البيانات، أضِف السطور التالية:
lib/app_state.dart
bool _loggedIn = false;
bool get loggedIn => _loggedIn;
// Add from here...
StreamSubscription<QuerySnapshot>? _guestBookSubscription;
List<GuestBookMessage> _guestBookMessages = [];
List<GuestBookMessage> get guestBookMessages => _guestBookMessages;
// ...to here.
- في قسم الإعداد في
ApplicationState
، أضِف السطور التالية للاشتراك في طلب بحث على مجموعة المستندات عندما يسجّل المستخدم الدخول وإلغاء الاشتراك عند تسجيل الخروج:
lib/app_state.dart
Future<void> init() async {
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform);
FirebaseUIAuth.configureProviders([
EmailAuthProvider(),
]);
FirebaseAuth.instance.userChanges().listen((user) {
if (user != null) {
_loggedIn = true;
_guestBookSubscription = FirebaseFirestore.instance
.collection('guestbook')
.orderBy('timestamp', descending: true)
.snapshots()
.listen((snapshot) {
_guestBookMessages = [];
for (final document in snapshot.docs) {
_guestBookMessages.add(
GuestBookMessage(
name: document.data()['name'] as String,
message: document.data()['text'] as String,
),
);
}
notifyListeners();
});
} else {
_loggedIn = false;
_guestBookMessages = [];
_guestBookSubscription?.cancel();
}
notifyListeners();
});
}
هذا القسم مهم لأنّه المكان الذي تُنشئ فيه طلب بحث عن مجموعة guestbook
، وتتعامل فيه مع الاشتراك في هذه المجموعة وإلغاء الاشتراك فيها. يمكنك الاستماع إلى البث، حيث يمكنك إعادة إنشاء ذاكرة تخزين مؤقتة على الجهاز للرسائل في مجموعة guestbook
، بالإضافة إلى تخزين مرجع لهذا الاشتراك حتى تتمكّن من إلغاء الاشتراك فيه لاحقًا. هناك الكثير من الإجراءات التي يتم تنفيذها هنا، لذا عليك استكشافها في أداة تصحيح الأخطاء لفحص ما يحدث والحصول على نموذج ذهني أكثر وضوحًا. لمزيد من المعلومات، يُرجى الاطّلاع على مقالة الحصول على آخر المعلومات في الوقت الفعلي باستخدام Firestore.
- في ملف
lib/guest_book.dart
، أضِف عملية الاستيراد التالية:
import 'guest_book_message.dart';
- في تطبيق
GuestBook
المصغّر، أضِف قائمة بالرسائل كجزء من الإعداد لربط هذه الحالة المتغيّرة بواجهة المستخدم:
lib/guest_book.dart
class GuestBook extends StatefulWidget {
// Modify the following line:
const GuestBook({
super.key,
required this.addMessage,
required this.messages,
});
final FutureOr<void> Function(String message) addMessage;
final List<GuestBookMessage> messages; // new
@override
_GuestBookState createState() => _GuestBookState();
}
- في
_GuestBookState
، عدِّل طريقةbuild
على النحو التالي لعرض هذه الإعدادات:
lib/guest_book.dart
class _GuestBookState extends State<GuestBook> {
final _formKey = GlobalKey<FormState>(debugLabel: '_GuestBookState');
final _controller = TextEditingController();
@override
// Modify from here...
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// ...to here.
Padding(
padding: const EdgeInsets.all(8.0),
child: Form(
key: _formKey,
child: Row(
children: [
Expanded(
child: TextFormField(
controller: _controller,
decoration: const InputDecoration(
hintText: 'Leave a message',
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Enter your message to continue';
}
return null;
},
),
),
const SizedBox(width: 8),
StyledButton(
onPressed: () async {
if (_formKey.currentState!.validate()) {
await widget.addMessage(_controller.text);
_controller.clear();
}
},
child: Row(
children: const [
Icon(Icons.send),
SizedBox(width: 4),
Text('SEND'),
],
),
),
],
),
),
),
// Modify from here...
const SizedBox(height: 8),
for (var message in widget.messages)
Paragraph('${message.name}: ${message.message}'),
const SizedBox(height: 8),
],
// ...to here.
);
}
}
يمكنك لف المحتوى السابق لطريقة build()
باستخدام تطبيق مصغّر Column
، ثم إضافة مجموعة لـ في نهاية عناصر Column
لإنشاء Paragraph
جديد لكل رسالة في قائمة الرسائل.
- عدِّل نص
HomePage
لإنشاءGuestBook
بشكل صحيح باستخدام المَعلمةmessages
الجديدة:
lib/home_page.dart
Consumer<ApplicationState>(
builder: (context, appState, _) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (appState.loggedIn) ...[
const Header('Discussion'),
GuestBook(
addMessage: (message) =>
appState.addMessageToGuestBook(message),
messages: appState.guestBookMessages, // new
),
],
],
),
),
مزامنة رسائل الاختبار
تعمل خدمة Firestore على مزامنة البيانات تلقائيًا وعلى الفور مع العملاء المشتركين في قاعدة البيانات.
مزامنة الرسائل الاختبارية:
- في التطبيق، ابحث عن الرسائل التي أنشأتها سابقًا في قاعدة البيانات.
- كتابة رسائل جديدة وتظهر هذه الإعلانات على الفور.
- افتح مساحة العمل في نوافذ أو علامات تبويب متعددة. تتم مزامنة الرسائل في الوقت الفعلي على جميع النوافذ وعلامات التبويب.
- اختياري: في قائمة قاعدة البيانات في وحدة تحكّم Firebase، يمكنك حذف رسائل جديدة أو تعديلها أو إضافتها يدويًا. تظهر جميع التغييرات في واجهة المستخدم.
تهانينا! قراءة مستندات Firestore في تطبيقك
معاينة التطبيق
8- إعداد قواعد الأمان الأساسية
لقد أعددت Firestore في البداية لاستخدام وضع الاختبار، ما يعني أنّ قاعدة بياناتك مفتوحة للقراءة والكتابة. ومع ذلك، يجب عدم استخدام الوضع التجريبي إلا خلال المراحل الأولى من التطوير. من أفضل الممارسات إعداد قواعد أمان لقاعدة بياناتك أثناء تطوير تطبيقك، لأنّ الأمان هو جزء لا يتجزأ من بنية تطبيقك وسلوكه.
تتيح لك "قواعد أمان Firebase" التحكّم في الوصول إلى المستندات والمجموعات في قاعدة بياناتك. تتيح لك بنية القواعد المرنة إنشاء قواعد تتطابق مع أي شيء، بدءًا من جميع عمليات الكتابة إلى قاعدة البيانات بالكامل ووصولاً إلى العمليات التي يتم إجراؤها على مستند معيّن.
إعداد قواعد الأمان الأساسية:
- في قائمة التطوير في وحدة تحكّم Firebase، انقر على قاعدة البيانات > القواعد. من المفترض أن تظهر لك قواعد الأمان التلقائية التالية وتحذير بشأن أنّ القواعد علنية:
- حدِّد المجموعات التي يكتب التطبيق البيانات فيها:
في match /databases/{database}/documents
، حدِّد المجموعة التي تريد تأمينها:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /guestbook/{entry} {
// You'll add rules here in the next step.
}
}
بما أنّك استخدمت معرّف Authenticate UID كحقل في كل مستند من دفاتر الضيوف، يمكنك الحصول على معرّف Authenticate UID والتأكّد من أنّ أي مستخدم يحاول الكتابة في المستند لديه معرّف Authenticate UID مطابق.
- أضِف قواعد القراءة والكتابة إلى مجموعة القواعد:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /guestbook/{entry} {
allow read: if request.auth.uid != null;
allow write:
if request.auth.uid == request.resource.data.userId;
}
}
}
الآن، يمكن للمستخدمين الذين سجّلوا الدخول فقط قراءة الرسائل في دفتر الضيوف، ولكن يمكن لكاتب الرسالة فقط تعديلها.
- أضِف عملية التحقّق من البيانات لضمان توفّر جميع الحقول المتوقّعة في المستند:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /guestbook/{entry} {
allow read: if request.auth.uid != null;
allow write:
if request.auth.uid == request.resource.data.userId
&& "name" in request.resource.data
&& "text" in request.resource.data
&& "timestamp" in request.resource.data;
}
}
}
9- خطوة إضافية: تطبيق ما تعلمته
تسجيل حالة الردّ على دعوة أحد الضيوف
في الوقت الحالي، لا يسمح تطبيقك للمستخدمين بالدردشة إلا عندما يكونون مهتمين بالحدث. بالإضافة إلى ذلك، إنّ الطريقة الوحيدة لمعرفة ما إذا كان أحد الأشخاص سيحضر هي عندما يُعلِن عن ذلك في المحادثة.
في هذه الخطوة، يمكنك تنظيم الحدث وإعلام المشاركين بعدد الضيوف. يمكنك إضافة بعض الإمكانات إلى حالة التطبيق. أولاً، يمكن للمستخدم الذي سجّل الدخول تحديد ما إذا كان سيحضر الاجتماع. أما العنصر الثاني، فهو عداد يعرض عدد المستخدمين المشارِكين في الحدث.
- في ملف
lib/app_state.dart
، أضِف الأسطر التالية إلى قسم accessors (عناصر الوصول) فيApplicationState
لكي يتمكّن رمز واجهة المستخدم من التفاعل مع هذه الحالة:
lib/app_state.dart
int _attendees = 0;
int get attendees => _attendees;
Attending _attending = Attending.unknown;
StreamSubscription<DocumentSnapshot>? _attendingSubscription;
Attending get attending => _attending;
set attending(Attending attending) {
final userDoc = FirebaseFirestore.instance
.collection('attendees')
.doc(FirebaseAuth.instance.currentUser!.uid);
if (attending == Attending.yes) {
userDoc.set(<String, dynamic>{'attending': true});
} else {
userDoc.set(<String, dynamic>{'attending': false});
}
}
- عدِّل طريقة
init()
فيApplicationState
على النحو التالي:
lib/app_state.dart
Future<void> init() async {
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform);
FirebaseUIAuth.configureProviders([
EmailAuthProvider(),
]);
// Add from here...
FirebaseFirestore.instance
.collection('attendees')
.where('attending', isEqualTo: true)
.snapshots()
.listen((snapshot) {
_attendees = snapshot.docs.length;
notifyListeners();
});
// ...to here.
FirebaseAuth.instance.userChanges().listen((user) {
if (user != null) {
_loggedIn = true;
_emailVerified = user.emailVerified;
_guestBookSubscription = FirebaseFirestore.instance
.collection('guestbook')
.orderBy('timestamp', descending: true)
.snapshots()
.listen((snapshot) {
_guestBookMessages = [];
for (final document in snapshot.docs) {
_guestBookMessages.add(
GuestBookMessage(
name: document.data()['name'] as String,
message: document.data()['text'] as String,
),
);
}
notifyListeners();
});
// Add from here...
_attendingSubscription = FirebaseFirestore.instance
.collection('attendees')
.doc(user.uid)
.snapshots()
.listen((snapshot) {
if (snapshot.data() != null) {
if (snapshot.data()!['attending'] as bool) {
_attending = Attending.yes;
} else {
_attending = Attending.no;
}
} else {
_attending = Attending.unknown;
}
notifyListeners();
});
// ...to here.
} else {
_loggedIn = false;
_emailVerified = false;
_guestBookMessages = [];
_guestBookSubscription?.cancel();
_attendingSubscription?.cancel(); // new
}
notifyListeners();
});
}
يضيف هذا الرمز طلب بحث مشتركًا دائمًا لتحديد عدد الضيوف وطلب بحث ثانٍ لا يكون نشطًا إلا عندما يكون المستخدم مسجِّلاً الدخول لتحديد ما إذا كان المستخدم يحضر الاجتماع.
- أضِف التعداد التالي في أعلى ملف
lib/app_state.dart
.
lib/app_state.dart
enum Attending { yes, no, unknown }
- أنشئ ملفًا جديدًا
yes_no_selection.dart
، وحدِّد تطبيقًا مصغّرًا جديدًا يعمل مثل الأزرار الاختيارية:
lib/yes_no_selection.dart
import 'package:flutter/material.dart';
import 'app_state.dart';
import 'src/widgets.dart';
class YesNoSelection extends StatelessWidget {
const YesNoSelection(
{super.key, required this.state, required this.onSelection});
final Attending state;
final void Function(Attending selection) onSelection;
@override
Widget build(BuildContext context) {
switch (state) {
case Attending.yes:
return Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
FilledButton(
onPressed: () => onSelection(Attending.yes),
child: const Text('YES'),
),
const SizedBox(width: 8),
TextButton(
onPressed: () => onSelection(Attending.no),
child: const Text('NO'),
),
],
),
);
case Attending.no:
return Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
TextButton(
onPressed: () => onSelection(Attending.yes),
child: const Text('YES'),
),
const SizedBox(width: 8),
FilledButton(
onPressed: () => onSelection(Attending.no),
child: const Text('NO'),
),
],
),
);
default:
return Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
StyledButton(
onPressed: () => onSelection(Attending.yes),
child: const Text('YES'),
),
const SizedBox(width: 8),
StyledButton(
onPressed: () => onSelection(Attending.no),
child: const Text('NO'),
),
],
),
);
}
}
}
يبدأ هذا الخيار في حالة غير محدّدة بدون تحديد نعم أو لا. بعد أن يختار المستخدم ما إذا كان سيحضر الحدث، يمكنك عرض هذا الخيار مميّزًا باستخدام زر ممتلئ، ويتراجع الخيار الآخر باستخدام عرض مسطّح.
- عدِّل طريقة
build()
فيHomePage
للاستفادة منYesNoSelection
، وفعِّل خيارًا للمستخدم الذي سجّل الدخول ليحدّد ما إذا كان سيحضر الحدث، وعرض عدد الضيوف في الحدث:
lib/home_page.dart
Consumer<ApplicationState>(
builder: (context, appState, _) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Add from here...
switch (appState.attendees) {
1 => const Paragraph('1 person going'),
>= 2 => Paragraph('${appState.attendees} people going'),
_ => const Paragraph('No one going'),
},
// ...to here.
if (appState.loggedIn) ...[
// Add from here...
YesNoSelection(
state: appState.attending,
onSelection: (attending) => appState.attending = attending,
),
// ...to here.
const Header('Discussion'),
GuestBook(
addMessage: (message) =>
appState.addMessageToGuestBook(message),
messages: appState.guestBookMessages,
),
],
],
),
),
إضافة قواعد
سبق لك إعداد بعض القواعد، لذا سيتم رفض البيانات التي تضيفها باستخدام الأزرار. عليك تعديل القواعد للسماح بإضافة عناصر إلى مجموعة attendees
.
- في مجموعة
attendees
، احصل على المعرّف الفريد للمصادقة الذي استخدمته كاسم للمستند وتأكّد من أنّuid
الذي يستخدمه المرسِل هو نفسه المستند الذي يكتبه:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// ... //
match /attendees/{userId} {
allow read: if true;
allow write: if request.auth.uid == userId;
}
}
}
يتيح ذلك للجميع قراءة قائمة الضيوف لأنّها لا تتضمّن أي بيانات خاصة، ولكن يمكن لصانع المحتوى فقط تعديلها.
- أضِف عملية التحقّق من البيانات لضمان توفّر جميع الحقول المتوقّعة في المستند:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// ... //
match /attendees/{userId} {
allow read: if true;
allow write: if request.auth.uid == userId
&& "attending" in request.resource.data;
}
}
}
- اختياري: في التطبيق، انقر على الأزرار للاطّلاع على النتائج في لوحة بيانات Firestore في وحدة تحكّم Firebase.
معاينة التطبيق
10- تهانينا!
لقد استخدمت Firebase لإنشاء تطبيق ويب تفاعلي في الوقت الفعلي.