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 Studio أو 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
أو استورِده في بيئة التطوير المتكاملة التي تفضّلها. يحتوي هذا الدليل على رمز البدء الخاص بالدرس التطبيقي حول الترميز، ويتضمّن تطبيق 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:
- في القائمة إنشاء، انقر على قاعدة بياناتFirestore > إنشاء قاعدة بيانات
- اختَر البدء في وضع الاختبار، ثم اقرأ بيان إخلاء المسؤولية بشأن قواعد الأمان. يضمن وضع الاختبار إمكانية الكتابة بحرية في قاعدة البيانات أثناء التطوير.
- انقر على التالي ثم اختَر موقع قاعدة البيانات. ويمكنك استخدام الإعداد التلقائي. ولا يمكنك تغيير الموقع الجغرافي لاحقًا.
- انقر على تفعيل.
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.
- ثبِّت واجهة سطر الأوامر في Firebase على جهازك، إذا لم يسبق لك إجراء ذلك.
- تثبيت واجهة سطر الأوامر FlutterFire:
$ dart pub global activate flutterfire_cli
وبعد تثبيت الأمر، يصبح الأمر flutterfire
متاحًا على مستوى العالم.
ضبط تطبيقاتك
يستخرج واجهة سطر الأوامر المعلومات من مشروعك على 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 إلى التطبيق، يمكنك إنشاء زر الرد على الدعوة الذي يسجل الأشخاص من خلال المصادقة. بالنسبة إلى نظام التشغيل 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، انقر على Database > القواعد: من المفترض أن تظهر لك قواعد الأمان التلقائية التالية وتحذير بشأن القواعد العلنية:
- تحديد المجموعات التي يكتب التطبيق البيانات إليها:
في 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.
}
}
نظرًا لأنك استخدمت معرف المستخدم الفريد (UID) للمصادقة كحقل في كل مستند دفتر زوار، يمكنك الحصول على المعرف الفريد للمصادقة والتحقق من أن أي شخص يحاول الكتابة إلى المستند لديه معرف 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 لإنشاء تطبيق ويب تفاعلي في الوقت الفعلي.