הכירו את Firebase for Flutter

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

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

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

מעבד קוד זה מניח שאתה מכיר את Flutter, והתקנת את Flutter SDK ועורך .

מה אתה תיצור

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

מה אתה צריך

אתה יכול להפעיל מעבד קוד זה באמצעות כל אחד מהמכשירים הבאים:

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

בנוסף לאמור לעיל, תצטרך גם:

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

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

בואו נתחיל בהורדת הגרסה הראשונית של הפרויקט שלנו מ- GitHub.

שכבו את מאגר GitHub משורת הפקודה:

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

לחלופין, אם מותקן אצלך כלי cli של GitHub :

gh repo clone flutter/codelabs flutter-codelabs

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

מבנה הספריות תחת flutter-codelabs/firebase-get-to-know-flutter הוא סדרה של צילומי מצב של היכן אתה צריך להיות בסוף כל שלב בעל שם. זהו שלב 2, אז איתור הקבצים התואמים הוא קל כמו:

cd flutter-codelabs/firebase-get-to-know-flutter/step_02

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

ייבא את אפליקציית המתנע

פתח או ייבא את flutter-codelabs/firebase-get-to-know-flutter/step_02 אל ה-IDE המועדף עליך. ספרייה זו מכילה את קוד ההתחלה של מעבדת הקוד שמורכבת מאפליקציית Flutter meetup שעדיין לא פעילה.

אתר את הקבצים לעבוד עליהם

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

אתר את הקבצים הבאים בפרויקט:

  • lib/main.dart : קובץ זה מכיל את נקודת הכניסה הראשית ואת ווידג'ט היישום.
  • lib/src/widgets.dart : קובץ זה מכיל קומץ ווידג'טים שיעזרו לתקן את הסגנון של האפליקציה. אלה משמשים להרכבת המסך של אפליקציית המתנע.
  • lib/src/authentication.dart : קובץ זה מכיל הטמעה חלקית של FirebaseUI Auth עם קבוצה של ווידג'טים ליצירת חוויית כניסה עבור אימות מבוסס דוא"ל של Firebase. הווידג'טים האלה עבור זרימת ההסמכה עדיין לא נמצאים בשימוש באפליקציית המתנע, אבל תחבר אותם בקרוב.

אתה תוסיף קבצים נוספים לפי הצורך כדי לבנות את שאר היישום.

סקירת הקובץ lib/main.dart

אפליקציה זו מנצלת את חבילת google_fonts כדי לאפשר לנו להפוך את Roboto לגופן ברירת המחדל בכל האפליקציה. תרגיל עבור הקורא החדור הוא לחקור את fonts.google.com ולהשתמש בגופנים שאתה מגלה שם בחלקים שונים של האפליקציה.

אתה משתמש בווידג'טים המסייעים מ- lib/src/widgets.dart בצורה של Header , Paragraph ו- IconAndDetail . ווידג'טים אלה מפחיתים את העומס בפריסת העמוד המתוארת בדף HomePage על ידי ביטול קוד משוכפל. יש לכך יתרון נוסף בכך שהוא מאפשר מראה ותחושה עקביים.

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

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

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

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

צור פרויקט Firebase

  1. היכנס ל- Firebase .
  2. במסוף Firebase, לחץ על הוסף פרויקט (או צור פרויקט ), ושם לפרויקט Firebase שלך ​​Firebase-Flutter-Codelab .

4395e4e67c08043a.png

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

b7138cde5f2c7b61.png

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

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

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

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

אפשר כניסה בדוא"ל עבור אימות Firebase

כדי לאפשר למשתמשים להיכנס לאפליקציית האינטרנט, תשתמש בשיטת הכניסה לדוא"ל/סיסמה עבור מעבדת קוד זה:

  1. במסוף Firebase, הרחב את תפריט Build בחלונית השמאלית.
  2. לחץ על אימות ולאחר מכן לחץ על הלחצן התחל , ולאחר מכן על הכרטיסייה שיטת כניסה (או לחץ כאן כדי לעבור ישירות לכרטיסייה שיטת כניסה ).
  3. לחץ על דואר אלקטרוני/סיסמה ברשימת ספקי הכניסה , הגדר את מתג הפעל למצב מופעל ולאחר מכן לחץ על שמור . 58e3e3e23c2f16a4.png

הפעל את Cloud Firestore

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

הפעל את Cloud Firestore:

  1. בקטע Build של מסוף Firebase, לחץ על Cloud Firestore .
  2. לחץ על צור מסד נתונים . 99e8429832d23fa3.png
  1. בחר באפשרות התחל במצב בדיקה . קרא את כתב הוויתור על כללי האבטחה. מצב בדיקה מבטיח שאתה יכול לכתוב בחופשיות למסד הנתונים במהלך הפיתוח. לחץ על הבא . 6be00e26c72ea032.png
  1. בחר את המיקום עבור מסד הנתונים שלך (אתה יכול פשוט להשתמש בברירת המחדל). שים לב שלא ניתן לשנות את המיקום הזה מאוחר יותר. 278656eefcfb0216.png
  2. לחץ על הפעל .

4. תצורת Firebase

על מנת להשתמש ב-Firebase עם Flutter, עליך לבצע תהליך כדי להגדיר את פרויקט Flutter כך שישתמש בספריות FlutterFire בצורה נכונה:

  • הוסף את התלות של FlutterFire לפרויקט שלך
  • רשום את הפלטפורמה הרצויה בפרויקט Firebase
  • הורד את קובץ התצורה הספציפי לפלטפורמה, והוסף אותו לקוד.

בספרייה ברמה העליונה של אפליקציית Flutter שלך, יש תיקיות משנה הנקראות android , ios , macos ו- web . ספריות אלה מכילות את קובצי התצורה הספציפיים לפלטפורמה עבור iOS ו- Android, בהתאמה.

הגדר תלות

עליך להוסיף את ספריות FlutterFire עבור שני מוצרי Firebase שבהם אתה משתמש באפליקציה זו - Firebase Auth ו-Cloud Firestore. הפעל את שלוש הפקודות הבאות כדי להוסיף את התלות.

$ flutter pub add firebase_core 
Resolving dependencies...
+ firebase_core 1.10.5
+ firebase_core_platform_interface 4.2.2
+ firebase_core_web 1.5.2
+ flutter_web_plugins 0.0.0 from sdk flutter
+ js 0.6.3
  test_api 0.4.3 (0.4.8 available)
Changed 5 dependencies!

firebase_core הוא הקוד הנפוץ הנדרש עבור כל התוספים של Firebase Flutter.

$ flutter pub add firebase_auth
Resolving dependencies...
+ firebase_auth 3.3.3
+ firebase_auth_platform_interface 6.1.8
+ firebase_auth_web 3.3.4
+ intl 0.17.0
  test_api 0.4.3 (0.4.8 available)
Changed 4 dependencies!

ה- firebase_auth מאפשר אינטגרציה עם יכולת האימות של Firebase.

$ flutter pub add cloud_firestore
Resolving dependencies...
+ cloud_firestore 3.1.4
+ cloud_firestore_platform_interface 5.4.9
+ cloud_firestore_web 2.6.4
  test_api 0.4.3 (0.4.8 available)
Changed 3 dependencies!

ה- cloud_firestore מאפשר גישה לאחסון נתונים של Cloud Firestore.

$ flutter pub add provider
Resolving dependencies...
+ nested 1.0.0
+ provider 6.0.1
  test_api 0.4.3 (0.4.8 available)
Changed 2 dependencies!

בזמן שהוספת את החבילות הנדרשות, עליך גם להגדיר את הפרויקטים של iOS, Android, macOS ו-Web runner כך שישתמשו כראוי ב-Firebase. אתה גם משתמש בחבילת provider שתאפשר הפרדה בין ההיגיון העסקי להיגיון התצוגה.

התקנת flutterfire

ה-CLI של FlutterFire תלוי ב-CLI הבסיסי של Firebase. אם עדיין לא עשית זאת, ודא ש-Firebase CLI מותקן במחשב שלך.

לאחר מכן, התקן את FlutterFire CLI על ידי הפעלת הפקודה הבאה:

$ dart pub global activate flutterfire_cli

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

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

ה-CLI שואב מידע מפרויקט Firebase שלך ​​ומאפליקציות פרויקט נבחרות כדי ליצור את כל התצורה עבור פלטפורמה ספציפית.

בשורש היישום שלך, הפעל את הפקודה configure:

$ flutterfire configure

פקודת התצורה תדריך אותך במספר תהליכים:

  1. בחירת פרויקט Firebase (בהתבסס על קובץ ה-.firebaserc או ממסוף Firebase).
  2. בקש מהי הפלטפורמות (למשל אנדרואיד, 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>

ראה זכויות ו-App Sandbox לפרטים נוספים.

5. הוסף כניסה למשתמש (RSVP)

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

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

לוגיקה עסקית עם ספק

אתה הולך להשתמש בחבילת provider כדי להפוך אובייקט מצב יישום מרכזי לזמין בכל עץ הווידג'טים של Flutter של היישום. כדי להתחיל, שנה את הייבוא ​​בחלק העליון של lib/main.dart :

lib/main.dart

import 'package:firebase_auth/firebase_auth.dart'; // new
import 'package:firebase_core/firebase_core.dart'; // new
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:provider/provider.dart';           // new

import 'firebase_options.dart';                    // new
import 'src/authentication.dart';                  // new
import 'src/widgets.dart';

קווי import ​​מציגים את Firebase Core ו-Auth, מושכים את חבילת provider שבה אתה משתמש כדי להפוך את אובייקט מצב היישום לזמין דרך עץ הווידג'טים, וכוללים את ווידג'טים לאימות מ- lib/src .

לאובייקט מצב יישום זה, ApplicationState , יש שתי תחומי אחריות עיקריים לשלב זה, אך יקבל תחומי אחריות נוספים ככל שתוסיף עוד יכולות ליישום בשלבים מאוחרים יותר. האחריות הראשונה היא לאתחל את ספריית Firebase עם קריאה ל- Firebase.initializeApp() , ולאחר מכן יש את הטיפול בזרימת ההרשאה. הוסף את המחלקה הבאה לסוף lib/main.dart :

lib/main.dart

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

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

    FirebaseAuth.instance.userChanges().listen((user) {
      if (user != null) {
        _loginState = ApplicationLoginState.loggedIn;
      } else {
        _loginState = ApplicationLoginState.loggedOut;
      }
      notifyListeners();
    });
  }

  ApplicationLoginState _loginState = ApplicationLoginState.loggedOut;
  ApplicationLoginState get loginState => _loginState;

  String? _email;
  String? get email => _email;

  void startLoginFlow() {
    _loginState = ApplicationLoginState.emailAddress;
    notifyListeners();
  }

  Future<void> verifyEmail(
    String email,
    void Function(FirebaseAuthException e) errorCallback,
  ) async {
    try {
      var methods =
          await FirebaseAuth.instance.fetchSignInMethodsForEmail(email);
      if (methods.contains('password')) {
        _loginState = ApplicationLoginState.password;
      } else {
        _loginState = ApplicationLoginState.register;
      }
      _email = email;
      notifyListeners();
    } on FirebaseAuthException catch (e) {
      errorCallback(e);
    }
  }

  Future<void> signInWithEmailAndPassword(
    String email,
    String password,
    void Function(FirebaseAuthException e) errorCallback,
  ) async {
    try {
      await FirebaseAuth.instance.signInWithEmailAndPassword(
        email: email,
        password: password,
      );
    } on FirebaseAuthException catch (e) {
      errorCallback(e);
    }
  }

  void cancelRegistration() {
    _loginState = ApplicationLoginState.emailAddress;
    notifyListeners();
  }

  Future<void> registerAccount(
      String email,
      String displayName,
      String password,
      void Function(FirebaseAuthException e) errorCallback) async {
    try {
      var credential = await FirebaseAuth.instance
          .createUserWithEmailAndPassword(email: email, password: password);
      await credential.user!.updateDisplayName(displayName);
    } on FirebaseAuthException catch (e) {
      errorCallback(e);
    }
  }

  void signOut() {
    FirebaseAuth.instance.signOut();
  }
}

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

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

שילוב זרימת האימות

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

lib/main.dart

void main() {
  // Modify from here
  runApp(
    ChangeNotifierProvider(
      create: (context) => ApplicationState(),
      builder: (context, _) => App(),
    ),
  );
  // to here.
}

השינוי בפונקציה main הופך את חבילת הספק לאחראית לאינסטציה של אובייקט מצב היישום באמצעות הווידג'ט ChangeNotifierProvider . אתה משתמש במחלקת הספק הספציפית הזו מכיוון שאובייקט מצב היישום מרחיב את ChangeNotifier וזה מאפשר לחבילת provider לדעת מתי להציג מחדש ווידג'טים תלויים. לבסוף, שלב את מצב היישום עם Authentication על ידי עדכון שיטת build של HomePage :

lib/main.dart

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Firebase Meetup'),
      ),
      body: ListView(
        children: <Widget>[
          Image.asset('assets/codelab.png'),
          const SizedBox(height: 8),
          const IconAndDetail(Icons.calendar_today, 'October 30'),
          const IconAndDetail(Icons.location_city, 'San Francisco'),
          // Add from here
          Consumer<ApplicationState>(
            builder: (context, appState, _) => Authentication(
              email: appState.email,
              loginState: appState.loginState,
              startLoginFlow: appState.startLoginFlow,
              verifyEmail: appState.verifyEmail,
              signInWithEmailAndPassword: appState.signInWithEmailAndPassword,
              cancelRegistration: appState.cancelRegistration,
              registerAccount: appState.registerAccount,
              signOut: appState.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!',
          ),
        ],
      ),
    );
  }
}

אתה מייצר את ווידג'ט Authentication ועוטף אותו בווידג'ט Consumer . הווידג'ט לצרכן בדרך הרגילה שבה ניתן להשתמש בחבילת provider כדי לבנות מחדש חלק מהעץ כאשר מצב היישום משתנה. ווידג'ט Authentication הוא ממשק המשתמש של האימות שתבדוק כעת.

בדיקת זרימת האימות

cdf2d25e436bd48d.png

הנה ההתחלה של זרימת האימות, שבה המשתמש יכול להקיש על כפתור ה-RSVP, כדי להפעיל את טופס הדוא"ל.

2a2cd6d69d172369.png

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

e5e65065dba36b54.png

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

fbb3ea35fb4f67a.png

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

4ed811a25b0cf816.png

ועם זה, יישמת זרימת אימות. מזל טוב!

6. כתוב הודעות ל-Cloud Firestore

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

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

מודל נתונים

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

7c20dc8424bb1d84.png

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

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

ראשית, הוסף ייבוא ​​עבור חבילת cloud_firestore ו- dart:async .

lib/main.dart

import 'dart:async';                                    // new

import 'package:cloud_firestore/cloud_firestore.dart';  // new
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:provider/provider.dart';

import 'firebase_options.dart';
import 'src/authentication.dart';
import 'src/widgets.dart';

כדי לבנות את רכיבי ממשק המשתמש של שדה הודעה וכפתור שלח, הוסף ווידג'ט סטטיסטי חדש של GuestBook בתחתית lib/main.dart .

lib/main.dart

class GuestBook extends StatefulWidget {
  const GuestBook({required this.addMessage});
  final FutureOr<void> Function(String message) addMessage;

  @override
  _GuestBookState 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 . למידע נוסף על Keys, וכיצד להשתמש בהם, אנא עיין בפרק Flutter Widgets 101 "מתי להשתמש במפתחות" .

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

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

const Header("What we'll be doing"),
const Paragraph(
  'Join us for a day full of Firebase Workshops and Pizza!',
),
// Add the following two lines.
const Header('Discussion'),
GuestBook(addMessage: (message) => print(message)),

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

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

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

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

בצע שינוי נוסף בקובץ lib/main.dart . הוסף את שיטת addMessageToGuestBook . בשלב הבא תחבר את ממשק המשתמש והיכולת הזו ביחד.

lib/main.dart

class ApplicationState extends ChangeNotifier {

  // Current content of ApplicationState elided ...

  // Add from here
  Future<DocumentReference> addMessageToGuestBook(String message) {
    if (_loginState != ApplicationLoginState.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
}

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

יש לך ממשק משתמש שבו המשתמש יכול להזין את הטקסט שהוא רוצה להוסיף לספר האורחים, ויש לך את הקוד להוסיף את הערך ל-Cloud Firestore. עכשיו כל מה שאתה צריך לעשות הוא לחבר את השניים יחד. ב- lib/main.dart את השינוי הבא בווידג'ט HomePage .

lib/main.dart

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Firebase Meetup'),
      ),
      body: ListView(
        children: <Widget>[
          Image.asset('assets/codelab.png'),
          const SizedBox(height: 8),
          const IconAndDetail(Icons.calendar_today, 'October 30'),
          const IconAndDetail(Icons.location_city, 'San Francisco'),
          Consumer<ApplicationState>(
            builder: (context, appState, _) => Authentication(
              email: appState.email,
              loginState: appState.loginState,
              startLoginFlow: appState.startLoginFlow,
              verifyEmail: appState.verifyEmail,
              signInWithEmailAndPassword: appState.signInWithEmailAndPassword,
              cancelRegistration: appState.cancelRegistration,
              registerAccount: appState.registerAccount,
              signOut: appState.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.loginState == ApplicationLoginState.loggedIn) ...[
                  const Header('Discussion'),
                  GuestBook(
                    addMessage: (message) =>
                        appState.addMessageToGuestBook(message),
                  ),
                ],
              ],
            ),
          ),
          // To here.
        ],
      ),
    );
  }
}

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

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

  1. ודא שאתה מחובר לאפליקציה.
  2. הזן הודעה כגון "היי!", ולאחר מכן לחץ על שלח .

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

אבל אתה יכול לראות את ההודעה החדשה שנוספה במסוף Firebase.

במסוף Firebase, בלוח המחוונים של מסד הנתונים , אתה אמור לראות את אוסף guestbook עם ההודעה החדשה שהוספת. אם תמשיך לשלוח הודעות, אוסף ספר האורחים שלך יכיל מסמכים רבים, כמו זה:

קונסולת Firebase

713870af0b3b63c.png

7. קרא הודעות

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

סנכרן הודעות

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

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

lib/main.dart

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

בחלק של ApplicationState שבו אתה מגדיר מצב ו-getters, הוסף את השורות החדשות הבאות:

lib/main.dart

  ApplicationLoginState _loginState = ApplicationLoginState.loggedOut;
  ApplicationLoginState get loginState => _loginState;

  String? _email;
  String? get email => _email;

  // Add from here
  StreamSubscription<QuerySnapshot>? _guestBookSubscription;
  List<GuestBookMessage> _guestBookMessages = [];
  List<GuestBookMessage> get guestBookMessages => _guestBookMessages;
  // to here.

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

lib/main.dart

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

    FirebaseAuth.instance.userChanges().listen((user) {
      if (user != null) {
        _loginState = ApplicationLoginState.loggedIn;
        // 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 {
        _loginState = ApplicationLoginState.loggedOut;
        // Add from here
        _guestBookMessages = [];
        _guestBookSubscription?.cancel();
        // to here.
      }
      notifyListeners();
    });
  }

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

למידע נוסף, עיין בתיעוד של Cloud Firestore .

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

lib/main.dart

class GuestBook extends StatefulWidget {
  // Modify the following line
  const GuestBook({required this.addMessage, required this.messages});
  final FutureOr<void> Function(String message) addMessage;
  final List<GuestBookMessage> messages; // new

  @override
  _GuestBookState createState() => _GuestBookState();
}

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

lib/main.dart

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

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

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

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

lib/main.dart

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

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

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

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

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

סקירת אפליקציה

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

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

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

אתה יכול לכתוב כללי אבטחה עבור Cloud Firestore במסוף Firebase:

  1. בקטע פיתוח של מסוף Firebase, לחץ על מסד נתונים ולאחר מכן בחר בכרטיסייה כללים (או לחץ כאן כדי לעבור ישירות לכרטיסייה כללים ).
  2. אתה אמור לראות את כללי האבטחה הבאים כברירת מחדל, יחד עם אזהרה על כך שהכללים פומביים.

7767a2d2e64e7275.png

זיהוי אוספים

ראשית, זהה את האוספים אליהם האפליקציה כותבת נתונים.

ב- match /databases/{database}/documents , זהה את האוסף שברצונך לאבטח:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /guestbook/{entry} {
     // You'll add rules here in the next step.
  }
}

הוסף כללי אבטחה

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

הוסף את כללי הקריאה והכתיבה למערכת הכללים שלך כפי שמוצג להלן:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /guestbook/{entry} {
      allow read: if request.auth.uid != null;
      allow write:
        if request.auth.uid == request.resource.data.userId;
    }
  }
}

כעת, עבור ספר האורחים, רק משתמשים מחוברים יכולים לקרוא הודעות (כל הודעה!), אבל רק מחבר ההודעה יכול לערוך הודעה.

הוסף כללי אימות

הוסף אימות נתונים כדי לוודא שכל השדות הצפויים קיימים במסמך:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /guestbook/{entry} {
      allow read: if request.auth.uid != null;
      allow write:
      if request.auth.uid == request.resource.data.userId
          && "name" in request.resource.data
          && "text" in request.resource.data
          && "timestamp" in request.resource.data;
    }
  }
}

9. שלב בונוס: תרגל את מה שלמדת

הקלט את סטטוס ה-RSVP של משתתף

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

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

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

lib/main.dart

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

Attending _attending = Attending.unknown;
StreamSubscription<DocumentSnapshot>? _attendingSubscription;
Attending get attending => _attending;
set attending(Attending attending) {
  final userDoc = FirebaseFirestore.instance
      .collection('attendees')
      .doc(FirebaseAuth.instance.currentUser!.uid);
  if (attending == Attending.yes) {
    userDoc.set(<String, dynamic>{'attending': true});
  } else {
    userDoc.set(<String, dynamic>{'attending': false});
  }
}

עדכן את שיטת init של ApplicationState באופן הבא:

lib/main.dart

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

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

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

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

lib/main.dart

enum Attending { yes, no, unknown }

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

lib/main.dart

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

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

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

lib/main.dart

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

הוסף כללים

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

עבור אוסף attendees , מכיוון שהשתמשת ב-UID של האימות כשם המסמך, אתה יכול לתפוס אותו ולוודא שה- uid של המגיש זהה למסמך שהם כותבים. אתה תאפשר לכולם לקרוא את רשימת המשתתפים (מכיוון שאין שם נתונים פרטיים), אבל רק היוצר אמור להיות מסוגל לעדכן אותה.

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

הוסף כללי אימות

הוסף אימות נתונים כדי לוודא שכל השדות הצפויים קיימים במסמך:

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

    }
  }
}

(אופציונלי) כעת תוכל לצפות בתוצאות של לחיצה על הכפתורים. עבור אל לוח המחוונים של Cloud Firestore שלך ​​במסוף Firebase.

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

10. מזל טוב!

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

מה שכיסינו

  • אימות Firebase
  • Cloud Firestore
  • כללי אבטחה של Firebase

הצעדים הבאים

  • רוצה ללמוד עוד על מוצרים אחרים של Firebase? אולי אתה רוצה לאחסן קבצי תמונה שמשתמשים מעלים? או לשלוח הודעות למשתמשים שלך? עיין בתיעוד של Firebase . רוצה ללמוד עוד על תוספי Flutter עבור Firebase? בדוק את FlutterFire למידע נוסף.
  • רוצה ללמוד עוד על Cloud Firestore? אולי אתה רוצה ללמוד על אוספי משנה ועסקאות? עבור אל מעבדת הקוד של Cloud Firestore עבור מעבדת קוד שמעמיקה יותר ב-Cloud Firestore. או בדוק את סדרת YouTube הזו כדי להכיר את Cloud Firestore !

למד עוד

איך זה הלך?

נשמח למשוב שלך! נא למלא טופס קצר (מאוד) כאן .