היכרות עם 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. הקוד של סדנת הקוד הזו נמצא בספרייה 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: הקובץ הזה מכיל כמה ווידג'טים שיעזרו לכם לסטנדרטיזציה של הסגנון של האפליקציה. הם יוצרים את המסך של אפליקציית ה-starter.
    • 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. לוחצים על Authentication > Get Started > Sign-in method > Email/Password > Enable > Save.

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 אפשר ליצור אפליקציות ב-sandbox מלא. האפליקציה הזו משתלבת עם הרשת כדי לתקשר עם שרתי Firebase, לכן צריך להגדיר לאפליקציה הרשאות של לקוח רשת.

macos/Runner/DebugProfile.entitlements

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>com.apple.security.app-sandbox</key>
	<true/>
	<key>com.apple.security.cs.allow-jit</key>
	<true/>
	<key>com.apple.security.network.server</key>
	<true/>
  <!-- Add the following two lines -->
	<key>com.apple.security.network.client</key>
	<true/>
</dict>
</plist>

macos/Runner/Release.entitlements

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>com.apple.security.app-sandbox</key>
	<true/>
  <!-- Add the following two lines -->
	<key>com.apple.security.network.client</key>
	<true/>
</dict>
</plist>

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

5. הוספת פונקציונליות של אישור השתתפות

עכשיו, אחרי שהוספתם את Firebase לאפליקציה, תוכלו ליצור לחצן RSVP שמאפשר לאנשים להירשם באמצעות אימות. יש חבילות FirebaseUI Auth מוכנות מראש ל-Android, ל-iOS ולאינטרנט, אבל צריך לפתח את היכולת הזו ב-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, מוסיפים ווידג'ט עם מצב (stateful) מסוג 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)),

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

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

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

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

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

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

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

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

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

הפעולה הזו כותבת את ההודעה במסד הנתונים של Firestore. עם זאת, ההודעה לא תופיע באפליקציית Flutter בפועל כי עדיין צריך להטמיע את אחזור הנתונים. בשלב הבא נסביר איך עושים את זה. עם זאת, אפשר לראות את ההודעה שנוספה באוסף guestbook במרכז הבקרה Database במסוף Firebase. אם תשלחו עוד הודעות, תוסיפו עוד מסמכים לאוסף 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, משנים את השיטה 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. אופציונלי: בתפריט Database במסוף Firebase, מוחקים, משנים או מוסיפים הודעות חדשות באופן ידני. כל השינויים מופיעים בממשק המשתמש.

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

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

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

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

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

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

8. הגדרת כללי אבטחה בסיסיים

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

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

מגדירים כללי אבטחה בסיסיים:

  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) כשדה בכל מסמך ביומן האורחים, אתם יכולים לקבל את מזהה האימות ולאמת שכל מי שמנסה לכתוב במסמך כולל מזהה אימות תואם.

  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. שלב בונוס: תרגול של מה שלמדתם

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

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

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

  1. בקובץ lib/app_state.dart, מוסיפים את השורות הבאות לקטע ה-accessors של 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 כדי ליצור אפליקציית אינטרנט אינטראקטיבית בזמן אמת!

מידע נוסף