Firebase for Flutter را بشناسید

1. قبل از شروع

در این کد لبه، برخی از اصول اولیه Firebase برای ایجاد برنامه های موبایل Flutter برای اندروید و iOS را خواهید آموخت.

پیش نیازها

این کد لبه فرض می کند که شما با Flutter آشنا هستید و Flutter SDK و یک ویرایشگر را نصب کرده اید.

آنچه شما ایجاد خواهید کرد

در این کد لبه شما با استفاده از Flutter، یک رویداد RSVP و برنامه چت کتاب مهمان را در Android، iOS، وب و macOS خواهید ساخت. کاربران را با Firebase Authentication احراز هویت می‌کنید و داده‌ها را با استفاده از Cloud Firestore همگام‌سازی می‌کنید.

آنچه شما نیاز دارید

شما می توانید این کد لبه را با استفاده از هر یک از دستگاه های زیر اجرا کنید:

  • یک دستگاه فیزیکی (اندروید یا iOS) به رایانه شما متصل شده و روی حالت توسعه دهنده تنظیم شده است.
  • شبیه ساز iOS (نیاز به نصب ابزار Xcode دارد.)
  • شبیه ساز اندروید (نیاز به راه اندازی در Android Studio دارد.)

علاوه بر موارد فوق، شما همچنین نیاز دارید:

  • مرورگر دلخواه شما، مانند کروم.
  • یک IDE یا ویرایشگر متن به انتخاب شما، مانند Android Studio یا VS Code که با پلاگین های Dart و Flutter پیکربندی شده است.
  • آخرین نسخه stable Flutter (یا beta اگر از زندگی در لبه لذت می برید).
  • یک حساب Google، مانند یک حساب جی میل، برای ایجاد و مدیریت پروژه Firebase شما.
  • ابزار خط فرمان firebase به حساب جیمیل شما وارد شده است.
  • کد نمونه آزمایشگاه کد. برای نحوه دریافت کد به مرحله بعدی مراجعه کنید.

2. کد نمونه را دریافت کنید

بیایید با دانلود نسخه اولیه پروژه خود از GitHub شروع کنیم.

مخزن GitHub را از خط فرمان کلون کنید:

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

یا اگر ابزار cli GitHub را نصب کرده اید:

gh repo clone flutter/codelabs flutter-codelabs

کد نمونه باید در دایرکتوری flutter-codelabs که حاوی کد مجموعه ای از codelabs است، کلون شود. کد این Codelab در 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 دلخواه خود باز کنید یا وارد کنید. این دایرکتوری حاوی کد شروع برای codelab است که از یک برنامه Flutter Meetup هنوز کاربردی نیست.

فایل هایی را که می خواهید روی آنها کار کنید پیدا کنید

کد موجود در این برنامه در چندین دایرکتوری پخش شده است. این تقسیم عملکرد برای آسان‌تر کردن کار با گروه‌بندی کد بر اساس عملکرد طراحی شده است.

فایل های زیر را در پروژه پیدا کنید:

  • lib/main.dart : این فایل حاوی نقطه ورودی اصلی و ویجت برنامه است.
  • lib/src/widgets.dart : این فایل حاوی تعدادی ویجت است که به استانداردسازی سبک برنامه کمک می کند. اینها برای نوشتن صفحه برنامه شروع کننده استفاده می شوند.
  • lib/src/authentication.dart : این فایل شامل اجرای جزئی FirebaseUI Auth با مجموعه ای از ویجت ها برای ایجاد یک تجربه کاربری ورود به سیستم برای احراز هویت مبتنی بر ایمیل 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، منوی Build را در پانل سمت چپ گسترش دهید.
  2. روی Authentication کلیک کنید، و سپس روی دکمه شروع ، سپس برگه روش ورود به سیستم (یا برای رفتن مستقیم به برگه روش ورود اینجا را کلیک کنید).
  3. روی ایمیل/گذرواژه در لیست ارائه دهندگان ورود به سیستم کلیک کنید، سوئیچ Enable را روی موقعیت روشن قرار دهید و سپس روی ذخیره کلیک کنید. 58e3e3e23c2f16a4.png

Cloud Firestore را فعال کنید

برنامه وب از Cloud Firestore برای ذخیره پیام های چت و دریافت پیام های چت جدید استفاده می کند.

فعال کردن Cloud Firestore:

  1. در بخش ساخت کنسول Firebase، روی Cloud Firestore کلیک کنید.
  2. روی ایجاد پایگاه داده کلیک کنید. 99e8429832d23fa3.png
  1. گزینه Start in test mode را انتخاب کنید. سلب مسئولیت در مورد قوانین امنیتی را بخوانید. حالت تست تضمین می کند که شما می توانید آزادانه در طول توسعه در پایگاه داده بنویسید. روی Next کلیک کنید. 6be00e26c72ea032.png
  1. مکان پایگاه داده خود را انتخاب کنید (فقط می توانید از پیش فرض استفاده کنید). توجه داشته باشید که این مکان بعداً قابل تغییر نیست. 278656eefcfb0216.png
  2. روی Enable کلیک کنید.

4. پیکربندی Firebase

برای استفاده از Firebase با Flutter، باید فرآیندی را برای پیکربندی پروژه Flutter دنبال کنید تا از کتابخانه های FlutterFire به درستی استفاده شود:

  • وابستگی های FlutterFire را به پروژه خود اضافه کنید
  • پلتفرم مورد نظر را در پروژه Firebase ثبت کنید
  • فایل پیکربندی مخصوص پلتفرم را دانلود کرده و به کد اضافه کنید.

در دایرکتوری سطح بالای برنامه Flutter شما، زیرشاخه هایی به نام android ، ios ، macos و web وجود دارد. این دایرکتوری ها به ترتیب فایل های پیکربندی مخصوص پلتفرم را برای iOS و اندروید نگهداری می کنند.

پیکربندی وابستگی ها

شما باید کتابخانه های FlutterFire را برای دو محصول Firebase که در این برنامه استفاده می کنید - Firebase Auth و Cloud Firestore اضافه کنید. سه دستور زیر را برای اضافه کردن وابستگی ها اجرا کنید.

$ flutter pub add firebase_core

firebase_core کد مشترک مورد نیاز برای همه پلاگین های Firebase Flutter است.

$ flutter pub add firebase_auth

firebase_auth ادغام با قابلیت احراز هویت Firebase را امکان پذیر می کند.

$ flutter pub add cloud_firestore

cloud_firestore دسترسی به فضای ذخیره سازی اطلاعات Cloud 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 CLI زیرین بستگی دارد. اگر قبلاً این کار را انجام نداده اید، اطمینان حاصل کنید که Firebase CLI روی دستگاه شما نصب شده است.

سپس FlutterFire CLI را با اجرای دستور زیر نصب کنید:

$ dart pub global activate flutterfire_cli

پس از نصب، دستور flutterfire به صورت جهانی در دسترس خواهد بود.

در حال پیکربندی برنامه های شما

CLI اطلاعات پروژه Firebase و برنامه های کاربردی پروژه انتخاب شده را استخراج می کند تا تمام تنظیمات را برای یک پلت فرم خاص ایجاد کند.

در ریشه برنامه خود، دستور configure را اجرا کنید:

$ flutterfire configure

دستور پیکربندی شما را از طریق تعدادی از فرآیندها راهنمایی می کند:

  1. انتخاب پروژه Firebase (بر اساس فایل .firebaserc یا از کنسول Firebase).
  2. درخواست کنید که برای چه پلتفرم هایی (مانند Android، iOS، macOS و وب) پیکربندی را انجام دهید.
  3. مشخص کنید که کدام برنامه های Firebase برای پلتفرم های انتخابی باید برای استخراج پیکربندی استفاده شوند. به طور پیش‌فرض، CLI سعی می‌کند به طور خودکار برنامه‌های Firebase را بر اساس پیکربندی پروژه فعلی شما مطابقت دهد.
  4. یک فایل firebase_options.dart در پروژه خود ایجاد کنید.

macOS را پیکربندی کنید

Flutter در macOS برنامه‌های کاملاً sandbox شده را می‌سازد. از آنجایی که این برنامه در حال ادغام با استفاده از شبکه برای برقراری ارتباط با سرورهای 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>

برای جزئیات بیشتر به حقوق و جعبه ایمنی برنامه مراجعه کنید.

5. افزودن ورود به سیستم کاربر (RSVP)

اکنون که Firebase را به برنامه اضافه کرده اید، می توانید یک دکمه RSVP تنظیم کنید که افراد را با استفاده از Firebase Authentication ثبت می کند. برای اندروید بومی، iOS بومی و وب، بسته‌های FirebaseUI Auth از پیش ساخته شده‌اند، اما برای Flutter باید این قابلیت را ایجاد کنید.

پروژه ای که در مرحله 2 بازیابی کردید شامل مجموعه ای از ویجت ها بود که رابط کاربری را برای بیشتر جریان احراز هویت پیاده سازی می کند. شما منطق تجاری را برای ادغام احراز هویت Firebase در برنامه پیاده سازی خواهید کرد.

منطق کسب و کار با ارائه دهنده

می‌خواهید از بسته provider استفاده کنید تا یک شیء حالت برنامه متمرکز را در سراسر درخت ویجت‌های Flutter برنامه در دسترس قرار دهید. برای شروع، واردات را در بالای lib/main.dart :

lib/main.dart

import 'dart:async';                                     // new
import 'package:firebase_auth/firebase_auth.dart'        // new
    hide EmailAuthProvider, PhoneAuthProvider;           // new
import 'package:firebase_core/firebase_core.dart';       // new
import 'package:firebase_ui_auth/firebase_ui_auth.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 و Auth را معرفی می‌کنند، بسته provider را که برای در دسترس قرار دادن شیء وضعیت برنامه از طریق درخت ویجت استفاده می‌کنید، وارد می‌کنند و ویجت‌های احراز هویت را از firebase_ui_auth شامل می‌شوند.

این شیء وضعیت برنامه، ApplicationState ، یک مسئولیت اصلی برای این مرحله دارد و آن هشدار دادن به درخت ویجت است که به‌روزرسانی یک وضعیت احراز هویت شده وجود دارد. کلاس زیر را به انتهای lib/main.dart :

lib/main.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();
    });
  }
}

ما در اینجا از یک ارائه‌دهنده استفاده می‌کنیم تا وضعیت وضعیت ورود کاربران را به برنامه ارتباط دهیم، نه بیشتر. برای ورود به یک کاربر، ما از رابط های کاربری ارائه شده توسط firebase_ui_auth استفاده می کنیم که راهی عالی برای بوت استرپ سریع صفحه های ورود به سیستم برای برنامه های شما است.

یکپارچه سازی جریان احراز هویت

اکنون که حالت شروع برنامه را دارید، زمان آن است که وضعیت برنامه را به مقداردهی اولیه برنامه متصل کنید و جریان احراز هویت را به HomePage اصلی اضافه کنید. نقطه ورودی اصلی را برای ادغام وضعیت برنامه از طریق بسته provider به روز کنید:

lib/main.dart

void main() {
  // Modify from here
  WidgetsFlutterBinding.ensureInitialized();

  runApp(ChangeNotifierProvider(
    create: (context) => ApplicationState(),
    builder: ((context, child) => const App()),
  ));
  // to here.
}

تغییر در تابع main ، بسته ارائه‌دهنده را مسئول نمونه‌سازی شی حالت برنامه با استفاده از ویجت ChangeNotifierProvider . شما از این کلاس ارائه دهنده خاص استفاده می کنید زیرا شیء وضعیت برنامه ChangeNotifier را گسترش می دهد و این به بسته provider امکان می دهد بداند که چه زمانی ویجت های وابسته را دوباره نمایش دهد.

از آنجایی که ما از FirebaseUI برای Flutter استفاده می‌کنیم، می‌خواهیم برنامه خود را برای هدایت به صفحه‌های مختلف که FirebaseUI برای ما فراهم می‌کند، به‌روزرسانی کنیم. برای انجام این کار، یک ویژگی initialRoute اضافه می کنیم و صفحه های ترجیحی خود را اضافه می کنیم که می توانیم در زیر ویژگی routes کنیم. تغییرات باید به این صورت باشد:

lib/main.dart

class App extends StatelessWidget {
  const App({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      //Start adding here
      initialRoute: '/home',
      routes: {
        '/home': (context) {
          return const HomePage();
        },
        '/sign-in': ((context) {
          return SignInScreen(
            actions: [
              ForgotPasswordAction(((context, email) {
                Navigator.of(context)
                    .pushNamed('/forgot-password', arguments: {'email': email});
              })),
              AuthStateChangeAction(((context, state) {
                if (state is SignedIn || state is UserCreated) {
                  var user = (state is SignedIn)
                      ? state.user
                      : (state as UserCreated).credential.user;
                  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);
                  }
                  Navigator.of(context).pushReplacementNamed('/home');
                }
              })),
            ],
          );
        }),
        '/forgot-password': ((context) {
          final arguments = ModalRoute.of(context)?.settings.arguments
              as Map<String, dynamic>?;

          return ForgotPasswordScreen(
            email: arguments?['email'] as String,
            headerMaxExtent: 200,
          );
        }),
        '/profile': ((context) {
          return ProfileScreen(
            providers: [],
            actions: [
              SignedOutAction(
                ((context) {
                  Navigator.of(context).pushReplacementNamed('/home');
                }),
              ),
            ],
          );
        })
      },
      // end adding here
      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,
      ),
    );
  }
}

هر صفحه بر اساس وضعیت جدید جریان احراز هویت، نوع عملکرد متفاوتی دارد. پس از اکثر تغییرات وضعیت در احراز هویت، ما می‌توانیم به یک صفحه ترجیحی، چه صفحه اصلی یا یک صفحه دیگر مانند نمایه، مسیر را تغییر دهیم. در نهایت، با به‌روزرسانی روش ساخت HomePage ، وضعیت برنامه را با AuthFunc ادغام کنید:

lib/main.dart

class HomePage extends StatelessWidget {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Consumer<ApplicationState>(
        builder: (context, appState, child) => Scaffold(
              appBar: AppBar(
                title: const Text('Firebase Meetup'),
              ),
              body: ListView(
                children: <Widget>[
                  Image.asset('assets/codelab.png'),
                  const SizedBox(height: 8),
                  const IconAndDetail(Icons.calendar_today, 'October 30'),
                  const IconAndDetail(Icons.location_city, 'San Francisco'),
                  // Add from here
                  Consumer<ApplicationState>(
                    builder: (context, appState, _) => AuthFunc(
                        loggedIn: appState.loggedIn,
                        signOut: () {
                          FirebaseAuth.instance.signOut();
                        }),
                  ),
                  // to here
                  const Divider(
                    height: 8,
                    thickness: 1,
                    indent: 8,
                    endIndent: 8,
                    color: Colors.grey,
                  ),
                  const Header("What we'll be doing"),
                  const Paragraph(
                    'Join us for a day full of Firebase Workshops and Pizza!',
                  ),
                ],
              ),
            ));
  }
}

ویجت AuthFunc را نمونه سازی می کنید و آن را در یک ویجت Consumer قرار می دهید. ویجت Consumer به روش معمولی که بسته provider را می توان برای بازسازی بخشی از درخت هنگام تغییر وضعیت برنامه استفاده کرد. ویجت AuthFunc ویجت های تکمیلی است که اکنون آنها را آزمایش خواهید کرد.

تست جریان احراز هویت

cdf2d25e436bd48d.png

در اینجا شروع جریان احراز هویت است، جایی که کاربر می تواند روی دکمه RSVP ضربه بزند تا SignInScreen را شروع کند.

2a2cd6d69d172369.png

با وارد کردن ایمیل، سیستم تأیید می کند که کاربر قبلاً ثبت نام کرده است، در این صورت از کاربر رمز عبور خواسته می شود، در غیر این صورت اگر کاربر ثبت نام نکرده باشد، از طریق فرم ثبت نام اقدام می کند.

e5e65065dba36b54.png

مطمئن شوید که سعی کنید یک رمز عبور کوتاه (کمتر از شش کاراکتر) وارد کنید تا جریان رسیدگی به خطا را بررسی کنید. اگر کاربر ثبت نام کرده باشد، در عوض رمز عبور را مشاهده خواهد کرد.

در این صفحه مطمئن شوید که رمزهای عبور نادرست را وارد کنید تا بررسی خطا در این صفحه بررسی شود. در نهایت، پس از ورود کاربر، تجربه ورود به سیستم را مشاهده خواهید کرد که به کاربر امکان خروج مجدد را می دهد.

4ed811a25b0cf816.png

و با آن، شما یک جریان احراز هویت را پیاده سازی کرده اید. تبریک میگم

6. برای Cloud Firestore پیام بنویسید

دانستن اینکه کاربران در حال آمدن هستند عالی است، اما بیایید به مهمانان کار دیگری برای انجام در برنامه بدهیم. اگر آنها بتوانند در دفترچه مهمان پیام بگذارند چه می شود؟ آنها می توانند به اشتراک بگذارند که چرا برای آمدن هیجان زده هستند یا با چه کسی امیدوارند ملاقات کنند.

برای ذخیره پیام‌های چتی که کاربران در برنامه می‌نویسند، از Cloud Firestore استفاده می‌کنید.

مدل داده

Cloud Firestore یک پایگاه داده NoSQL است و داده های ذخیره شده در پایگاه داده به مجموعه ها، اسناد، فیلدها و زیر مجموعه ها تقسیم می شود. شما هر پیام چت را به عنوان یک سند در یک مجموعه سطح بالا به نام guestbook ذخیره خواهید کرد.

7c20dc8424bb1d84.png

افزودن پیام به Firestore

در این بخش، قابلیت نوشتن پیام‌های جدید به پایگاه داده را برای کاربران اضافه می‌کنید. ابتدا عناصر UI (فیلد فرم و دکمه ارسال) را اضافه می‌کنید و سپس کدی را اضافه می‌کنید که این عناصر را به پایگاه داده متصل می‌کند.

ابتدا، import برای بسته 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, 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 استفاده می کنید. برای اطلاعات بیشتر در مورد کلیدها و نحوه استفاده از آنها، لطفاً قسمت Flutter Widgets 101 "When to Use Keys" را ببینید.

همچنین به نحوه چیدمان ویجت ها توجه داشته باشید، شما یک 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)),

در حالی که این برای نمایش ویجت کافی است، اما برای انجام کار مفید کافی نیست. شما این کد را به زودی به روز خواهید کرد تا کاربردی شود.

پیش نمایش برنامه

کاربری که روی دکمه SEND کلیک می کند، قطعه کد زیر را فعال می کند. محتویات فیلد ورودی پیام را به مجموعه guestbook پایگاه داده اضافه می کند. به طور خاص، روش addMessageToGuestBook محتوای پیام را به یک سند جدید (با شناسه ای که به طور خودکار تولید می شود) به مجموعه guestbook اضافه می کند.

توجه داشته باشید که FirebaseAuth.instance.currentUser.uid مرجعی است به شناسه منحصربه‌فرد تولید شده خودکار که Firebase Authentication برای همه کاربرانی که وارد سیستم شده‌اند ارائه می‌کند.

تغییر دیگری در فایل 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 (!_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
}

سیم کشی UI به پایگاه داده

شما یک رابط کاربری دارید که در آن کاربر می‌تواند متنی را که می‌خواهد به کتاب مهمان اضافه کند، وارد کند، و شما کدی برای افزودن ورودی به 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, _) => 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> استفاده می کنید تا وضعیت برنامه را برای بخشی از درختی که در حال ارائه آن هستید در دسترس قرار دهید. این به شما امکان می دهد به شخصی که پیامی را در UI وارد می کند واکنش نشان دهید و آن را در پایگاه داده منتشر کنید. در بخش بعدی بررسی خواهید کرد که آیا پیام های اضافه شده در پایگاه داده منتشر شده است یا خیر.

تست ارسال پیام

  1. مطمئن شوید که وارد برنامه شده اید.
  2. پیامی مانند "Hey There!" را وارد کنید و سپس روی SEND کلیک کنید.

این عمل پیام را در پایگاه داده 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

  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/main.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 را بازسازی می کنید، و همچنین یک مرجع به این اشتراک ذخیره می کنید تا بتوانید بعداً اشتراک آن را لغو کنید. اینجا اتفاقات زیادی می افتد، و ارزش آن را دارد که مدتی را در یک دیباگر بگذرانیم تا ببینیم چه اتفاقی می افتد زمانی که یک مدل ذهنی واضح تر به دست آوریم.

برای اطلاعات بیشتر، به مستندات Cloud Firestore مراجعه کنید.

در ویجت GuestBook باید این حالت در حال تغییر را به رابط کاربری متصل کنید. ویجت را با افزودن لیستی از پیام ها به عنوان بخشی از پیکربندی آن تغییر می دهید.

lib/main.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/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، پیام های جدید را حذف، اصلاح یا اضافه کنید. هر تغییری باید در UI ظاهر شود.

تبریک می گویم! شما در حال خواندن اسناد Cloud Firestore در برنامه خود هستید!

بررسی برنامه p

8. قوانین اساسی امنیتی را تنظیم کنید

شما در ابتدا Cloud Firestore را برای استفاده از حالت تست تنظیم کردید، به این معنی که پایگاه داده شما برای خواندن و نوشتن باز است. با این حال، شما فقط باید از حالت تست در مراحل اولیه توسعه استفاده کنید. به عنوان بهترین روش، هنگام توسعه برنامه خود، باید قوانین امنیتی را برای پایگاه داده خود تنظیم کنید. امنیت باید در ساختار و رفتار برنامه شما یکپارچه باشد.

قوانین امنیتی به شما امکان می دهد دسترسی به اسناد و مجموعه های موجود در پایگاه داده خود را کنترل کنید. سینتکس قوانین انعطاف پذیر به شما امکان می دهد قوانینی ایجاد کنید که هر چیزی را از همه نوشته ها گرفته تا کل پایگاه داده تا عملیات روی یک سند خاص مطابقت دهد.

می توانید قوانین امنیتی برای Cloud Firestore را در کنسول Firebase بنویسید:

  1. در بخش Develop کنسول Firebase، روی Database کلیک کنید و سپس برگه Rules را انتخاب کنید (یا اینجا را کلیک کنید تا مستقیماً به برگه Rules بروید).
  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. مرحله پاداش: آنچه را که آموخته اید تمرین کنید

وضعیت RSVP یک شرکت کننده را ثبت کنید

در حال حاضر، برنامه شما فقط به افراد این امکان را می دهد که در صورت علاقه مندی به رویداد، چت را شروع کنند. همچنین، تنها راهی که می‌توانید از آمدن کسی مطلع شوید این است که آن را در چت پست کند. بیایید سازماندهی کنیم و به مردم اطلاع دهیم که چند نفر می آیند.

شما قصد دارید چند قابلیت جدید را به حالت برنامه اضافه کنید. اولین مورد این است که کاربرانی که به سیستم وارد شده اند می توانند نامزدی را برای حضور یا عدم حضورشان اعلام کنند. قابلیت دوم شمارنده تعداد افرادی است که واقعاً در آن شرکت می کنند.

در lib/main.dart ، موارد زیر را به بخش Accessors اضافه کنید تا کد UI برای تعامل با این حالت فعال شود:

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);

    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) {
        _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(
      {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: [
              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'),
              ),
            ],
          ),
        );
    }
  }
}

در مرحله بعد، باید روش ساخت صفحه اصلی را به روز کنید تا از مزایای HomePage استفاده YesNoSelection ، و به کاربر وارد شده اجازه دهید در صورت حضور در آن نامزد شود. همچنین تعداد شرکت کنندگان در این رویداد را نمایش می دهید.

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.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 ، از آنجایی که شما از Authentication UID به‌عنوان نام سند استفاده کرده‌اید، می‌توانید آن را بگیرید و بررسی کنید که uid همان سندی است که در حال نوشتن است. به همه اجازه می‌دهید فهرست شرکت‌کنندگان را بخوانند (زیرا هیچ داده خصوصی در آنجا وجود ندارد)، اما فقط سازنده باید بتواند آن را به‌روزرسانی کند.

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    // ... //
    match /attendees/{userId} {
      allow read: if true;
      allow write: if request.auth.uid == userId;
    }
  }
}

قوانین اعتبار سنجی را اضافه کنید

برای اطمینان از وجود تمام فیلدهای مورد انتظار در سند، اعتبارسنجی داده را اضافه کنید:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    // ... //
    match /attendees/{userId} {
      allow read: if true;
      allow write: if request.auth.uid == userId
          && "attending" in request.resource.data;

    }
  }
}

(اختیاری) اکنون می توانید نتایج کلیک روی دکمه ها را مشاهده کنید. به داشبورد Cloud Firestore خود در کنسول Firebase بروید.

پیش نمایش برنامه

10. تبریک می گویم!

شما از Firebase برای ساختن یک برنامه وب تعاملی و بلادرنگ استفاده کرده اید!

آنچه را پوشش داده ایم

  • احراز هویت Firebase
  • Cloud Firestore
  • قوانین امنیتی Firebase

مراحل بعدی

  • آیا می خواهید درباره سایر محصولات Firebase بیشتر بدانید؟ شاید بخواهید فایل های تصویری را که کاربران آپلود می کنند ذخیره کنید؟ یا برای کاربران خود نوتیفیکیشن بفرستید؟ اسناد Firebase را بررسی کنید. آیا می خواهید درباره افزونه های Flutter برای Firebase بیشتر بدانید؟ برای اطلاعات بیشتر FlutterFire را بررسی کنید.
  • آیا می خواهید درباره Cloud Firestore بیشتر بدانید؟ شاید بخواهید در مورد زیر مجموعه ها و تراکنش ها بیاموزید؟ برای یافتن کدهایی که در Cloud Firestore عمیق‌تر می‌شود، به آزمایشگاه کد وب Cloud Firestore بروید. یا برای آشنایی با Cloud Firestore این مجموعه YouTube را بررسی کنید !

بیشتر بدانید

چطور گذشت؟

بازخورد شما را دوست داریم! لطفاً یک فرم (بسیار) کوتاه را در اینجا پر کنید.