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

1. قبل از شروع

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

پیش نیازها

چیزی که یاد خواهید گرفت

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

صفحه اصلی برنامه در اندروید

صفحه اصلی برنامه در iOS

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

هر یک از دستگاه های زیر:

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

شما همچنین به موارد زیر نیاز دارید:

  • مرورگر دلخواه شما، مانند گوگل کروم.
  • یک 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 حاوی کد مجموعه ای از کدلب ها است. کد این کد لبه در دایرکتوری 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 : این فایل شامل اجرای بخشی از Authentication با مجموعه ای از ویجت ها برای ایجاد یک تجربه کاربری برای ورود به سیستم برای احراز هویت مبتنی بر ایمیل Firebase است. این ویجت‌ها برای جریان احراز هویت هنوز در برنامه شروع استفاده نمی‌شوند، اما به زودی آنها را اضافه می‌کنید.

برای ساخت بقیه برنامه، فایل‌های اضافی را در صورت نیاز اضافه می‌کنید.

فایل lib/main.dart را مرور کنید

این برنامه از بسته google_fonts استفاده می کند تا Roboto را به عنوان فونت پیش فرض در سراسر برنامه تبدیل کند. می توانید fonts.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. در قسمت Project name ، Firebase-Flutter-Codelab را وارد کنید و سپس روی Continue کلیک کنید.

4395e4e67c08043a.png

  1. روی گزینه های ایجاد پروژه کلیک کنید. اگر از شما خواسته شد، شرایط Firebase را بپذیرید، اما از تنظیم Google Analytics صرفنظر کنید زیرا از آن برای این برنامه استفاده نخواهید کرد.

b7138cde5f2c7b61.png

برای کسب اطلاعات بیشتر درباره پروژه‌های Firebase، به درک پروژه‌های Firebase مراجعه کنید.

این برنامه از محصولات Firebase زیر استفاده می کند که برای برنامه های وب در دسترس هستند:

  • احراز هویت: به کاربران اجازه می دهد وارد برنامه شما شوند.
  • Firestore: داده های ساخت یافته را در فضای ابری ذخیره می کند و در صورت تغییر داده ها، اعلان های فوری دریافت می کند.
  • قوانین امنیتی Firebase: پایگاه داده شما را ایمن می کند.

برخی از این محصولات نیاز به پیکربندی خاصی دارند یا باید آنها را در کنسول Firebase فعال کنید.

احراز هویت ورود به ایمیل را فعال کنید

  1. در پنجره نمای کلی پروژه کنسول Firebase، منوی ساخت را گسترش دهید.
  2. روی تأیید هویت > شروع به کار > روش ورود به سیستم > ایمیل/گذرواژه > فعال کردن > ذخیره کلیک کنید.

58e3e3e23c2f16a4.png

Firestore را راه اندازی کنید

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

در اینجا نحوه راه اندازی Firestore در پروژه Firebase آمده است:

  1. در پنل سمت چپ کنسول Firebase، Build را گسترش دهید و سپس پایگاه داده Firestore را انتخاب کنید.
  2. روی ایجاد پایگاه داده کلیک کنید.
  3. شناسه پایگاه داده را روی (default) بگذارید.
  4. یک مکان برای پایگاه داده خود انتخاب کنید، سپس روی Next کلیک کنید.
    برای یک برنامه واقعی، می خواهید مکانی را انتخاب کنید که به کاربران شما نزدیک باشد.
  5. در حالت تست روی Start کلیک کنید. سلب مسئولیت در مورد قوانین امنیتی را بخوانید.
    بعداً در این کد، قوانین امنیتی را برای ایمن سازی داده های خود اضافه خواهید کرد. بدون افزودن قوانین امنیتی برای پایگاه داده خود، یک برنامه را به صورت عمومی توزیع یا افشا نکنید .
  6. روی ایجاد کلیک کنید.

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 ادغام با Authentication را امکان پذیر می کند.

$ flutter pub add cloud_firestore

بسته cloud_firestore دسترسی به ذخیره سازی داده Firestore را امکان پذیر می کند.

$ flutter pub add provider

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

$ flutter pub add firebase_ui_auth

شما بسته های مورد نیاز را اضافه کرده اید، اما همچنین باید پروژه های iOS، Android، macOS و Web runner را برای استفاده مناسب از Firebase پیکربندی کنید. شما همچنین از بسته provider استفاده می کنید که امکان جداسازی منطق تجاری از منطق نمایش را فراهم می کند.

FlutterFire CLI را نصب کنید

FlutterFire CLI به Firebase CLI زیرین بستگی دارد.

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

اکنون که Firebase را به برنامه اضافه کردید، می توانید یک دکمه RSVP ایجاد کنید که افراد را با احراز هویت ثبت می کند. برای اندروید، بومی 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 را تغییر دهید:

lib/main.dart

import 'package:firebase_ui_auth/firebase_ui_auth.dart'; // new
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';               // new
import 'package:google_fonts/google_fonts.dart';
import 'package:provider/provider.dart';                 // new

import 'app_state.dart';                                 // new
import 'home_page.dart';
  1. وضعیت برنامه را با مقداردهی اولیه برنامه وصل کنید و سپس جریان احراز هویت را به HomePage اضافه کنید:

lib/main.dart

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

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

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

  1. با ایجاد یک پیکربندی GoRouter برنامه خود را برای مدیریت پیمایش به صفحات مختلف که FirebaseUI برای شما فراهم می کند، به روز کنید:

lib/main.dart

// Add GoRouter configuration outside the App class
final _router = GoRouter(
  routes: [
    GoRoute(
      path: '/',
      builder: (context, state) => const HomePage(),
      routes: [
        GoRoute(
          path: 'sign-in',
          builder: (context, state) {
            return SignInScreen(
              actions: [
                ForgotPasswordAction(((context, email) {
                  final uri = Uri(
                    path: '/sign-in/forgot-password',
                    queryParameters: <String, String?>{
                      'email': email,
                    },
                  );
                  context.push(uri.toString());
                })),
                AuthStateChangeAction(((context, state) {
                  final user = switch (state) {
                    SignedIn state => state.user,
                    UserCreated state => state.credential.user,
                    _ => null
                  };
                  if (user == null) {
                    return;
                  }
                  if (state is UserCreated) {
                    user.updateDisplayName(user.email!.split('@')[0]);
                  }
                  if (!user.emailVerified) {
                    user.sendEmailVerification();
                    const snackBar = SnackBar(
                        content: Text(
                            'Please check your email to verify your email address'));
                    ScaffoldMessenger.of(context).showSnackBar(snackBar);
                  }
                  context.pushReplacement('/');
                })),
              ],
            );
          },
          routes: [
            GoRoute(
              path: 'forgot-password',
              builder: (context, state) {
                final arguments = state.uri.queryParameters;
                return ForgotPasswordScreen(
                  email: arguments['email'],
                  headerMaxExtent: 200,
                );
              },
            ),
          ],
        ),
        GoRoute(
          path: 'profile',
          builder: (context, state) {
            return ProfileScreen(
              providers: const [],
              actions: [
                SignedOutAction((context) {
                  context.pushReplacement('/');
                }),
              ],
            );
          },
        ),
      ],
    ),
  ],
);
// end of GoRouter configuration

// Change MaterialApp to MaterialApp.router and add the routerConfig
class App extends StatelessWidget {
  const App({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      title: 'Firebase Meetup',
      theme: ThemeData(
        buttonTheme: Theme.of(context).buttonTheme.copyWith(
              highlightColor: Colors.deepPurple,
            ),
        primarySwatch: Colors.deepPurple,
        textTheme: GoogleFonts.robotoTextTheme(
          Theme.of(context).textTheme,
        ),
        visualDensity: VisualDensity.adaptivePlatformDensity,
        useMaterial3: true,
      ),
      routerConfig: _router, // new
    );
  }
}

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

  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 قرار می دهید. ویجت Consumer روش معمولی است که بسته provider را می توان برای بازسازی بخشی از درخت هنگام تغییر وضعیت برنامه استفاده کرد. ویجت AuthFunc ویجت های تکمیلی است که شما آزمایش می کنید.

جریان احراز هویت را آزمایش کنید

cdf2d25e436bd48d.png

  1. در برنامه، روی دکمه RSVP ضربه بزنید تا 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)),

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

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

صفحه اصلی برنامه در اندروید با یکپارچه سازی چت

صفحه اصلی برنامه در iOS با یکپارچه سازی چت

صفحه اصلی برنامه در وب با ادغام چت

صفحه اصلی برنامه در macOS با یکپارچه سازی چت

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

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

  • در فایل 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> استفاده می کنید تا وضعیت برنامه را برای بخشی از درختی که رندر می کنید در دسترس قرار دهید. این به شما امکان می دهد به شخصی که پیامی را در UI وارد می کند واکنش نشان دهید و آن را در پایگاه داده منتشر کنید. در بخش بعدی، بررسی می کنید که آیا پیام های اضافه شده در پایگاه داده منتشر شده اند یا خیر.

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

  1. در صورت لزوم، وارد برنامه شوید.
  2. پیامی مانند Hey there! و سپس SEND را کلیک کنید.

این عمل پیام را در پایگاه داده 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. برای ساخت صحیح GuestBook با پارامتر messages جدید، بدنه HomePage را به روز کنید:

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

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

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

صفحه اصلی برنامه در اندروید با یکپارچه سازی چت

صفحه اصلی برنامه در 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 احراز هویت را دریافت کنید و تأیید کنید که هر کسی که قصد نوشتن در سند را دارد دارای یک 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. مرحله پاداش: آنچه را که آموخته اید تمرین کنید

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

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

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

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

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. برای استفاده از YesNoSelection متد build() HomePage را به‌روزرسانی کنید، یک کاربر وارد شده را قادر می‌سازد تا نامزد حضور در آن باشد و تعداد شرکت‌کنندگان را برای رویداد نمایش دهد:

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 برای ساختن یک برنامه وب تعاملی و بلادرنگ استفاده کردید!

بیشتر بدانید