تعرف على Firebase من أجل Flutter

1. قبل أن تبدأ

في هذا الدرس التطبيقي حول التعليمات البرمجية، ستتعلم بعض أساسيات Firebase لإنشاء تطبيقات Flutter للهواتف المحمولة لنظامي التشغيل Android وiOS.

المتطلبات الأساسية

ما ستتعلمه

  • كيفية إنشاء تطبيق RSVP للحدث وتطبيق دردشة سجل الزوار على Android وiOS والويب وmacOS باستخدام Flutter.
  • كيفية مصادقة المستخدمين باستخدام مصادقة Firebase ومزامنة البيانات مع Firestore.

الشاشة الرئيسية للتطبيق على الأندرويد

الشاشة الرئيسية للتطبيق على نظام iOS

ماذا ستحتاج

أي من الأجهزة التالية:

  • جهاز فعلي يعمل بنظام Android أو iOS متصل بجهاز الكمبيوتر الخاص بك ويتم ضبطه على وضع المطور.
  • محاكي iOS (يتطلب أدوات Xcode ).
  • محاكي Android (يتطلب الإعداد في Android Studio ).

تحتاج أيضًا إلى ما يلي:

  • متصفح من اختيارك مثل Google Chrome.
  • تم تكوين IDE أو محرر نصوص من اختيارك باستخدام المكونات الإضافية Dart وFlutter، مثل Android Studio أو Visual Studio Code .
  • أحدث إصدار stable من Flutter أو beta إذا كنت تستمتع بالعيش على الحافة.
  • حساب Google لإنشاء وإدارة مشروع Firebase الخاص بك.
  • تم تسجيل دخول Firebase CLI إلى حساب Google الخاص بك.

2. احصل على نموذج التعليمات البرمجية

قم بتنزيل الإصدار الأولي لمشروعك من GitHub:

  1. من سطر الأوامر، انسخ مستودع GitHub في دليل flutter-codelabs :
git clone https://github.com/flutter/codelabs.git flutter-codelabs

يحتوي دليل flutter-codelabs على التعليمات البرمجية لمجموعة من Codelabs. الكود الخاص بهذا الدرس التطبيقي موجود في دليل flutter-codelabs/firebase-get-to-know-flutter . يحتوي الدليل على سلسلة من اللقطات التي توضح الشكل الذي يجب أن يبدو عليه مشروعك في نهاية كل خطوة. على سبيل المثال، أنت في الخطوة الثانية.

  1. ابحث عن الملفات المطابقة للخطوة الثانية:
cd flutter-codelabs/firebase-get-to-know-flutter/step_02

إذا كنت تريد التخطي للأمام أو معرفة كيف ينبغي لشيء ما أن يبدو بعد خطوة ما، فابحث في الدليل المسمى باسم الخطوة التي تهمك.

قم باستيراد تطبيق البداية

  • افتح أو قم باستيراد flutter-codelabs/firebase-get-to-know-flutter/step_02 في IDE المفضل لديك. يحتوي هذا الدليل على رمز البدء الخاص ببرنامج Codelab، والذي يتكون من تطبيق Flutter meetup الذي لم يعمل بعد.

حدد موقع الملفات التي تحتاج إلى العمل

ينتشر الكود الموجود في هذا التطبيق عبر أدلة متعددة. هذا التقسيم للوظائف يجعل العمل أسهل لأنه يجمع التعليمات البرمجية حسب الوظيفة.

  • حدد موقع الملفات التالية:
    • lib/main.dart : يحتوي هذا الملف على نقطة الإدخال الرئيسية وأداة التطبيق.
    • lib/home_page.dart : يحتوي هذا الملف على أداة الصفحة الرئيسية.
    • lib/src/widgets.dart : يحتوي هذا الملف على مجموعة من الأدوات للمساعدة في توحيد نمط التطبيق. إنهم يشكلون شاشة تطبيق البداية.
    • lib/src/authentication.dart : يحتوي هذا الملف على تطبيق جزئي للمصادقة مع مجموعة من الأدوات لإنشاء تجربة مستخدم لتسجيل الدخول للمصادقة المستندة إلى البريد الإلكتروني لـ Firebase. لم يتم استخدام عناصر واجهة المستخدم الخاصة بتدفق المصادقة حتى الآن في التطبيق المبدئي، ولكن يمكنك إضافتها قريبًا.

يمكنك إضافة ملفات إضافية كما هو مطلوب لإنشاء بقية التطبيق.

قم بمراجعة ملف lib/main.dart

يستفيد هذا التطبيق من حزمة google_fonts لجعل Roboto هو الخط الافتراضي في جميع أنحاء التطبيق. يمكنك استكشاف الخطوط.google.com واستخدام الخطوط التي تكتشفها هناك في أجزاء مختلفة من التطبيق.

يمكنك استخدام الأدوات المساعدة من ملف lib/src/widgets.dart في شكل Header و Paragraph و IconAndDetail . تقوم هذه الأدوات بإزالة التعليمات البرمجية المكررة لتقليل الفوضى في تخطيط الصفحة الموضح في HomePage . وهذا يتيح أيضًا مظهرًا وملمسًا متسقين.

إليك الشكل الذي يبدو عليه تطبيقك على أنظمة Android وiOS والويب وmacOS:

الشاشة الرئيسية للتطبيق على الأندرويد

الشاشة الرئيسية للتطبيق على نظام iOS

الشاشة الرئيسية للتطبيق على الويب

الشاشة الرئيسية للتطبيق على نظام التشغيل macOS

3. إنشاء وتكوين مشروع Firebase

يعد عرض معلومات الحدث أمرًا رائعًا لضيوفك، ولكنه ليس مفيدًا جدًا لأي شخص بمفرده. تحتاج إلى إضافة بعض الوظائف الديناميكية إلى التطبيق. للقيام بذلك، تحتاج إلى توصيل Firebase بتطبيقك. للبدء في استخدام Firebase، يتعين عليك إنشاء مشروع Firebase وتكوينه.

إنشاء مشروع Firebase

  1. قم بتسجيل الدخول إلى Firebase .
  2. في وحدة التحكم، انقر فوق إضافة مشروع أو إنشاء مشروع .
  3. في حقل اسم المشروع ، أدخل Firebase-Flutter-Codelab ثم انقر فوق متابعة .

4395e4e67c08043a.png

  1. انقر فوق خيارات إنشاء المشروع. إذا طُلب منك، فاقبل شروط Firebase، ولكن قم بتخطي إعداد Google Analytics لأنك لن تستخدمه لهذا التطبيق.

b7138cde5f2c7b61.png

لمعرفة المزيد حول مشاريع Firebase، راجع فهم مشاريع Firebase .

يستخدم التطبيق منتجات Firebase التالية المتوفرة لتطبيقات الويب:

  • المصادقة: يتيح للمستخدمين تسجيل الدخول إلى تطبيقك.
  • Firestore: يحفظ البيانات المنظمة على السحابة ويحصل على إشعارات فورية عند تغيير البيانات.
  • قواعد أمان Firebase: يؤمن قاعدة البيانات الخاصة بك.

تحتاج بعض هذه المنتجات إلى تكوين خاص أو تحتاج إلى تمكينها في وحدة تحكم Firebase.

تمكين مصادقة تسجيل الدخول عبر البريد الإلكتروني

  1. في جزء النظرة العامة على المشروع بوحدة تحكم Firebase، قم بتوسيع قائمة الإنشاء .
  2. انقر فوق المصادقة > البدء > طريقة تسجيل الدخول > البريد الإلكتروني/كلمة المرور > تمكين > حفظ .

58e3e3e23c2f16a4.png

تمكين فايرستور

يستخدم تطبيق الويب Firestore لحفظ رسائل الدردشة وتلقي رسائل دردشة جديدة.

تمكين متجر النار:

  • في قائمة Build ، انقر فوق Firestore Database > إنشاء قاعدة بيانات .

99e8429832d23fa3.png

  1. حدد البدء في وضع الاختبار ثم اقرأ إخلاء المسؤولية حول قواعد الأمان. يضمن وضع الاختبار إمكانية الكتابة بحرية إلى قاعدة البيانات أثناء التطوير.

6be00e26c72ea032.png

  1. انقر فوق التالي ثم حدد موقع قاعدة البيانات الخاصة بك. يمكنك استخدام الافتراضي. ولا يمكنك تغيير الموقع لاحقًا.

278656eefcfb0216.png

  1. انقر فوق تمكين .

4. تكوين Firebase

لاستخدام Firebase مع Flutter، يلزمك إكمال المهام التالية لتكوين مشروع Flutter لاستخدام مكتبات FlutterFire بشكل صحيح:

  1. أضف تبعيات FlutterFire إلى مشروعك.
  2. قم بتسجيل النظام الأساسي المطلوب في مشروع Firebase.
  3. قم بتنزيل ملف التكوين الخاص بالنظام الأساسي ثم قم بإضافته إلى الكود.

في دليل المستوى الأعلى لتطبيق 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 CLI

يعتمد FlutterFire CLI على Firebase CLI الأساسي.

  1. إذا لم تكن قد قمت بذلك بالفعل، فقم بتثبيت Firebase CLI على جهازك.
  2. تثبيت FlutterFire CLI:
$ dart pub global activate flutterfire_cli

بمجرد التثبيت، يصبح الأمر flutterfire متاحًا عالميًا.

قم بتكوين تطبيقاتك

تستخرج واجهة سطر الأوامر (CLI) المعلومات من مشروع Firebase الخاص بك وتطبيقات المشروع المحددة لإنشاء كل التكوينات لنظام أساسي معين.

في جذر تطبيقك، قم بتشغيل أمر configure :

$ flutterfire configure

يرشدك أمر التكوين خلال العمليات التالية:

  1. حدد مشروع Firebase استنادًا إلى ملف .firebaserc أو من Firebase Console.
  2. تحديد الأنظمة الأساسية للتكوين، مثل Android وiOS وmacOS والويب.
  3. حدد تطبيقات Firebase التي تريد استخراج التكوين منها. افتراضيًا، تحاول واجهة سطر الأوامر (CLI) مطابقة تطبيقات Firebase تلقائيًا بناءً على تكوين مشروعك الحالي.
  4. قم بإنشاء ملف 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 الخاصة بالتطبيق:

  1. قم بإنشاء ملف جديد باسم 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 ، والتي تعد طريقة رائعة لتشغيل شاشات تسجيل الدخول في تطبيقاتك بسرعة.

دمج تدفق المصادقة

  1. قم بتعديل الواردات في الجزء العلوي من ملف lib/main.dart :

ليب/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';
  1. قم بتوصيل حالة التطبيق بتهيئة التطبيق ثم قم بإضافة تدفق المصادقة إلى HomePage :

ليب/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 معرفة متى يجب إعادة عرض عناصر واجهة المستخدم التابعة.

  1. قم بتحديث تطبيقك للتعامل مع التنقل إلى الشاشات المختلفة التي يوفرها لك FirebaseUI، عن طريق إنشاء تكوين GoRouter :

ليب/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
    );
  }
}

تحتوي كل شاشة على نوع مختلف من الإجراءات المرتبطة بها بناءً على الحالة الجديدة لتدفق المصادقة. بعد معظم تغييرات الحالة في المصادقة، يمكنك إعادة التوجيه مرة أخرى إلى الشاشة المفضلة، سواء كانت الشاشة الرئيسية أو شاشة مختلفة، مثل ملف التعريف.

  1. في أسلوب إنشاء فئة 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 هو عناصر واجهة المستخدم الإضافية التي تقوم باختبارها.

اختبار تدفق المصادقة

cdf2d25e436bd48d.png

  1. في التطبيق، اضغط على زر الرد على الدعوة لبدء SignInScreen .

2a2cd6d69d172369.png

  1. ادخل عنوان البريد الإلكتروني. إذا كنت مسجلاً بالفعل، فسيطالبك النظام بإدخال كلمة المرور. وبخلاف ذلك، سيطالبك النظام بإكمال نموذج التسجيل.

e5e65065dba36b54.png

  1. أدخل كلمة مرور أقل من ستة أحرف للتحقق من تدفق معالجة الأخطاء. إذا كنت مسجلاً، فسترى كلمة المرور بدلاً من ذلك.
  2. أدخل كلمات مرور غير صحيحة للتحقق من تدفق معالجة الأخطاء.
  3. أدخل كلمة المرور الصحيحة. ترى تجربة تسجيل الدخول، والتي توفر للمستخدم القدرة على تسجيل الخروج.

4ed811a25b0cf816.png

6. اكتب رسائل إلى Firestore

من الرائع أن تعرف أن المستخدمين قادمون، ولكن عليك أن تمنح الضيوف شيئًا آخر للقيام به في التطبيق. ماذا لو كان بإمكانهم ترك رسائل في سجل الزوار؟ يمكنهم مشاركة سبب حماسهم للحضور أو من يأملون في مقابلته.

لتخزين رسائل الدردشة التي يكتبها المستخدمون في التطبيق، يمكنك استخدام Firestore .

نموذج البيانات

Firestore هي قاعدة بيانات NoSQL، ويتم تقسيم البيانات المخزنة في قاعدة البيانات إلى مجموعات ومستندات وحقول ومجموعات فرعية. يمكنك تخزين كل رسالة من رسائل الدردشة كمستند في مجموعة guestbook ، وهي مجموعة ذات مستوى أعلى.

7c20dc8424bb1d84.png

إضافة رسائل إلى Firestore

في هذا القسم، يمكنك إضافة وظيفة للمستخدمين لكتابة الرسائل إلى قاعدة البيانات. أولاً، تقوم بإضافة حقل نموذج وزر إرسال، ثم تقوم بإضافة التعليمات البرمجية التي تربط هذه العناصر بقاعدة البيانات.

  1. أنشئ ملفًا جديدًا باسم 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 ملء أي مساحة إضافية في الصف. لفهم سبب الحاجة إلى ذلك بشكل أفضل، راجع فهم القيود .

الآن بعد أن أصبح لديك عنصر واجهة مستخدم يمكّن المستخدم من إدخال بعض النص لإضافته إلى دفتر الزوار، فأنت بحاجة إلى عرضه على الشاشة.

  1. قم بتحرير نص 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)),

وفي حين أن هذا يكفي لعرض الأداة، إلا أنه لا يكفي للقيام بأي شيء مفيد. يمكنك تحديث هذا الرمز قريبًا لجعله فعالاً.

معاينة التطبيق

الشاشة الرئيسية للتطبيق على Android مع تكامل الدردشة

الشاشة الرئيسية للتطبيق على iOS مع تكامل الدردشة

الشاشة الرئيسية للتطبيق على الويب مع تكامل الدردشة

الشاشة الرئيسية للتطبيق على نظام التشغيل macOS مع تكامل الدردشة

عندما ينقر المستخدم على إرسال ، فإنه يؤدي إلى تشغيل مقتطف الشفرة التالي. يقوم بإضافة محتويات حقل إدخال الرسالة إلى مجموعة 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> مرة أخرى لإتاحة حالة التطبيق لجزء الشجرة الذي تعرضه. يتيح لك هذا الرد على شخص يقوم بإدخال رسالة في واجهة المستخدم ونشرها في قاعدة البيانات. في القسم التالي، يمكنك اختبار ما إذا كان يتم نشر الرسائل المضافة في قاعدة البيانات.

اختبار إرسال الرسائل

  1. إذا لزم الأمر، قم بتسجيل الدخول إلى التطبيق.
  2. أدخل رسالة، مثل Hey there! ، ثم انقر فوق إرسال .

يقوم هذا الإجراء بكتابة الرسالة إلى قاعدة بيانات Firestore الخاصة بك. ومع ذلك، فإنك لا ترى الرسالة في تطبيق Flutter الفعلي لأنك لا تزال بحاجة إلى تنفيذ استرداد البيانات، وهو ما تفعله في الخطوة التالية. ومع ذلك، في لوحة معلومات قاعدة البيانات بوحدة تحكم Firebase، يمكنك رؤية رسالتك المضافة في مجموعة guestbook . إذا قمت بإرسال المزيد من الرسائل، فإنك تضيف المزيد من المستندات إلى مجموعة guestbook الخاص بك. على سبيل المثال، راجع مقتطف التعليمات البرمجية التالي:

713870af0b3b63c.png

7. قراءة الرسائل

من الجميل أن يتمكن الضيوف من كتابة رسائل إلى قاعدة البيانات، لكن لا يمكنهم رؤيتها في التطبيق بعد. الوقت لإصلاح ذلك!

مزامنة الرسائل

لعرض الرسائل، تحتاج إلى إضافة أدوات الاستماع التي يتم تشغيلها عند تغير البيانات ثم إنشاء عنصر واجهة المستخدم الذي يعرض الرسائل الجديدة. يمكنك إضافة رمز إلى حالة التطبيق الذي يستمع للرسائل المضافة حديثًا من التطبيق.

  1. أنشئ ملفًا جديدًا 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;
}
  1. في ملف 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
  1. في قسم 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.
  1. في قسم التهيئة في 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 .

  1. في ملف lib/guest_book.dart ، قم بإضافة الاستيراد التالي:
import 'guest_book_message.dart';
  1. في عنصر واجهة المستخدم 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();
}
  1. في _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 جديدة لكل رسالة في قائمة الرسائل.

  1. قم بتحديث نص 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 بمزامنة البيانات تلقائيًا وعلى الفور مع العملاء المشتركين في قاعدة البيانات.

اختبار مزامنة الرسائل:

  1. في التطبيق، ابحث عن الرسائل التي قمت بإنشائها مسبقًا في قاعدة البيانات.
  2. كتابة رسائل جديدة. تظهر على الفور.
  3. افتح مساحة العمل الخاصة بك في نوافذ أو علامات تبويب متعددة. تتم مزامنة الرسائل في الوقت الفعلي عبر النوافذ وعلامات التبويب.
  4. اختياري: في قائمة قاعدة البيانات بوحدة تحكم Firebase، يمكنك حذف الرسائل الجديدة أو تعديلها أو إضافتها يدويًا. تظهر كافة التغييرات في واجهة المستخدم.

تهانينا! تقرأ مستندات Firestore في تطبيقك!

معاينة التطبيق

الشاشة الرئيسية للتطبيق على Android مع تكامل الدردشة

الشاشة الرئيسية للتطبيق على iOS مع تكامل الدردشة

الشاشة الرئيسية للتطبيق على الويب مع تكامل الدردشة

الشاشة الرئيسية للتطبيق على نظام التشغيل macOS مع تكامل الدردشة

8. قم بإعداد قواعد الأمان الأساسية

قمت في البداية بإعداد Firestore لاستخدام وضع الاختبار، مما يعني أن قاعدة البيانات الخاصة بك مفتوحة للقراءة والكتابة. ومع ذلك، يجب عليك استخدام وضع الاختبار فقط خلال المراحل الأولى من التطوير. كأفضل الممارسات، يجب عليك إعداد قواعد الأمان لقاعدة البيانات الخاصة بك أثناء تطوير تطبيقك. يعد الأمان جزءًا لا يتجزأ من بنية تطبيقك وسلوكه.

تتيح لك قواعد أمان Firebase التحكم في الوصول إلى المستندات والمجموعات الموجودة في قاعدة بياناتك. يتيح لك بناء جملة القواعد المرنة إنشاء قواعد تتطابق مع أي شيء بدءًا من جميع عمليات الكتابة إلى قاعدة البيانات بأكملها وحتى العمليات على مستند معين.

قم بإعداد قواعد الأمان الأساسية:

  1. في قائمة التطوير بوحدة تحكم Firebase، انقر فوق قاعدة البيانات > القواعد . يجب أن تشاهد قواعد الأمان الافتراضية التالية وتحذيرًا بشأن كون القواعد عامة:

7767a2d2e64e7275.png

  1. حدد المجموعات التي يكتب إليها التطبيق البيانات:

في 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) والتحقق من أن أي شخص يحاول الكتابة إلى المستند لديه معرف فريد مطابق للمصادقة.

  1. أضف قواعد القراءة والكتابة إلى مجموعة القواعد الخاصة بك:
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;
    }
  }
}

الآن، يمكن فقط للمستخدمين الذين قاموا بتسجيل الدخول قراءة الرسائل الموجودة في دفتر الزوار، ولكن يمكن لمؤلف الرسالة فقط تحرير الرسالة.

  1. أضف التحقق من صحة البيانات للتأكد من وجود كافة الحقول المتوقعة في المستند:
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. خطوة إضافية: مارس ما تعلمته

سجل حالة الرد على دعوة الحضور

في الوقت الحالي، يسمح تطبيقك للأشخاص بالدردشة فقط عندما يكونون مهتمين بالحدث. وأيضًا، الطريقة الوحيدة التي تعرف بها ما إذا كان شخص ما قادمًا هي عندما يقول ذلك في الدردشة.

في هذه الخطوة، تصبح منظمًا وتتيح للأشخاص معرفة عدد الأشخاص القادمين. يمكنك إضافة اثنين من الإمكانات إلى حالة التطبيق. الأول هو قدرة المستخدم الذي قام بتسجيل الدخول على ترشيح ما إذا كان سيحضر أم لا. والثاني هو عداد لعدد الأشخاص الحاضرين.

  1. في ملف 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});
  }
}
  1. قم بتحديث طريقة 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();
    });
  }

يضيف هذا الرمز استعلامًا مشتركًا دائمًا لتحديد عدد الحضور واستعلامًا ثانيًا يكون نشطًا فقط أثناء تسجيل دخول المستخدم لتحديد ما إذا كان المستخدم سيحضر أم لا.

  1. قم بإضافة التعداد التالي في أعلى الملف lib/app_state.dart .

lib/app_state.dart

enum Attending { yes, no, unknown }
  1. قم بإنشاء ملف جديد 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'),
              ),
            ],
          ),
        );
    }
  }
}

يبدأ في حالة غير محددة مع عدم تحديد "نعم" أو " لا" . بمجرد أن يحدد المستخدم ما إذا كان سيحضر أم لا، فإنك تظهر هذا الخيار المميز بزر مملوء ويتراجع الخيار الآخر بعرض مسطح.

  1. قم بتحديث طريقة 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 .

  1. في مجموعة 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;
    }
  }
}

يتيح هذا للجميع قراءة قائمة الحاضرين لأنه لا توجد بيانات خاصة هناك، ولكن يمكن للمنشئ فقط تحديثها.

  1. أضف التحقق من صحة البيانات للتأكد من وجود كافة الحقول المتوقعة في المستند:
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;

    }
  }
}
  1. اختياري: في التطبيق، انقر على الأزرار لرؤية النتائج في لوحة معلومات Firestore في وحدة تحكم Firebase.

معاينة التطبيق

الشاشة الرئيسية للتطبيق على الأندرويد

الشاشة الرئيسية للتطبيق على نظام iOS

الشاشة الرئيسية للتطبيق على الويب

الشاشة الرئيسية للتطبيق على نظام التشغيل macOS

10. تهانينا!

لقد استخدمت Firebase لإنشاء تطبيق ويب تفاعلي في الوقت الفعلي!

يتعلم أكثر