הכירו את Firebase for Flutter

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

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

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

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

מה תלמד

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

מה אתה צריך

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

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

אתה צריך גם את הדברים הבאים:

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

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

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

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

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

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

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

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

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

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

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

  • אתר את הקבצים הבאים:
    • lib/main.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:

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

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

צור פרויקט Firebase

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

4395e4e67c08043a.png

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

b7138cde5f2c7b61.png

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

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

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

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

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

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

58e3e3e23c2f16a4.png

הפעל את Firestore

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

הפעל את Firestore:

  • בתפריט Build , לחץ על Cloud Firestore > צור מסד נתונים .

99e8429832d23fa3.png

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

6be00e26c72ea032.png

  1. לחץ על הבא ולאחר מכן בחר את המיקום עבור מסד הנתונים שלך. אתה יכול להשתמש בברירת המחדל. לא תוכל לשנות את המיקום מאוחר יותר.

278656eefcfb0216.png

  1. לחץ על הפעל .

4. הגדר את Firebase

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

  1. הוסף את התלות של FlutterFire לפרויקט שלך.
  2. רשום את הפלטפורמה הרצויה בפרויקט Firebase.
  3. הורד את קובץ התצורה הספציפי לפלטפורמה ולאחר מכן הוסף אותו לקוד.

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

הגדר תלות

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

  • משורת הפקודה, הוסף את התלות הבאות:
$ flutter pub add firebase_core

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

$ flutter pub add firebase_auth

חבילת firebase_auth מאפשרת אינטגרציה עם Authentication.

$ flutter pub add cloud_firestore

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

$ flutter pub add provider

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

$ flutter pub add firebase_ui_auth

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

התקן את FlutterFire CLI

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

  1. אם עדיין לא עשית זאת, התקן את Firebase CLI במחשב שלך.
  2. התקן את 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>

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

5. הוסף פונקציונליות RSVP

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

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

הוסף הגיון עסקי עם חבילת Provider

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

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

lib/main.dart

import 'dart:async';                                     // new
import 'package:firebase_auth/firebase_auth.dart'        // new
    hide EmailAuthProvider, PhoneAuthProvider;           // new
import 'package:firebase_core/firebase_core.dart';       // new
import 'package:firebase_ui_auth/firebase_ui_auth.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 שהופכת את אובייקט מצב האפליקציה לזמין בכל עץ הווידג'טים, וכוללות את ווידג'טים לאימות מחבילת firebase_ui_auth .

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

  1. הוסף את המחלקה הבאה לסוף הקובץ lib/main.dart :

lib/main.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();
    });
  }
}

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

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

  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 מספק לך:

lib/main.dart

class App extends StatelessWidget {
  const App({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      //Start adding here
      initialRoute: '/home',
      routes: {
        '/home': (context) {
          return const HomePage();
        },
        '/sign-in': ((context) {
          return SignInScreen(
            actions: [
              ForgotPasswordAction(((context, email) {
                Navigator.of(context)
                    .pushNamed('/forgot-password', arguments: {'email': email});
              })),
              AuthStateChangeAction(((context, state) {
                if (state is SignedIn || state is UserCreated) {
                  var user = (state is SignedIn)
                      ? state.user
                      : (state as UserCreated).credential.user;
                  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);
                  }
                  Navigator.of(context).pushReplacementNamed('/home');
                }
              })),
            ],
          );
        }),
        '/forgot-password': ((context) {
          final arguments = ModalRoute.of(context)?.settings.arguments
              as Map<String, dynamic>?;

          return ForgotPasswordScreen(
            email: arguments?['email'] as String,
            headerMaxExtent: 200,
          );
        }),
        '/profile': ((context) {
          return ProfileScreen(
            providers: [],
            actions: [
              SignedOutAction(
                ((context) {
                  Navigator.of(context).pushReplacementNamed('/home');
                }),
              ),
            ],
          );
        })
      },
      // end adding here
      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,
      ),
    );
  }
}

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

  1. בשיטת הבנייה של מחלקת HomePage , שלב את מצב האפליקציה עם הווידג'ט AuthFunc :

lib/main.dart

class HomePage extends StatelessWidget {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Consumer<ApplicationState>(
        builder: (context, appState, child) => 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. באפליקציה, הקש על כפתור RSVP כדי להפעיל את SignInScreen .

2a2cd6d69d172369.png

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

e5e65065dba36b54.png

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

4ed811a25b0cf816.png

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

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

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

מודל נתונים

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

7c20dc8424bb1d84.png

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

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

  1. בקובץ lib/main.dart , הוסף ייבוא ​​עבור 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';
  1. בסוף הקובץ lib/main.dart , הוסף ווידג'ט מצבי של GuestBook האורחים כדי לבנות את רכיבי ממשק המשתמש של שדה הודעה וכפתור שלח:

lib/main.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 עטוף ב-widget 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)),

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

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

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

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

  • בקובץ 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 (!_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/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, _) => 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. בקובץ lib/main.dart לפני ווידג'ט GuestBook האורחים, הוסף את המחלקה הבאה כדי לחשוף תצוגה מובנית של הנתונים שאתה מאחסן ב-Firestore.

lib/main.dart

class GuestBookMessage {
  GuestBookMessage({required this.name, required this.message});
  final String name;
  final String message;
}
  1. בחלק של ApplicationState שבו אתה מגדיר מצב ו-getters, הוסף את השורות הבאות:

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

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

lib/main.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/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.
    );
  }
}

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

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

lib/main.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 באפליקציה שלך!

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

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

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

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

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

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

7767a2d2e64e7275.png

  1. זהה את האוספים אליהם האפליקציה כותבת נתונים:

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

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

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

  1. הוסף את כללי הקריאה והכתיבה לקבוצת הכללים שלך:
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /guestbook/{entry} {
      allow read: if request.auth.uid != null;
      allow write:
        if request.auth.uid == request.resource.data.userId;
    }
  }
}

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

  1. הוסף אימות נתונים כדי לוודא שכל השדות הצפויים קיימים במסמך:
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /guestbook/{entry} {
      allow read: if request.auth.uid != null;
      allow write:
      if request.auth.uid == request.resource.data.userId
          && "name" in request.resource.data
          && "text" in request.resource.data
          && "timestamp" in request.resource.data;
    }
  }
}

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

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

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

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

  1. בקובץ lib/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});
  }
}
  1. עדכן את שיטת init() של ApplicationState באופן הבא:

lib/main.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) {
        _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();
    });
  }

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

  1. הוסף את הספירה הבאה לאחר הצהרת GuestBookMessage :

lib/main.dart

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

lib/main.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: [
              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'),
              ),
            ],
          ),
        );
    }
  }
}

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

  1. עדכן את שיטת ה- build() של 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.loggedIn) ...[
        // Add from here...
        YesNoSelection(
          state: appState.attending,
          onSelection: (attending) => appState.attending = attending,
        ),
        // ...to here.
        const Header('Discussion'),
        GuestBook(
          addMessage: (message) =>
              appState.addMessageToGuestBook(message),
          messages: appState.guestBookMessages,
        ),
      ],
    ],
  ),
),

הוסף כללים

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

  1. באוסף attendees , קח את ה-UID של האימות שבו השתמשת כשם המסמך וודא שה- uid של המגיש זהה למסמך שהם כותבים:
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    // ... //
    match /attendees/{userId} {
      allow read: if true;
      allow write: if request.auth.uid == userId;
    }
  }
}

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

  1. הוסף אימות נתונים כדי לוודא שכל השדות הצפויים קיימים במסמך:
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    // ... //
    match /attendees/{userId} {
      allow read: if true;
      allow write: if request.auth.uid == userId
          && "attending" in request.resource.data;

    }
  }
}
  1. אופציונלי: באפליקציה, לחץ על כפתורים כדי לראות את התוצאות בלוח המחוונים של Firestore במסוף Firebase.

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

10. מזל טוב!

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

למד עוד