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

۱. قبل از شروع

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

پیش‌نیازها

آنچه یاد خواهید گرفت

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

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

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

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

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

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

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

  • یک مرورگر دلخواه، مثلاً گوگل کروم.
  • یک IDE یا ویرایشگر متن دلخواه شما که با افزونه‌های Dart و Flutter پیکربندی شده باشد، مانند Android Studio یا Visual Studio Code .
  • آخرین نسخه stable فلاتر یا beta اگر از زندگی در حاشیه لذت می‌برید.
  • یک حساب گوگل برای ایجاد و مدیریت پروژه Firebase شما.
  • رابط خط فرمان Firebase CLI) به حساب گوگل شما وارد شد.

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

نسخه اولیه پروژه خود را از GitHub دانلود کنید:

  1. از خط فرمان، مخزن گیت‌هاب را به دایرکتوری flutter-codelabs کپی کنید:
git clone https://github.com/flutter/codelabs.git flutter-codelabs

پوشه flutter-codelabs شامل کد مجموعه‌ای از codelabها است. کد این codelab در پوشه 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 است که هنوز کاربردی نشده است. وقتی برنامه را در IDE خود باز می‌کنید، متوجه خطاهای زمان کامپایل خواهید شد که در مرحله ۴ برطرف خواهند شد.

فایل‌هایی را که نیاز به کار دارند پیدا کنید

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

  • فایل‌های زیر را پیدا کنید:
    • lib/main.dart : این فایل شامل نقطه ورودی اصلی و ویجت برنامه است.
    • lib/home_page.dart : این فایل شامل ویجت صفحه اصلی است.
    • lib/src/widgets.dart : این فایل شامل تعدادی ویجت است که به استانداردسازی سبک برنامه کمک می‌کند. آن‌ها صفحه برنامه اولیه را تشکیل می‌دهند.
    • lib/src/authentication.dart : این فایل شامل پیاده‌سازی جزئی از احراز هویت به همراه مجموعه‌ای از ویجت‌ها برای ایجاد تجربه کاربری ورود به سیستم برای احراز هویت مبتنی بر ایمیل Firebase است. این ویجت‌ها برای جریان احراز هویت هنوز در برنامه اولیه استفاده نمی‌شوند، اما به زودی آنها را اضافه خواهید کرد.

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

فایل lib/main.dart را بررسی کنید

این برنامه از بسته google_fonts برای تبدیل Roboto به فونت پیش‌فرض در سراسر برنامه استفاده می‌کند. می‌توانید fonts.google.com را بررسی کنید و از فونت‌هایی که در آنجا کشف می‌کنید در بخش‌های مختلف برنامه استفاده کنید.

شما از ویجت‌های کمکی موجود در فایل lib/src/widgets.dart به شکل Header ، Paragraph و IconAndDetail استفاده می‌کنید. این ویجت‌ها کدهای تکراری را حذف می‌کنند تا بی‌نظمی در طرح‌بندی صفحه که در HomePage توضیح داده شده است را کاهش دهند. این امر همچنین باعث ایجاد ظاهر و حس یکپارچه می‌شود.

ظاهر برنامه شما در اندروید، iOS، وب و macOS به این شکل است:

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

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

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

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

۳. ایجاد و راه‌اندازی یک پروژه Firebase

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

ایجاد یک پروژه فایربیس

  1. با استفاده از حساب گوگل خود وارد کنسول فایربیس شوید.
  2. برای ایجاد یک پروژه جدید، روی دکمه کلیک کنید و سپس نام پروژه را وارد کنید (برای مثال، Firebase-Flutter-Codelab ).
  3. روی ادامه کلیک کنید.
  4. در صورت درخواست، شرایط Firebase را مرور و قبول کنید و سپس روی ادامه کلیک کنید.
  5. (اختیاری) دستیار هوش مصنوعی را در کنسول Firebase (با نام "Gemini در Firebase") فعال کنید.
  6. برای این codelab، به گوگل آنالیتیکس نیاز ندارید ، بنابراین گزینه گوگل آنالیتیکس را غیرفعال کنید .
  7. روی ایجاد پروژه کلیک کنید، منتظر بمانید تا پروژه شما آماده شود و سپس روی ادامه کلیک کنید.

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

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

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

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

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

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

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

صفحه تنظیمات احراز هویت فایربیس، با تب «روش ورود» انتخاب شده. این صفحه نشان می‌دهد که ارائه‌دهنده «ایمیل/رمز عبور» فعال است، در حالی که ارائه‌دهنده «لینک ایمیل (ورود بدون رمز عبور)» غیرفعال است.

فایر استور را راه اندازی کنید

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

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

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

۴. پیکربندی فایربیس

برای استفاده از Firebase با Flutter، باید کارهای زیر را انجام دهید تا پروژه Flutter را طوری پیکربندی کنید که از کتابخانه‌های FlutterFire به درستی استفاده کند:

  1. وابستگی‌های FlutterFire را به پروژه خود اضافه کنید.
  2. پلتفرم انتخاب شده را در پروژه Firebase ثبت کنید.
  3. فایل پیکربندی مخصوص پلتفرم را دانلود کنید و سپس آن را به کد اضافه کنید.

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

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

شما باید کتابخانه‌های FlutterFire را برای دو محصول Firebase که در این برنامه استفاده می‌کنید، یعنی Authentication و Firestore، اضافه کنید.

  • از خط فرمان، وابستگی‌های زیر را از ریشه برنامه خود اضافه کنید ( .../firebase-get-to-know-flutter/step_02 ):
$ flutter pub add firebase_core firebase_auth cloud_firestore provider firebase_ui_auth

برای استفاده از Firebase در برنامه Flutter خود، باید چند بسته تخصصی را با هم ترکیب کنید:

  • پکیج firebase_core : این نقطه شروع ضروری است. شما باید این پکیج را داشته باشید، زیرا سایر ابزارهای Firebase برای Flutter به آن وابسته هستند.
  • پکیج firebase_auth : این پکیج برای مدیریت حساب‌های کاربری است. به شما امکان می‌دهد ویژگی‌هایی مانند ثبت‌نام، ورود و خروج را اضافه کنید.
  • پکیج cloud_firestore : از این برای اتصال برنامه خود به پایگاه داده Firestore استفاده کنید و به شما امکان می‌دهد داده‌های برنامه خود را ذخیره و به آنها دسترسی داشته باشید.
  • پکیج firebase_ui_auth : این پکیج تنظیمات احراز هویت را بسیار سریع‌تر می‌کند. این پکیج ویجت‌های آماده‌ای (مانند صفحات ورود از پیش ساخته شده) را ارائه می‌دهد، بنابراین لازم نیست همه چیز را از ابتدا بسازید.
  • بسته‌ی provider : این یک انتخاب محبوب برای مدیریت وضعیت است. به برنامه‌ی شما کمک می‌کند تا اطلاعات (مانند اینکه چه کسی وارد سیستم شده است) را پیگیری کند و آن داده‌ها را در تمام صفحات مختلفی که به آنها نیاز دارند، در دسترس قرار دهد.

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

نصب رابط خط فرمان FlutterFire

رابط خط فرمان FlutterFire به رابط خط فرمان Firebase وابسته است.

  1. اگر قبلاً این کار را نکرده‌اید، Firebase CLI را روی دستگاه خود نصب کنید.
  2. رابط خط فرمان FlutterFire را نصب کنید:
$ dart pub global activate flutterfire_cli

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

برنامه‌های خود را پیکربندی کنید

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

در ریشه برنامه خود ( flutter-codelabs/firebase-get-to-know-flutter/step_02 )، دستور configure را اجرا کنید:

$ flutterfire configure

دستور پیکربندی فرآیندهای زیر را انجام می‌دهد:

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

پیکربندی macOS

فلاتر در 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 مراجعه کنید.

۵. قابلیت 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 را که شیء app state را در سراسر درخت ویجت در دسترس قرار می‌دهد، دریافت می‌کنند و ویجت‌های احراز هویت را از بسته‌ی 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() باعث می‌شود که بسته ارائه‌دهنده مسئول نمونه‌سازی شیء app state با ویجت ChangeNotifierProvider باشد. شما از این کلاس provider خاص استفاده می‌کنید زیرا شیء app state از کلاس 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,
            ),
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        textTheme: GoogleFonts.robotoTextTheme(
          Theme.of(context).textTheme,
        ),
        visualDensity: VisualDensity.adaptivePlatformDensity
      ),
      routerConfig: _router, // new
    );
  }
}

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

  1. در متد build کلاس 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

۶. نوشتن پیام در فایراستور

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

برای ذخیره پیام‌های چتی که کاربران در برنامه می‌نویسند، از 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 محتوای پیام را به یک سند جدید با یک شناسه (ID) که به طور خودکار در مجموعه 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({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'),
          Consumer<ApplicationState>(
            builder: (context, appState, _) => AuthFunc(
                loggedIn: appState.loggedIn,
                signOut: () {
                  FirebaseAuth.instance.signOut();
                }),
          ),
          const Divider(
            height: 8,
            thickness: 1,
            indent: 8,
            endIndent: 8,
            color: Colors.grey,
          ),
          const Header("What we'll be doing"),
          const Paragraph(
            'Join us for a day full of Firebase Workshops and Pizza!',
          ),
          // Modify from here...
          Consumer<ApplicationState>(
            builder: (context, appState, _) => Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                if (appState.loggedIn) ...[
                  const Header('Discussion'),
                  GuestBook(
                    addMessage: (message) =>
                        appState.addMessageToGuestBook(message),
                  ),
                ],
              ],
            ),
          ),
          // ...to here.
        ],
      ),
    );
  }
}

شما دو خطی را که در ابتدای این مرحله اضافه کردید، با پیاده‌سازی کامل جایگزین کردید. شما دوباره از Consumer<ApplicationState> برای در دسترس قرار دادن وضعیت برنامه برای بخشی از درخت که رندر می‌کنید، استفاده می‌کنید. این به شما امکان می‌دهد به کسی که پیامی را در رابط کاربری وارد می‌کند واکنش نشان دهید و آن را در پایگاه داده منتشر کنید. در بخش بعدی، آزمایش می‌کنید که آیا پیام‌های اضافه شده در پایگاه داده منتشر می‌شوند یا خیر.

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

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

این عمل پیام را در پایگاه داده Firestore شما می‌نویسد. با این حال، شما این پیام را در برنامه Flutter واقعی خود مشاهده نمی‌کنید زیرا هنوز نیاز به پیاده‌سازی بازیابی داده‌ها دارید که در مرحله بعدی انجام می‌دهید. با این حال، در داشبورد پایگاه داده کنسول Firebase، می‌توانید پیام اضافه شده خود را در مجموعه guestbook مشاهده کنید. اگر پیام‌های بیشتری ارسال کنید، اسناد بیشتری به مجموعه guestbook خود اضافه می‌کنید. برای مثال، قطعه کد زیر را مشاهده کنید:

۷۱۳۸۷۰af0b3b63c.png

۷. خواندن پیام‌ها

خیلی خوبه که مهمان‌ها می‌تونن توی پایگاه داده پیام بنویسن، اما هنوز نمی‌تونن اون‌ها رو توی برنامه ببینن. وقتشه این مشکل رو حل کنیم!

همگام‌سازی پیام‌ها

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

  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 که state و getterها را تعریف می‌کنید، خطوط زیر را اضافه کنید:

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;
        // Add from here...
        _guestBookSubscription = FirebaseFirestore.instance
            .collection('guestbook')
            .orderBy('timestamp', descending: true)
            .snapshots()
            .listen((snapshot) {
          _guestBookMessages = [];
          for (final document in snapshot.docs) {
            _guestBookMessages.add(
              GuestBookMessage(
                name: document.data()['name'] as String,
                message: document.data()['text'] as String,
              ),
            );
          }
          notifyListeners();
        });
        // ...to here.
      } else {
        _loggedIn = false;
        // Add from here...
        _guestBookMessages = [];
        _guestBookSubscription?.cancel();
        // to here.
      }
      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
  State<GuestBook> createState() => _GuestBookState();
}
  1. در _GuestBookState ، متد build را به صورت زیر تغییر دهید تا این پیکربندی نمایش داده شود:

lib/guest_book.dart

class _GuestBookState extends State<GuestBook> {
  final _formKey = GlobalKey<FormState>(debugLabel: '_GuestBookState');
  final _controller = TextEditingController();

  @override
  Widget build(BuildContext context) {
    // Modify from here...
    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: [
                      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 پوشش می‌دهید و سپس یک مجموعه for در انتهای فرزندان Column اضافه می‌کنید تا برای هر پیام در لیست پیام‌ها، یک Paragraph جدید ایجاد شود.

  1. بدنه‌ی HomePage را به‌روزرسانی کنید تا GuestBook با پارامتر جدید messages به درستی بسازد:

lib/home_page.dart

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

همگام‌سازی پیام آزمایشی

فایراستور به طور خودکار و فوری داده‌ها را با مشتریانی که در پایگاه داده مشترک شده‌اند، همگام‌سازی می‌کند.

تست همگام‌سازی پیام:

  1. در برنامه، پیام‌هایی را که قبلاً در پایگاه داده ایجاد کرده‌اید، پیدا کنید.
  2. پیام‌های جدید بنویسید. آنها فوراً ظاهر می‌شوند.
  3. فضای کاری خود را در چندین پنجره یا تب باز کنید. پیام‌ها به صورت آنی در بین پنجره‌ها و تب‌ها همگام‌سازی می‌شوند.
  4. اختیاری: در منوی پایگاه داده کنسول فایربیس، پیام‌های جدید را به صورت دستی حذف، اصلاح یا اضافه کنید. همه تغییرات در رابط کاربری ظاهر می‌شوند.

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

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

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

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

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

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

۸. قوانین امنیتی اولیه را تنظیم کنید

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

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

قوانین امنیتی اولیه را تنظیم کنید:

  1. در منوی Develop کنسول Firebase، روی Database > Rules کلیک کنید. باید قوانین امنیتی پیش‌فرض زیر و هشداری در مورد عمومی بودن قوانین را مشاهده کنید:

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.
  }
}

از آنجا که شما از شناسه کاربری احراز هویت (Authentication UID) به عنوان یک فیلد در هر سند guestbook استفاده کرده‌اید، می‌توانید شناسه کاربری احراز هویت را دریافت کرده و تأیید کنید که هر کسی که سعی در نوشتن در سند دارد، دارای شناسه کاربری احراز هویت منطبق است.

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

۹. مرحله‌ی اضافه: آموخته‌هایتان را تمرین کنید

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

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

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

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

lib/app_state.dart

int _attendees = 0;
int get attendees => _attendees;

Attending _attending = Attending.unknown;
StreamSubscription<DocumentSnapshot>? _attendingSubscription;
Attending get attending => _attending;
set attending(Attending attending) {
  final userDoc = FirebaseFirestore.instance
      .collection('attendees')
      .doc(FirebaseAuth.instance.currentUser!.uid);
  if (attending == Attending.yes) {
    userDoc.set(<String, dynamic>{'attending': true});
  } else {
    userDoc.set(<String, dynamic>{'attending': false});
  }
}
  1. متد init() مربوط به ApplicationState را به صورت زیر به‌روزرسانی کنید:

lib/app_state.dart

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

    FirebaseUIAuth.configureProviders([
      EmailAuthProvider(),
    ]);

    // Add from here...
    FirebaseFirestore.instance
        .collection('attendees')
        .where('attending', isEqualTo: true)
        .snapshots()
        .listen((snapshot) {
      _attendees = snapshot.docs.length;
      notifyListeners();
    });
    // ...to here.

    FirebaseAuth.instance.userChanges().listen((user) {
      if (user != null) {
        _loggedIn = true;
        _emailVerified = user.emailVerified;
        _guestBookSubscription = FirebaseFirestore.instance
            .collection('guestbook')
            .orderBy('timestamp', descending: true)
            .snapshots()
            .listen((snapshot) {
          _guestBookMessages = [];
          for (final document in snapshot.docs) {
            _guestBookMessages.add(
              GuestBookMessage(
                name: document.data()['name'] as String,
                message: document.data()['text'] as String,
              ),
            );
          }
          notifyListeners();
        });
        // Add from here...
        _attendingSubscription = FirebaseFirestore.instance
            .collection('attendees')
            .doc(user.uid)
            .snapshots()
            .listen((snapshot) {
          if (snapshot.data() != null) {
            if (snapshot.data()!['attending'] as bool) {
              _attending = Attending.yes;
            } else {
              _attending = Attending.no;
            }
          } else {
            _attending = Attending.unknown;
          }
          notifyListeners();
        });
        // ...to here.
      } else {
        _loggedIn = false;
        _emailVerified = false;
        _guestBookMessages = [];
        _guestBookSubscription?.cancel();
        _attendingSubscription?.cancel(); // new
      }
      notifyListeners();
    });
  }

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

  1. شمارش زیر را در بالای فایل lib/app_state.dart اضافه کنید.

lib/app_state.dart

enum Attending { yes, no, unknown }
  1. یک فایل جدید yes_no_selection.dart ایجاد کنید، یک ویجت جدید تعریف کنید که مانند دکمه‌های رادیویی عمل کند:

lib/yes_no_selection.dart

import 'package:flutter/material.dart';

import 'app_state.dart';
import 'src/widgets.dart';

class YesNoSelection extends StatelessWidget {
  const YesNoSelection(
      {super.key, required this.state, required this.onSelection});
  final Attending state;
  final void Function(Attending selection) onSelection;

  @override
  Widget build(BuildContext context) {
    switch (state) {
      case Attending.yes:
        return Padding(
          padding: const EdgeInsets.all(8.0),
          child: Row(
            children: [
              FilledButton(
                onPressed: () => onSelection(Attending.yes),
                child: const Text('YES'),
              ),
              const SizedBox(width: 8),
              TextButton(
                onPressed: () => onSelection(Attending.no),
                child: const Text('NO'),
              ),
            ],
          ),
        );
      case Attending.no:
        return Padding(
          padding: const EdgeInsets.all(8.0),
          child: Row(
            children: [
              TextButton(
                onPressed: () => onSelection(Attending.yes),
                child: const Text('YES'),
              ),
              const SizedBox(width: 8),
              FilledButton(
                onPressed: () => onSelection(Attending.no),
                child: const Text('NO'),
              ),
            ],
          ),
        );
      default:
        return Padding(
          padding: const EdgeInsets.all(8.0),
          child: Row(
            children: [
              StyledButton(
                onPressed: () => onSelection(Attending.yes),
                child: const Text('YES'),
              ),
              const SizedBox(width: 8),
              StyledButton(
                onPressed: () => onSelection(Attending.no),
                child: const Text('NO'),
              ),
            ],
          ),
        );
    }
  }
}

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

  1. متد build() در HomePage را به‌روزرسانی کنید تا از YesNoSelection بهره‌مند شود، به کاربر وارد شده اجازه دهید تا مشخص کند که آیا در رویداد شرکت می‌کند یا خیر و تعداد شرکت‌کنندگان در رویداد را نمایش دهد:

lib/home_page.dart

import 'yes_no_selection.dart';             // new

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 ، شناسه‌ی احراز هویت (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;
    }
  }
}

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

  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

۱۰. تبریک می‌گویم!

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

بیشتر بدانید