היכרות עם Firebase for Flutter

1. לפני שמתחילים

בקודלאב הזה תלמדו את העקרונות הבסיסיים של Firebase כדי ליצור אפליקציות לנייד ב-Flutter ל-Android ול-iOS.

דרישות מוקדמות

מה תלמדו

  • איך יוצרים אישור השתתפות באירוע ואפליקציית צ'אט בספר האורחים ב-Android, ב-iOS, באינטרנט וב-macOS באמצעות Flutter.
  • איך לאמת משתמשים באמצעות אימות ב-Firebase ולסנכרן נתונים עם Firestore.

מסך הבית של האפליקציה ב-Android

מסך הבית של האפליקציה ב-iOS

מה צריך להכין

כל אחד מהמכשירים הבאים:

  • מכשיר Android או iOS פיזי שמחובר למחשב ומוגדר למצב פיתוח.
  • הסימולטור של iOS (נדרשים כלי Xcode).
  • האמולטור של Android (נדרשת הגדרה ב-Android Studio).

בנוסף, צריך:

  • דפדפן לבחירתך, כמו Google Chrome.
  • סביבת פיתוח משולבת (IDE) או עורך טקסט לבחירתכם שמוגדרים עם הפלאגינים של Dart ו-Flutter, כמו Android Studio או Visual Studio Code.
  • הגרסה האחרונה stable של Flutter או beta אם אתם אוהבים לחיות על הקצה.
  • חשבון Google ליצירה ולניהול של פרויקט Firebase.
  • ה-CLI של Firebase שמחובר לחשבון Google שלכם.

2. קבלת קוד לדוגמה

מורידים את הגרסה הראשונית של הפרויקט מ-GitHub:

  1. משורת הפקודה, משכפלים את מאגר GitHub בספרייה flutter-codelabs:
git clone https://github.com/flutter/codelabs.git flutter-codelabs

הספרייה flutter-codelabs מכילה את הקוד של אוסף של Codelabs. הקוד של ה-Codelab הזה נמצא בספרייה flutter-codelabs/firebase-get-to-know-flutter. הספרייה מכילה סדרה של קובצי snapshot שמראים איך הפרויקט אמור להיראות בסוף כל שלב. לדוגמה, אתם נמצאים בשלב השני.

  1. מאתרים את הקבצים התואמים לשלב השני:
cd flutter-codelabs/firebase-get-to-know-flutter/step_02

אם אתם רוצים לדלג קדימה או לראות איך משהו אמור להיראות אחרי שלב כלשהו, כדאי לעיין בספרייה שנקראת אחרי השלב שבו אתם מעוניינים.

ייבוא האפליקציה למתחילים

  • פותחים או מייבאים את הספרייה flutter-codelabs/firebase-get-to-know-flutter/step_02 בסביבת הפיתוח המשולבת (IDE) המועדפת. הספרייה הזו מכילה את קוד ההתחלה של סדנת הקוד, שכולל אפליקציית Flutter ל-Meetup שעדיין לא פונקציונלית.

איתור הקבצים שדורשים טיפול

הקוד באפליקציה הזו מפוזר במספר ספריות. פיצול הפונקציונליות הופך את העבודה לקלה יותר, כי היא מקבצת את הקוד לפי פונקציונליות.

  • מאתרים את הקבצים הבאים:
    • 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. כך אפשר גם לשמור על עיצוב עקבי.

כך נראה האפליקציה ב-Android, ב-iOS, באינטרנט וב-macOS:

במסך הבית של האפליקציה ב-Android

מסך הבית של האפליקציה ב-iOS

מסך הבית של האפליקציה באינטרנט

במסך הבית של האפליקציה ב-macOS

3. יצירה והגדרה של פרויקט Firebase

הצגת פרטי האירוע שימושית מאוד לאורחים, אבל היא לא שימושית במיוחד לכל אחד אחר. אתם צריכים להוסיף פונקציונליות דינמית לאפליקציה. כדי לעשות את זה, תצטרכו לקשר את Firebase לאפליקציה שלכם. כדי להתחיל לעבוד עם Firebase, עליכם ליצור ולהגדיר פרויקט Firebase.

יוצרים פרויקט Firebase

  1. נכנסים ל-Firebase.
  2. במסוף, לוחצים על Add Project או Create a project.
  3. בשדה Project name (שם הפרויקט), מזינים Firebase-Flutter-Codelab ואז לוחצים על Continue (המשך).

4395e4e67c08043a.png

  1. לוחצים על האפשרויות ליצירת פרויקט. אם מופיעה בקשה, מאשרים את התנאים של Firebase אבל מדלגים על ההגדרה של Google Analytics כי לא תשתמשו בו באפליקציה הזו.

b7138cde5f2c7b61.png

מידע נוסף על פרויקטים ב-Firebase זמין במאמר הסבר על פרויקטים ב-Firebase.

האפליקציה משתמשת במוצרי Firebase הבאים, שזמינים לאפליקציות אינטרנט:

  • אימות: מאפשר למשתמשים להיכנס לאפליקציה.
  • Firestore: שמירת נתונים מובְנים בענן וקבלת התראות מיידיות כשהנתונים משתנים.
  • כללי אבטחה של Firebase: אבטחה של מסד הנתונים.

לחלק מהמוצרים האלה צריך לקבוע הגדרות מיוחדות או להפעיל אותם במסוף Firebase.

הפעלת אימות של כניסה לחשבון באימייל

  1. בחלונית Project Overview במסוף Firebase, מרחיבים את התפריט Build.
  2. לוחצים על אימות > תחילת העבודה > שיטת כניסה > אימייל/סיסמה > הפעלה > שמירה.

58e3e3e23c2f16a4.png

הגדרת Firestore

אפליקציית האינטרנט משתמשת ב-Firestore כדי לשמור הודעות צ'אט ולקבל הודעות צ'אט חדשות.

כך מגדירים את Firestore בפרויקט Firebase:

  1. בחלונית הימנית של מסוף Firebase, מרחיבים את Build ובוחרים באפשרות Firestore database.
  2. לוחצים על Create database.
  3. משאירים את הערך (default) בשדה Database ID.
  4. בוחרים מיקום למסד הנתונים ולוחצים על הבא.
    באפליקציה אמיתית, כדאי לבחור מיקום קרוב למשתמשים.
  5. לוחצים על הפעלה במצב בדיקה. קוראים את כתב הוויתור לגבי כללי האבטחה.
    בהמשך הסדנה תוסיפו כללי אבטחה כדי לאבטח את הנתונים. אסור להפיץ או לחשוף אפליקציה באופן ציבורי בלי להוסיף כללי אבטחה למסד הנתונים.
  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 שמאפשרת להפריד בין לוגיקה עסקית לבין לוגיקה של תצוגה.

התקנת ה-CLI של FlutterFire

ה-CLI של FlutterFire תלוי ב-CLI של Firebase שמתחתיו.

  1. אם עוד לא עשיתם זאת, התקינו את Firebase CLI במחשב.
  2. מתקינים את ה-CLI של FlutterFire:
$ 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. הוספת פונקציונליות של אישור השתתפות

עכשיו, אחרי שהוספתם את Firebase לאפליקציה, אתם יכולים ליצור לחצן אישור השתתפות שרושם אנשים באמצעות אימות. למודעות מותאמות ל-Android, ל-iOS ולאינטרנט, יש חבילות FirebaseUI Auth מוכנות מראש, אבל צריך לפתח את היכולת הזו בשביל Flutter.

הפרויקט שאחזרתם מקודם כלל קבוצה של ווידג'טים שמטמיעים את ממשק המשתמש לרוב תהליך האימות. אתם מטמיעים את הלוגיקה העסקית כדי לשלב אימות באפליקציה.

הוספת לוגיקה עסקית באמצעות החבילה Provider

משתמשים בחבילת provider כדי להפוך אובייקט מרוכז של מצב האפליקציה לזמין בכל עץ האפליקציה של Flutter:

  1. יוצרים קובץ חדש בשם app_state.dart עם התוכן הבא:

lib/app_state.dart

import 'package:firebase_auth/firebase_auth.dart'
    hide EmailAuthProvider, PhoneAuthProvider;
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_ui_auth/firebase_ui_auth.dart';
import 'package:flutter/material.dart';

import 'firebase_options.dart';

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

  bool _loggedIn = false;
  bool get loggedIn => _loggedIn;

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

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

    FirebaseAuth.instance.userChanges().listen((user) {
      if (user != null) {
        _loggedIn = true;
      } else {
        _loggedIn = false;
      }
      notifyListeners();
    });
  }
}

הצהרות import מאפשרות להשתמש ב-Firebase Core וב-Auth, גוררות את החבילה provider שמאפשרת להשתמש באובייקט מצב האפליקציה בכל עץ הווידג'טים, וכוללות את הווידג'טים לאימות מהחבילה firebase_ui_auth.

לאובייקט המצב של האפליקציה ApplicationState יש אחריות ראשית אחת בשלב הזה, והיא להתריע לעץ הווידג'טים על עדכון למצב מאומת.

אתם משתמשים בספק כדי להעביר את מצב סטטוס ההתחברות של המשתמש לאפליקציה בלבד. כדי לאפשר למשתמש להתחבר, אתם משתמשים בממשקי המשתמש שסופקו על ידי חבילת firebase_ui_auth. זו דרך מצוינת להפעיל במהירות מסכי התחברות באפליקציות שלכם.

משלבים את תהליך האימות

  1. משנים את פעולות הייבוא בחלק העליון של הקובץ lib/main.dart:

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. כדי לעדכן את האפליקציה כך שתטפל בניווט למסכים שונים ש-FirebaseUI מספק, יוצרים הגדרה של GoRouter:

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. בשיטת ה-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. ווידג'ט הצרכן הוא הדרך הרגילה שבה ניתן להשתמש בחבילה provider כדי לבנות מחדש חלק מהעץ כשמצב האפליקציה משתנה. הווידג'ט AuthFunc הוא הווידג'טים המשניים שאתם בודקים.

בדיקת תהליך האימות

cdf2d25e436bd48d.png

  1. באפליקציה, מקישים על הלחצן אישור הגעה כדי להתחיל את SignInScreen.

2a2cd6d69d172369.png

  1. מזינים כתובת אימייל. אם כבר נרשמת, המערכת תבקש ממך להזין סיסמה. אם לא, המערכת תבקש מכם למלא את טופס ההרשמה.

e5e65065dba36b54.png

  1. כדי לבדוק את תהליך טיפול השגיאות, מזינים סיסמה באורך של פחות משישה תווים. אם אתם רשומים, תופיע הסיסמה של החשבון הזה.
  2. צריך להזין סיסמאות שגויות כדי לבדוק את תהליך הטיפול בשגיאות.
  3. מזינים את הסיסמה הנכונה. תוצג חוויית ההתחברות לחשבון, שמאפשרת למשתמש להתנתק.

4ed811a25b0cf816.png

6. כתיבת הודעות ב-Firestore

טוב לדעת שהמשתמשים בדרך, אבל צריך לתת לאורחים משהו אחר לעשות באפליקציה. מה יקרה אם הם יוכלו להשאיר הודעות בספר האורחים? הם יכולים לספר למה הם שמחים להגיע או עם מי הם מקווים לפגוש.

כדי לאחסן את הודעות הצ'אט שהמשתמשים כותבים באפליקציה, משתמשים ב-Firestore.

מודל נתונים

Firestore הוא מסד נתונים מסוג NoSQL, והנתונים שמאוחסנים במסד הנתונים מחולקים לאוספים, למסמכים, לשדות ולאוספים משניים. כל הודעה בצ'אט נשמרת כמסמך באוסף guestbook, שהוא אוסף ברמה העליונה.

7c20dc8424bb1d84.png

הוספת הודעות ל-Firestore

בסעיף הזה, תוסיפו את הפונקציונליות שמאפשרת למשתמשים לכתוב הודעות במסד הנתונים. קודם מוסיפים שדה טופס ולחצן שליחה, ואז מוסיפים את הקוד שמקשר את הרכיבים האלה למסד הנתונים.

  1. יוצרים קובץ חדש בשם guest_book.dart, מוסיפים ווידג'ט עם שמירת מצב GuestBook כדי ליצור את רכיבי ממשק המשתמש של שדה הודעה ולחצן שליחה:

lib/guest_book.dart

import 'dart:async';

import 'package:flutter/material.dart';

import 'src/widgets.dart';

class GuestBook extends StatefulWidget {
  const GuestBook({required this.addMessage, super.key});

  final FutureOr<void> Function(String message) addMessage;

  @override
  State<GuestBook> createState() => _GuestBookState();
}

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

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: Form(
        key: _formKey,
        child: Row(
          children: [
            Expanded(
              child: TextFormField(
                controller: _controller,
                decoration: const InputDecoration(
                  hintText: 'Leave a message',
                ),
                validator: (value) {
                  if (value == null || value.isEmpty) {
                    return 'Enter your message to continue';
                  }
                  return null;
                },
              ),
            ),
            const SizedBox(width: 8),
            StyledButton(
              onPressed: () async {
                if (_formKey.currentState!.validate()) {
                  await widget.addMessage(_controller.text);
                  _controller.clear();
                }
              },
              child: Row(
                children: const [
                  Icon(Icons.send),
                  SizedBox(width: 4),
                  Text('SEND'),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

יש כאן כמה נקודות עניין. קודם כול יוצרים מופע של טופס כדי שתוכלו לאמת שההודעה אכן מכילה תוכן ולהציג למשתמש הודעת שגיאה אם אין תוכן. כדי לאמת טופס, צריך לגשת למצב הטופס מאחורי הטופס באמצעות GlobalKey. מידע נוסף על מפתחות ועל אופן השימוש בהם זמין במאמר מתי כדאי להשתמש במפתחות.

כמו כן, חשוב לזכור את האופן שבו הווידג'טים נפתחים, יש Row עם TextFormField ו-StyledButton, שמכיל Row. שימו לב גם ש-TextFormField עטוף בווידג'ט Expanded, מה שמאלץ את TextFormField למלא את כל המרחב הנוסף בשורה. כדי להבין טוב יותר למה צריך לעשות זאת, אפשר לעיין במאמר הסבר על אילוצים.

עכשיו, אחרי שיצרתם ווידג&#39;ט שמאפשר למשתמש להזין טקסט כדי להוסיף אותו לספר האורחים, אתם צריכים להציג אותו במסך.

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

זה מספיק כדי להציג את הווידג&#39;ט, אבל לא מספיק כדי לבצע פעולות שימושיות. בקרוב תעדכנו את הקוד הזה כדי שיהיה פעיל.

תצוגה מקדימה של האפליקציה

מסך הבית של האפליקציה ב-Android עם שילוב בצ&#39;אט

מסך הבית של האפליקציה ב-iOS עם שילוב צ&#39;אט

מסך הבית של האפליקציה בדפדפן עם שילוב של צ&#39;אט

מסך הבית של האפליקציה ב-macOS עם שילוב של צ&#39;אט

כשמשתמש לוחץ על שליחה, מופעל קטע הקוד הבא. הפונקציה מוסיפה את התוכן של שדה הקלט של ההודעה לאוסף guestbook של מסד הנתונים. באופן ספציפי, השיטה addMessageToGuestBook מוסיפה את תוכן ההודעה למסמך חדש עם מזהה שנוצר באופן אוטומטי באוסף guestbook.

חשוב לשים לב ש-FirebaseAuth.instance.currentUser.uid הוא הפניה למזהה הייחודי שנוצר באופן אוטומטי, והאימות מעניק לכל המשתמשים המחוברים.

  • בקובץ lib/app_state.dart, מוסיפים את השיטה addMessageToGuestBook. אפשר לחבר את היכולת הזו לממשק המשתמש בשלב הבא.

lib/app_state.dart

import 'package:cloud_firestore/cloud_firestore.dart'; // new
import 'package:firebase_auth/firebase_auth.dart'
    hide EmailAuthProvider, PhoneAuthProvider;
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_ui_auth/firebase_ui_auth.dart';
import 'package:flutter/material.dart';

import 'firebase_options.dart';

class ApplicationState extends ChangeNotifier {

  // Current content of ApplicationState elided ...

  // Add from here...
  Future<DocumentReference> addMessageToGuestBook(String message) {
    if (!_loggedIn) {
      throw Exception('Must be logged in');
    }

    return FirebaseFirestore.instance
        .collection('guestbook')
        .add(<String, dynamic>{
      'text': message,
      'timestamp': DateTime.now().millisecondsSinceEpoch,
      'name': FirebaseAuth.instance.currentUser!.displayName,
      'userId': FirebaseAuth.instance.currentUser!.uid,
    });
  }
  // ...to here.
}

חיבור ממשק המשתמש למסד הנתונים

יש לכם ממשק משתמש שבו המשתמש יכול להזין את הטקסט שהוא רוצה להוסיף לספר האורחים, ויש לכם את הקוד להוספת הרשומה ל-Firestore. עכשיו כל מה שצריך לעשות הוא לחבר את השניים.

  • בקובץ lib/home_page.dart, מבצעים את השינוי הבא בווידג'ט HomePage:

lib/home_page.dart

import 'package:firebase_auth/firebase_auth.dart'
    hide EmailAuthProvider, PhoneAuthProvider;
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

import 'app_state.dart';
import 'guest_book.dart';                         // new
import 'src/authentication.dart';
import 'src/widgets.dart';

class HomePage extends StatelessWidget {
  const HomePage({Key? key}) : super(key: key);

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

החלפתם את שתי השורות שהוספתם בתחילת השלב הזה בהטמעה המלאה. שוב משתמשים ב-Consumer<ApplicationState> כדי להפוך את מצב האפליקציה לזמין לחלק של העץ שרוצים ליצור לו עיבוד. כך תוכלו להגיב למי שמזין הודעה בממשק המשתמש ולפרסם אותה במסד הנתונים. בקטע הבא נבדוק אם ההודעות שנוספו מתפרסמות במסד הנתונים.

בדיקה של שליחת הודעות

  1. אם צריך, נכנסים לאפליקציה.
  2. מזינים הודעה, למשל Hey there!, ולוחצים על שליחה.

הפעולה הזו תכתוב את ההודעה למסד הנתונים שלכם ב-Firestore. עם זאת, ההודעה לא מופיעה באפליקציה של Flutter בפועל, כי עדיין צריך ליישם אחזור של הנתונים, לעשות זאת בשלב הבא. עם זאת, במרכז הבקרה של מסד נתונים במסוף Firebase, אפשר לראות את ההודעה שנוספה באוסף guestbook. אם תשלחו עוד הודעות, תוסיפו עוד מסמכים לאוסף guestbook. לדוגמה, קטע הקוד הבא:

713870af0b3b63c.png

7. לקריאת ההודעות

ממש נחמד שאורחים יכולים לכתוב הודעות במסד הנתונים, אבל הם עדיין לא יכולים לראות אותן באפליקציה. הגיע הזמן לפתור את הבעיה הזו.

סנכרון הודעות

כדי להציג הודעות, צריך להוסיף מאזינים שמופעלים כשהנתונים משתנים, ואז ליצור רכיב בממשק המשתמש שמוצגות בו הודעות חדשות. מוסיפים קוד למצב האפליקציה שמאזינים להודעות חדשות שנוספו מהאפליקציה.

  1. יוצרים קובץ חדש guest_book_message.dart ומוסיפים את הכיתה הבאה כדי לחשוף תצוגה מובנית של הנתונים שאתם מאחסנים ב-Firestore.

lib/guest_book_message.dart

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

  final String name;
  final String message;
}
  1. מוסיפים את פעולות הייבוא הבאות לקובץ lib/app_state.dart:

lib/app_state.dart

import 'dart:async';                                     // new

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart'
    hide EmailAuthProvider, PhoneAuthProvider;
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_ui_auth/firebase_ui_auth.dart';
import 'package:flutter/material.dart';

import 'firebase_options.dart';
import 'guest_book_message.dart';                        // new
  1. בקטע של ApplicationState שבו מגדירים את המצב ואת ה-getters, מוסיפים את השורות הבאות:

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, משנים את ה-method 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, ואז מוסיפים collection 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
        ),
      ],
    ],
  ),
),

בדיקה של סנכרון ההודעות

Firestore מסנכרנת נתונים באופן אוטומטי ומיידי עם לקוחות שנרשמו למסד הנתונים.

בדיקת סנכרון ההודעות:

  1. באפליקציה, מחפשים את ההודעות שיצרתם קודם במסד הנתונים.
  2. לכתוב הודעות חדשות. הן מופיעות באופן מיידי.
  3. פותחים את סביבת העבודה בכמה חלונות או כרטיסיות. ההודעות מסונכרנות בזמן אמת בין החלונות והכרטיסיות.
  4. אופציונלי: בתפריט מסד נתונים במסוף Firebase, אפשר למחוק, לשנות או להוסיף הודעות חדשות באופן ידני. כל השינויים יופיעו בממשק המשתמש.

כל הכבוד! קראת מסמכי Firestore באפליקציה שלך!

תצוגה מקדימה של האפליקציה

מסך הבית של האפליקציה ב-Android עם שילוב בצ&#39;אט

מסך הבית של האפליקציה ב-iOS עם שילוב צ&#39;אט

מסך הבית של האפליקציה בדפדפן עם שילוב של צ&#39;אט

מסך הבית של האפליקציה ב-macOS עם שילוב של צ&#39;אט

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

מאחר שהשתמשתם במזהה האימות (Authentication 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. שלב בונוס: תרגול של מה שלמדתם

איך מתעדים את סטטוס אישור ההשתתפות של משתתפים

בשלב הזה, האפליקציה מאפשרת לאנשים להתכתב בצ&#39;אט רק אם הם מתעניינים באירוע. בנוסף, הדרך היחידה לדעת אם מישהו מגיע היא כאשר הוא אומר זאת בצ'אט.

בשלב הזה תקפידו לארגן את הרשימות כדי להודיע לאנשים כמה אנשים יגיעו. מוסיפים כמה יכולות למצב האפליקציה. הראשונה היא היכולת של משתמש מחובר להודיע אם הוא רוצה להשתתף. השלב השני הוא ספירה של מספר המשתתפים בפגישה.

  1. בקובץ lib/app_state.dart, מוסיפים את השורות הבאות לקטע של רכיבי הגישה ב-ApplicationState, כדי שקוד ממשק המשתמש יוכל לקיים אינטראקציה עם המצב הזה:

lib/app_state.dart

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

Attending _attending = Attending.unknown;
StreamSubscription<DocumentSnapshot>? _attendingSubscription;
Attending get attending => _attending;
set attending(Attending attending) {
  final userDoc = FirebaseFirestore.instance
      .collection('attendees')
      .doc(FirebaseAuth.instance.currentUser!.uid);
  if (attending == Attending.yes) {
    userDoc.set(<String, dynamic>{'attending': true});
  } else {
    userDoc.set(<String, dynamic>{'attending': false});
  }
}
  1. מעדכנים את השיטה init() של ApplicationState באופן הבא:

lib/app_state.dart

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

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

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

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

הקוד הזה מוסיף שאילתה עם תמיד מנויים כדי לקבוע את מספר המשתתפים, ושאילתה שנייה שפעילה רק כשהמשתמש מחובר לחשבון כדי לקבוע אם המשתמש משתתף.

  1. מוסיפים את המניין הבא בחלק העליון של הקובץ lib/app_state.dart.

lib/app_state.dart

enum Attending { yes, no, unknown }
  1. יוצרים קובץ חדש yes_no_selection.dart, מגדירים ווידג'ט חדש שפועל כמו לחצני רדיו:

lib/yes_no_selection.dart

import 'package:flutter/material.dart';

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

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

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

הוא מתחיל במצב לא מוגדר, בלי שהאפשרות כן או לא מסומנת. אחרי שהמשתמש מחליט אם להשתתף, האפשרות תוצג באופן מודגש באמצעות לחצן מלא והאפשרות השנייה תוסר בתצוגה שטוחה.

  1. מעדכנים את השיטה build() של HomePage כדי לנצל את YesNoSelection, מאפשרים למשתמש שמחובר לחשבון לציין אם הוא ישתתף באירוע ומציגים את מספר המשתתפים באירוע:

lib/home_page.dart

Consumer<ApplicationState>(
  builder: (context, appState, _) => Column(
    crossAxisAlignment: CrossAxisAlignment.start,
    children: [
      // Add from here...
      switch (appState.attendees) {
        1 => const Paragraph('1 person going'),
        >= 2 => Paragraph('${appState.attendees} people going'),
        _ => const Paragraph('No one going'),
      },
      // ...to here.
      if (appState.loggedIn) ...[
        // Add from here...
        YesNoSelection(
          state: appState.attending,
          onSelection: (attending) => appState.attending = attending,
        ),
        // ...to here.
        const Header('Discussion'),
        GuestBook(
          addMessage: (message) =>
              appState.addMessageToGuestBook(message),
          messages: appState.guestBookMessages,
        ),
      ],
    ],
  ),
),

הוספת כללים

כבר הגדרתם כמה כללים, ולכן הנתונים שתוסיפו באמצעות הלחצנים יידחו. עליך לעדכן את הכללים כדי לאפשר הוספה לאוסף attendees.

  1. באוסף attendees, מחפשים את מזהה האימות ששימש כשם המסמך ומוודאים שהערך של uid של השולח זהה למסמך שהוא כותב:
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.

תצוגה מקדימה של האפליקציה

במסך הבית של האפליקציה ב-Android

מסך הבית של האפליקציה ב-iOS

מסך הבית של האפליקציה באינטרנט

במסך הבית של האפליקציה ב-macOS

10. כל הכבוד!

השתמשתם ב-Firebase כדי ליצור אפליקציית אינטרנט אינטראקטיבית בזמן אמת!

מידע נוסף