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

1. قبل أن تبدأ

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

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

يفترض مختبر الأكواد هذا أنك معتاد على Flutter ، وأنك قمت بتثبيت Flutter SDK ومحرر .

ماذا ستنشئ

في مختبر الأكواد هذا ، ستنشئ حدثًا RSVP وتطبيق دردشة سجل الزوار على Android و iOS والويب و macOS باستخدام Flutter. ستقوم بمصادقة المستخدمين باستخدام مصادقة Firebase ومزامنة البيانات باستخدام Cloud Firestore.

ماذا ستحتاج

يمكنك تشغيل معمل الرموز هذا باستخدام أي من الأجهزة التالية:

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

بالإضافة إلى ما سبق ، ستحتاج أيضًا إلى:

  • متصفح من اختيارك ، مثل Chrome.
  • IDE أو محرر نصوص من اختيارك ، مثل Android Studio أو VS Code الذي تم تكوينه باستخدام الإضافات Dart و Flutter.
  • أحدث إصدار stable من Flutter (أو beta إذا كنت تستمتع بالعيش على الحافة).
  • حساب Google ، مثل حساب gmail ، لإنشاء مشروع Firebase وإدارته.
  • أداة سطر أوامر firebase ، مسجّلة الدخول إلى حساب Gmail الخاص بك.
  • نموذج التعليمات البرمجية لمعمل الرموز. راجع الخطوة التالية لمعرفة كيفية الحصول على الرمز.

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

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

استنساخ مستودع GitHub من سطر الأوامر:

git clone https://github.com/flutter/codelabs.git flutter-codelabs

بدلاً من ذلك ، إذا كان لديك أداة cli الخاصة بـ GitHub مثبتة:

gh repo clone flutter/codelabs flutter-codelabs

يجب استنساخ نموذج التعليمات البرمجية في دليل flutter-codelabs ، والذي يحتوي على رمز مجموعة من مختبرات الرموز. رمز مختبر الرموز هذا موجود في flutter-codelabs/firebase-get-to-know-flutter .

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

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/src/widgets.dart : يحتوي هذا الملف على عدد قليل من عناصر واجهة المستخدم للمساعدة في توحيد نمط التطبيق. تُستخدم هذه لإنشاء شاشة تطبيق المبتدئين.
  • lib/src/authentication.dart FirebaseUI مع مجموعة من الأدوات لإنشاء تجربة مستخدم لتسجيل الدخول للمصادقة المستندة إلى البريد الإلكتروني في 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

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

4395e4e67c08043a.png

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

b7138cde5f2c7b61.png

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

يستخدم التطبيق الذي تنشئه العديد من منتجات Firebase المتوفرة لتطبيقات الويب:

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

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

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

للسماح للمستخدمين بتسجيل الدخول إلى تطبيق الويب ، ستستخدم طريقة تسجيل الدخول إلى البريد الإلكتروني / كلمة المرور لمختبر الرموز هذا:

  1. في وحدة تحكم Firebase ، وسّع قائمة الإنشاء في اللوحة اليمنى.
  2. انقر فوق المصادقة ، ثم انقر فوق الزر " البدء" ، ثم علامة التبويب "طريقة تسجيل الدخول" (أو انقر هنا للانتقال مباشرةً إلى علامة التبويب "طريقة تسجيل الدخول" ).
  3. انقر فوق البريد الإلكتروني / كلمة المرور في قائمة موفري تسجيل الدخول ، واضبط مفتاح تمكين على وضع التشغيل ، ثم انقر فوق حفظ . 58e3e3e23c2f16a4.png

تفعيل Cloud Firestore

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

تمكين Cloud Firestore:

  1. في قسم إنشاء وحدة تحكم Firebase ، انقر فوق Cloud Firestore .
  2. انقر فوق إنشاء قاعدة بيانات . 99e8429832d23fa3.png
  1. حدد خيار البدء في وضع الاختبار . اقرأ إخلاء المسؤولية عن قواعد الأمان. يضمن وضع الاختبار أنه يمكنك الكتابة بحرية إلى قاعدة البيانات أثناء التطوير. انقر فوق التالي . 6be00e26c72ea032.png
  1. حدد موقع قاعدة البيانات الخاصة بك (يمكنك فقط استخدام الافتراضي). لاحظ أنه لا يمكن تغيير هذا الموقع لاحقًا. 278656eefcfb0216.png
  2. انقر فوق تمكين .

4. تكوين Firebase

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

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

في دليل المستوى الأعلى لتطبيق Flutter ، توجد أدلة فرعية تسمى android و ios و macos web تحتوي هذه الأدلة على ملفات التكوين الخاصة بالنظام الأساسي لنظامي iOS و Android ، على التوالي.

تكوين التبعيات

تحتاج إلى إضافة مكتبات FlutterFire لمنتجي Firebase اللذين تستخدمهما في هذا التطبيق - Firebase Auth و Cloud Firestore. قم بتشغيل الأوامر الثلاثة التالية لإضافة التبعيات.

$ flutter pub add firebase_core 
Resolving dependencies...
+ firebase_core 1.10.5
+ firebase_core_platform_interface 4.2.2
+ firebase_core_web 1.5.2
+ flutter_web_plugins 0.0.0 from sdk flutter
+ js 0.6.3
  test_api 0.4.3 (0.4.8 available)
Changed 5 dependencies!

إن firebase_core هو الرمز الشائع المطلوب لجميع مكونات Firebase Flutter الإضافية.

$ flutter pub add firebase_auth
Resolving dependencies...
+ firebase_auth 3.3.3
+ firebase_auth_platform_interface 6.1.8
+ firebase_auth_web 3.3.4
+ intl 0.17.0
  test_api 0.4.3 (0.4.8 available)
Changed 4 dependencies!

يتيح firebase_auth التكامل مع إمكانية مصادقة Firebase.

$ flutter pub add cloud_firestore
Resolving dependencies...
+ cloud_firestore 3.1.4
+ cloud_firestore_platform_interface 5.4.9
+ cloud_firestore_web 2.6.4
  test_api 0.4.3 (0.4.8 available)
Changed 3 dependencies!

يتيح cloud_firestore الوصول إلى تخزين بيانات Cloud Firestore.

$ flutter pub add provider
Resolving dependencies...
+ nested 1.0.0
+ provider 6.0.1
  test_api 0.4.3 (0.4.8 available)
Changed 2 dependencies!

أثناء إضافة الحزم المطلوبة ، تحتاج أيضًا إلى تكوين مشاريع iOS و Android و macOS و Web runner لاستخدام Firebase بشكل مناسب. أنت تستخدم أيضًا حزمة provider التي ستمكّن من فصل منطق الأعمال عن منطق العرض.

تركيب flutterfire

يعتمد FlutterFire CLI على Firebase CLI الأساسي. إذا لم تكن قد قمت بذلك بالفعل ، فتأكد من تثبيت Firebase CLI على جهازك.

بعد ذلك ، قم بتثبيت FlutterFire CLI عن طريق تشغيل الأمر التالي:

$ dart pub global activate flutterfire_cli

بمجرد التثبيت ، سيكون أمر flutterfire متاحًا عالميًا.

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

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

في جذر التطبيق الخاص بك ، قم بتشغيل أمر التكوين:

$ 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 / عداء / 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>

راجع الاستحقاقات و App Sandbox لمزيد من التفاصيل.

5. إضافة تسجيل دخول المستخدم (RSVP)

الآن بعد أن أضفت Firebase إلى التطبيق ، يمكنك إعداد زر RSVP الذي يسجل الأشخاص الذين يستخدمون مصادقة Firebase . بالنسبة لنظام التشغيل Android الأصلي ، ونظام iOS الأصلي ، والويب ، توجد حزم FirebaseUI Auth سابقة الإنشاء ، ولكن بالنسبة إلى Flutter ، ستحتاج إلى بناء هذه الإمكانية.

تضمن المشروع الذي قمت باسترداده في الخطوة 2 مجموعة من الأدوات التي تنفذ واجهة المستخدم لمعظم تدفق المصادقة. ستنفذ منطق الأعمال لدمج مصادقة Firebase في التطبيق.

منطق الأعمال مع المزود

ستستخدم حزمة provider لإتاحة كائن حالة تطبيق مركزي عبر شجرة أدوات Flutter للتطبيق. للبدء ، قم بتعديل عمليات الاستيراد في الجزء العلوي من lib/main.dart :

lib / main.dart

import 'package:firebase_auth/firebase_auth.dart'; // new
import 'package:firebase_core/firebase_core.dart'; // new
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:provider/provider.dart';           // new

import 'firebase_options.dart';                    // new
import 'src/authentication.dart';                  // new
import 'src/widgets.dart';

تقدم خطوط import Firebase Core and Auth ، واسحب حزمة provider التي تستخدمها لإتاحة كائن حالة التطبيق من خلال شجرة عناصر واجهة المستخدم ، وتضمين أدوات المصادقة من lib/src .

كائن حالة التطبيق هذا ، ApplicationState ، لديه مسؤوليتين رئيسيتين لهذه الخطوة ، ولكنه سيكتسب مسؤوليات إضافية عندما تضيف المزيد من الإمكانات إلى التطبيق في خطوات لاحقة. تتمثل المسؤولية الأولى في تهيئة مكتبة Firebase باستدعاء Firebase.initializeApp() ، ثم هناك معالجة لتدفق التفويض. أضف الفئة التالية إلى نهاية lib/main.dart :

lib / main.dart

class ApplicationState extends ChangeNotifier {
  ApplicationState() {
    init();
  }

  Future<void> init() async {
    await Firebase.initializeApp(
      options: DefaultFirebaseOptions.currentPlatform,
    );

    FirebaseAuth.instance.userChanges().listen((user) {
      if (user != null) {
        _loginState = ApplicationLoginState.loggedIn;
      } else {
        _loginState = ApplicationLoginState.loggedOut;
      }
      notifyListeners();
    });
  }

  ApplicationLoginState _loginState = ApplicationLoginState.loggedOut;
  ApplicationLoginState get loginState => _loginState;

  String? _email;
  String? get email => _email;

  void startLoginFlow() {
    _loginState = ApplicationLoginState.emailAddress;
    notifyListeners();
  }

  Future<void> verifyEmail(
    String email,
    void Function(FirebaseAuthException e) errorCallback,
  ) async {
    try {
      var methods =
          await FirebaseAuth.instance.fetchSignInMethodsForEmail(email);
      if (methods.contains('password')) {
        _loginState = ApplicationLoginState.password;
      } else {
        _loginState = ApplicationLoginState.register;
      }
      _email = email;
      notifyListeners();
    } on FirebaseAuthException catch (e) {
      errorCallback(e);
    }
  }

  Future<void> signInWithEmailAndPassword(
    String email,
    String password,
    void Function(FirebaseAuthException e) errorCallback,
  ) async {
    try {
      await FirebaseAuth.instance.signInWithEmailAndPassword(
        email: email,
        password: password,
      );
    } on FirebaseAuthException catch (e) {
      errorCallback(e);
    }
  }

  void cancelRegistration() {
    _loginState = ApplicationLoginState.emailAddress;
    notifyListeners();
  }

  Future<void> registerAccount(
      String email,
      String displayName,
      String password,
      void Function(FirebaseAuthException e) errorCallback) async {
    try {
      var credential = await FirebaseAuth.instance
          .createUserWithEmailAndPassword(email: email, password: password);
      await credential.user!.updateDisplayName(displayName);
    } on FirebaseAuthException catch (e) {
      errorCallback(e);
    }
  }

  void signOut() {
    FirebaseAuth.instance.signOut();
  }
}

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

وتجدر الإشارة إلى أن هذا ليس تنفيذًا كاملاً لتدفق FirebaseUI Auth ، لأنه لا يتعامل مع حالة مستخدم لديه حساب حالي يواجه مشكلة في تسجيل الدخول. يُترك تنفيذ هذه الإمكانية الإضافية كتدريب لـ القارئ المتحمس.

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

الآن بعد أن بدأت حالة التطبيق ، حان الوقت لإدخال حالة التطبيق في تهيئة التطبيق وإضافة تدفق المصادقة إلى HomePage الرئيسية. قم بتحديث نقطة الدخول الرئيسية لدمج حالة التطبيق عبر حزمة provider :

lib / main.dart

void main() {
  // Modify from here
  runApp(
    ChangeNotifierProvider(
      create: (context) => ApplicationState(),
      builder: (context, _) => App(),
    ),
  );
  // to here.
}

يجعل التعديل على الوظيفة main حزمة الموفر مسؤولة عن إنشاء كائن حالة التطبيق باستخدام عنصر واجهة مستخدم ChangeNotifierProvider . أنت تستخدم فئة الموفر المحددة هذه لأن كائن حالة التطبيق يمتد ChangeNotifier وهذا يمكّن حزمة provider من معرفة وقت إعادة عرض عناصر واجهة المستخدم التابعة. أخيرًا ، قم بدمج حالة التطبيق مع Authentication عن طريق تحديث HomePage build الصفحة الرئيسية:

lib / main.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'),
          // Add from here
          Consumer<ApplicationState>(
            builder: (context, appState, _) => Authentication(
              email: appState.email,
              loginState: appState.loginState,
              startLoginFlow: appState.startLoginFlow,
              verifyEmail: appState.verifyEmail,
              signInWithEmailAndPassword: appState.signInWithEmailAndPassword,
              cancelRegistration: appState.cancelRegistration,
              registerAccount: appState.registerAccount,
              signOut: appState.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!',
          ),
        ],
      ),
    );
  }
}

تقوم بإنشاء مثيل أداة Authentication ، وتلفها في عنصر واجهة مستخدم Consumer . عنصر واجهة المستخدم للمستهلك بالطريقة المعتادة التي يمكن من خلالها استخدام حزمة provider لإعادة إنشاء جزء من الشجرة عندما تتغير حالة التطبيق. أداة Authentication هي واجهة مستخدم المصادقة التي ستختبرها الآن.

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

cdf2d25e436bd48d.png

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

2a2cd6d69d172369.png

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

e5e65065dba36b54.png

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

fbb3ea35fb4f67a.png

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

4ed811a25b0cf816.png

وبهذا تكون قد نفذت تدفق المصادقة. تهاني!

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

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

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

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

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

7c20dc8424bb1d84.png

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

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

أولاً ، أضف الواردات لحزمة cloud_firestore و dart:async .

lib / main.dart

import 'dart:async';                                    // new

import 'package:cloud_firestore/cloud_firestore.dart';  // new
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:provider/provider.dart';

import 'firebase_options.dart';
import 'src/authentication.dart';
import 'src/widgets.dart';

لإنشاء عناصر واجهة المستخدم الخاصة بحقل رسالة وزر إرسال ، قم بإضافة GuestBook ضيف جديد لعنصر واجهة مستخدم جديد في الجزء السفلي من lib/main.dart .

lib / main.dart

class GuestBook extends StatefulWidget {
  const GuestBook({required this.addMessage});
  final FutureOr<void> Function(String message) addMessage;

  @override
  _GuestBookState 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 . لمزيد من المعلومات حول المفاتيح وكيفية استخدامها ، يرجى الاطلاع على الحلقة 101 من Flutter Widgets "متى تستخدم المفاتيح" .

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

قم بإجراء تغيير آخر على ملف lib/main.dart . أضف طريقة addMessageToGuestBook . ستقوم بتوصيل واجهة المستخدم وهذه الإمكانية معًا في الخطوة التالية.

lib / main.dart

class ApplicationState extends ChangeNotifier {

  // Current content of ApplicationState elided ...

  // Add from here
  Future<DocumentReference> addMessageToGuestBook(String message) {
    if (_loginState != ApplicationLoginState.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
}

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

لديك واجهة مستخدم حيث يمكن للمستخدم إدخال النص الذي يريد إضافته إلى سجل الزوار ، ولديك الرمز لإضافة الإدخال إلى Cloud Firestore. الآن كل ما عليك فعله هو توصيل الاثنين معًا. في lib/main.dart ، قم بإجراء التغيير التالي على أداة HomePage الرئيسية.

lib / main.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, _) => Authentication(
              email: appState.email,
              loginState: appState.loginState,
              startLoginFlow: appState.startLoginFlow,
              verifyEmail: appState.verifyEmail,
              signInWithEmailAndPassword: appState.signInWithEmailAndPassword,
              cancelRegistration: appState.cancelRegistration,
              registerAccount: appState.registerAccount,
              signOut: appState.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.loginState == ApplicationLoginState.loggedIn) ...[
                  const Header('Discussion'),
                  GuestBook(
                    addMessage: (message) =>
                        appState.addMessageToGuestBook(message),
                  ),
                ],
              ],
            ),
          ),
          // To here.
        ],
      ),
    );
  }
}

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

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

  1. تأكد من تسجيل الدخول إلى التطبيق.
  2. أدخل رسالة مثل "مرحبًا!" ، ثم انقر فوق إرسال .

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

ولكن يمكنك رؤية الرسالة المضافة حديثًا في وحدة تحكم Firebase.

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

وحدة تحكم Firebase

713870af0b3b63c.png

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

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

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

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

فوق عنصر واجهة مستخدم GuestBook مباشرةً ، توجد فئة القيمة التالية. يعرض هذا الفصل عرضًا منظمًا للبيانات التي تخزنها في Cloud Firestore.

lib / main.dart

class GuestBookMessage {
  GuestBookMessage({required this.name, required this.message});
  final String name;
  final String message;
}

في قسم ApplicationState حيث تحدد الحالة والأرقام ، أضف الأسطر الجديدة التالية:

lib / main.dart

  ApplicationLoginState _loginState = ApplicationLoginState.loggedOut;
  ApplicationLoginState get loginState => _loginState;

  String? _email;
  String? get email => _email;

  // Add from here
  StreamSubscription<QuerySnapshot>? _guestBookSubscription;
  List<GuestBookMessage> _guestBookMessages = [];
  List<GuestBookMessage> get guestBookMessages => _guestBookMessages;
  // to here.

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

lib / main.dart

  Future<void> init() async {
    await Firebase.initializeApp(
      options: DefaultFirebaseOptions.currentPlatform,
    );

    FirebaseAuth.instance.userChanges().listen((user) {
      if (user != null) {
        _loginState = ApplicationLoginState.loggedIn;
        // Add from here
        _guestBookSubscription = FirebaseFirestore.instance
            .collection('guestbook')
            .orderBy('timestamp', descending: true)
            .snapshots()
            .listen((snapshot) {
          _guestBookMessages = [];
          for (final document in snapshot.docs) {
            _guestBookMessages.add(
              GuestBookMessage(
                name: document.data()['name'] as String,
                message: document.data()['text'] as String,
              ),
            );
          }
          notifyListeners();
        });
        // to here.
      } else {
        _loginState = ApplicationLoginState.loggedOut;
        // Add from here
        _guestBookMessages = [];
        _guestBookSubscription?.cancel();
        // to here.
      }
      notifyListeners();
    });
  }

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

لمزيد من المعلومات ، راجع وثائق Cloud Firestore .

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

lib / main.dart

class GuestBook extends StatefulWidget {
  // Modify the following line
  const GuestBook({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 / main.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.
    );
  }
}

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

أخيرًا ، تحتاج الآن إلى تحديث نص الصفحة الرئيسية HomePage سجل GuestBook بشكل صحيح باستخدام معلمة messages الجديدة.

lib / main.dart

Consumer<ApplicationState>(
  builder: (context, appState, _) => Column(
    crossAxisAlignment: CrossAxisAlignment.start,
    children: [
      if (appState.loginState == ApplicationLoginState.loggedIn) ...[
        const Header('Discussion'),
        GuestBook(
          addMessage: (message) =>
              appState.addMessageToGuestBook(message),
          messages: appState.guestBookMessages, // new
        ),
      ],
    ],
  ),
),

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

يقوم Cloud Firestore بمزامنة البيانات تلقائيًا وفوريًا مع العملاء المشتركين في قاعدة البيانات.

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

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

مراجعة التطبيق ص

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

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

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

يمكنك كتابة قواعد أمان لـ Cloud Firestore في وحدة تحكم Firebase:

  1. في قسم التطوير بوحدة تحكم Firebase ، انقر فوق قاعدة البيانات ، ثم حدد علامة التبويب القواعد (أو انقر هنا للانتقال مباشرةً إلى علامة التبويب القواعد ).
  2. يجب أن تشاهد قواعد الأمان الافتراضية التالية ، جنبًا إلى جنب مع تحذير حول كون القواعد عامة.

7767a2d2e64e7275.png

تحديد المجموعات

أولاً ، حدد المجموعات التي يكتب التطبيق البيانات إليها.

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

lib / main.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 / main.dart

  Future<void> init() async {
    await Firebase.initializeApp(
      options: DefaultFirebaseOptions.currentPlatform,
    );

    // 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) {
        _loginState = ApplicationLoginState.loggedIn;
        _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 {
        _loginState = ApplicationLoginState.loggedOut;
        _guestBookMessages = [];
        _guestBookSubscription?.cancel();
        _attendingSubscription?.cancel(); // new
      }
      notifyListeners();
    });
  }

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

lib / main.dart

enum Attending { yes, no, unknown }

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

lib / main.dart

class YesNoSelection extends StatelessWidget {
  const YesNoSelection({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: [
              ElevatedButton(
                style: ElevatedButton.styleFrom(elevation: 0),
                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),
              ElevatedButton(
                style: ElevatedButton.styleFrom(elevation: 0),
                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'),
              ),
            ],
          ),
        );
    }
  }
}

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

lib / main.dart

Consumer<ApplicationState>(
  builder: (context, appState, _) => Column(
    crossAxisAlignment: CrossAxisAlignment.start,
    children: [
      // Add from here
      if (appState.attendees >= 2)
        Paragraph('${appState.attendees} people going')
      else if (appState.attendees == 1)
        const Paragraph('1 person going')
      else
        const Paragraph('No one going'),
      // To here.
      if (appState.loginState == ApplicationLoginState.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;

    }
  }
}

(اختياري) يمكنك الآن عرض نتائج النقر فوق الأزرار. انتقل إلى لوحة معلومات Cloud Firestore في وحدة تحكم Firebase.

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

10. مبروك!

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

ما غطينا

  • مصادقة Firebase
  • سحابة Firestore
  • قواعد أمان Firebase

الخطوات التالية

  • هل تريد معرفة المزيد عن منتجات Firebase الأخرى؟ ربما تريد تخزين ملفات الصور التي يقوم المستخدمون بتحميلها؟ أو إرسال إخطارات إلى المستخدمين لديك؟ تحقق من وثائق Firebase . هل تريد معرفة المزيد حول مكونات Flutter الإضافية لـ Firebase؟ تحقق من FlutterFire لمزيد من المعلومات.
  • هل تريد معرفة المزيد عن Cloud Firestore؟ ربما تريد التعرف على المجموعات الفرعية والمعاملات؟ توجه إلى مختبر كود الويب Cloud Firestore للحصول على مختبر ترميز يتعمق أكثر في Cloud Firestore. أو تحقق من سلسلة YouTube هذه للتعرف على Cloud Firestore !

يتعلم أكثر

كيف سار الأمر؟

نحن نحب ملاحظاتك! يرجى ملء نموذج قصير (جدًا) هنا .