1- قبل البدء
في هذا الدرس التطبيقي حول الترميز، ستتعلّم بعض أساسيات Firebase لإنشاء تطبيقات Flutter للأجهزة الجوّالة لنظامَي التشغيل Android وiOS.
المتطلبات الأساسية
- معرفة جيدة بإطار عمل Flutter
- حزمة تطوير البرامج (SDK) من Flutter
- محرِّر نصوص من اختيارك
ما ستتعرّف عليه
- كيفية إنشاء تطبيق للردّ على دعوات الأحداث والدردشة في دفتر الضيوف على أجهزة Android وiOS والويب وmacOS باستخدام Flutter
- كيفية مصادقة المستخدمين من خلال مصادقة Firebase ومزامنة البيانات مع 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 الإلكتروني الذي لم يعمل بعد.
تحديد موقع الملفات التي تحتاج إلى تحسين
الرمز البرمجي في هذا التطبيق موزّع على أدلة متعددة. يجعل هذا التقسيم من الوظائف العمل أسهل لأنه يجمع الرمز حسب الوظيفة.
- حدد موقع الملفات التالية:
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
الدمج مع واجهة برمجة التطبيقات Authentication.
$ 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 CLI
يعتمد FlutterFire CLI على واجهة سطر الأوامر الأساسية في Firebase.
- ثبِّت واجهة سطر الأوامر في Firebase على جهازك، إذا لم يسبق لك إجراء ذلك.
- ثبِّت أداة FlutterFire CLI:
$ dart pub global activate flutterfire_cli
بعد التثبيت، يصبح الطلب flutterfire
متاحًا على مستوى العالم.
ضبط تطبيقاتك
تستخرج واجهة برمجة التطبيقات (CLI) المعلومات من مشروعك على Firebase وتطبيقات المشروع المحدّدة لإنشاء جميع الإعدادات لمنصّة معيّنة.
في جذر تطبيقك، شغِّل الأمر configure
:
$ flutterfire configure
يرشدك أمر الضبط خلال العمليات التالية:
- اختَر مشروع Firebase استنادًا إلى ملف
.firebaserc
أو من "وحدة تحكُّم Firebase". - تحديد الأنظمة الأساسية التي تريد ضبط الإعدادات لها، مثل Android وiOS وmacOS والويب
- حدِّد تطبيقات Firebase التي تريد استخراج الإعدادات منها. يحاول CLI تلقائيًا مطابقة تطبيقات 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 الأساسية والمصادقة، ويتم سحب حزمة 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
. أداة المستهلك هي الطريقة المعتادة لاستخدام حزمة 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
، أضِف الأسطر التالية إلى قسم أدوات الوصول في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 لإنشاء تطبيق ويب تفاعلي في الوقت الفعلي.