1. قبل البدء
في هذا الدرس التطبيقي حول الترميز، ستتعرّف على بعض أساسيات Firebase لإنشاء تطبيقات Flutter للأجهزة الجوّالة على Android وiOS.
المتطلبات الأساسية
- الإلمام بإطار عمل Flutter
- حزمة تطوير البرامج (SDK) لنظام التشغيل Flutter
- محرّر نصوص من اختيارك
أهداف الدورة التعليمية
- كيفية إنشاء تطبيق دردشة لردّ الدعوات وكتابة رسائل التهنئة على Android وiOS والويب وmacOS باستخدام Flutter
- كيفية مصادقة المستخدمين باستخدام مصادقة Firebase ومزامنة البيانات مع Firestore
|
|
|
|
المتطلبات
أيّ من الأجهزة التالية:
- جهاز Android أو iOS فعلي متصل بجهاز الكمبيوتر وتم ضبطه على وضع المطوّرين
- محاكي iOS (يتطلّب أدوات Xcode)
- محاكي Android (يجب إعداده في استوديو Android)
تحتاج أيضًا إلى ما يلي:
- متصفّح من اختيارك، مثل Google Chrome
- بيئة تطوير متكاملة (IDE) أو أداة تعديل نصوص من اختيارك تم ضبطها باستخدام المكوّنات الإضافية Dart وFlutter، مثل استوديو Android أو Visual Studio Code
- أحدث إصدار من Flutter وهو
stableأوbetaإذا كنت من محبّي تجربة أحدث الميزات - حساب Google لإنشاء مشروعك على Firebase وإدارته
- تم تسجيل دخول
Firebaseواجهة سطر الأوامر إلى حسابك على 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. عند فتح التطبيق في بيئة التطوير المتكاملة، ستلاحظ أخطاء وقت الترجمة التي سيتم إصلاحها في الخطوة 4.
تحديد موقع الملفات التي تحتاج إلى تعديل
يتم توزيع الرمز البرمجي في هذا التطبيق على عدة أدلة. يسهّل هذا التقسيم للوظائف العمل لأنّه يجمّع الرمز حسب الوظيفة.
- ابحث عن الملفات التالية:
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 باستخدام حساب Google.
- انقر على الزر لإنشاء مشروع جديد، ثم أدخِل اسم المشروع (على سبيل المثال،
Firebase-Flutter-Codelab).
- انقر على متابعة.
- إذا طُلب منك ذلك، راجِع بنود Firebase واقبلها، ثم انقر على متابعة.
- (اختياري) فعِّل ميزة "المساعدة المستندة إلى الذكاء الاصطناعي" في وحدة تحكّم Firebase (المعروفة باسم "Gemini في Firebase").
- في هذا الدرس العملي، لا تحتاج إلى "إحصاءات Google"، لذا أوقِف خيار "إحصاءات Google".
- انقر على إنشاء مشروع، وانتظِر إلى أن يتم توفير مشروعك، ثم انقر على متابعة.
لمزيد من المعلومات عن مشاريع Firebase، اطّلِع على مقالة التعرّف على مشاريع 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.
- من سطر الأوامر، أضِف التبعيات التالية من جذر تطبيقك (
.../firebase-get-to-know-flutter/step_02):
$ flutter pub add firebase_core firebase_auth cloud_firestore provider firebase_ui_auth
لاستخدام Firebase في تطبيق Flutter، عليك الجمع بين بعض الحِزم المتخصّصة:
firebase_coreالحزمة: هذه هي نقطة البداية الأساسية. يجب أن تكون لديك هذه الحزمة، لأنّ جميع أدوات Firebase الأخرى لإطار عمل Flutter تعتمد عليها.- حزمة
firebase_auth: هذه الحزمة مخصّصة للتعامل مع حسابات المستخدمين. تتيح لك إضافة ميزات مثل الاشتراك وتسجيل الدخول وتسجيل الخروج. - حزمة
cloud_firestore: استخدِم هذه الحزمة لربط تطبيقك بقاعدة بيانات Firestore، ما يتيح لك حفظ بيانات تطبيقك والوصول إليها. - حزمة
firebase_ui_auth: تسهّل هذه الحزمة عملية إعداد المصادقة بشكل كبير. توفّر هذه المكتبة أدوات جاهزة للاستخدام (مثل شاشات تسجيل الدخول المُنشأة مسبقًا) حتى لا تضطر إلى إنشاء كل شيء من البداية. - حزمة
provider: هذا الخيار رائج لإدارة الحالة. فهي تساعد تطبيقك في تتبُّع المعلومات (مثل المستخدم الذي سجّل الدخول) وإتاحة هذه البيانات على جميع الشاشات المختلفة التي تحتاج إليها.
لقد أضفت الحِزم المطلوبة، ولكن عليك أيضًا ضبط مشاريع iOS وAndroid وmacOS وWeb runner لاستخدام Firebase بشكلٍ مناسب. يمكنك أيضًا استخدام حزمة provider التي تتيح فصل منطق النشاط التجاري عن منطق العرض.
تثبيت FlutterFire CLI
تعتمد أداة FlutterFire CLI على أداة Firebase CLI الأساسية.
- إذا لم يسبق لك إجراء ذلك، ثبِّت Firebase CLI على جهازك.
- ثبِّت FlutterFire CLI:
$ dart pub global activate flutterfire_cli
بعد تثبيته، يصبح الأمر flutterfire متاحًا على مستوى العالم.
إعداد تطبيقاتك
تستخرج واجهة سطر الأوامر المعلومات من مشروع Firebase وتطبيقات المشروع المحدّدة لإنشاء جميع إعدادات نظام أساسي معيّن.
في جذر تطبيقك (flutter-codelabs/firebase-get-to-know-flutter/step_02)، نفِّذ الأمر 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 إلى التطبيق، يمكنك إنشاء زر الرد على الدعوة يسجّل المستخدمين باستخدام خدمة المصادقة. بالنسبة إلى تطبيقات 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,
),
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
textTheme: GoogleFonts.robotoTextTheme(
Theme.of(context).textTheme,
),
visualDensity: VisualDensity.adaptivePlatformDensity
),
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. أداة Consumer هي الطريقة المعتادة التي يمكن بها استخدام حزمة 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({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'),
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;
// Add from here...
_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();
});
// ...to here.
} else {
_loggedIn = false;
// Add from here...
_guestBookMessages = [];
_guestBookSubscription?.cancel();
// to here.
}
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
State<GuestBook> createState() => _GuestBookState();
}
- في
_GuestBookState، عدِّل طريقةbuildعلى النحو التالي لعرض هذا الإعداد:
lib/guest_book.dart
class _GuestBookState extends State<GuestBook> {
final _formKey = GlobalKey<FormState>(debugLabel: '_GuestBookState');
final _controller = TextEditingController();
@override
Widget build(BuildContext context) {
// Modify from here...
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: [
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
),
],
],
),
),
اختبار مزامنة الرسائل
تتم مزامنة البيانات تلقائيًا وفوريًا مع العملاء المشتركين في قاعدة البيانات.
مزامنة الرسائل الاختبارية:
- في التطبيق، ابحث عن الرسائل التي أنشأتها سابقًا في قاعدة البيانات.
- كتابة رسائل جديدة تظهر هذه الإشعارات على الفور.
- افتح مساحة عملك في نوافذ أو علامات تبويب متعددة. تتم مزامنة الرسائل في الوقت الفعلي على مستوى النوافذ وعلامات التبويب.
- اختياري: في قائمة قاعدة البيانات في وحدة تحكّم 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.
}
}
بما أنّك استخدمت المعرّف الفريد للمصادقة كحقل في كل مستند من مستندات سجل الزوار، يمكنك الحصول على المعرّف الفريد للمصادقة والتأكّد من أنّ أي شخص يحاول الكتابة في المستند لديه معرّف فريد مطابق للمصادقة.
- أضِف قواعد القراءة والكتابة إلى مجموعة القواعد:
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، أضِف الأسطر التالية إلى قسم أدوات الوصول في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
import 'yes_no_selection.dart'; // new
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 للمصادقة الذي استخدمته كاسم للمستند وتأكَّد من أنّ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 لإنشاء تطبيق ويب تفاعلي في الوقت الفعلي.















