1. לפני שמתחילים
ב-Codelab הזה תלמדו כמה מהעקרונות הבסיסיים של Firebase כדי ליצור אפליקציות לנייד ב-Flutter ל-Android ול-iOS.
דרישות מוקדמות
- היכרות עם Flutter
- ערכת Flutter SDK
- עורך טקסט לפי בחירתכם
מה תלמדו
- איך ליצור אפליקציה לניהול אישורי השתתפות באירועים ולניהול ספר אורחים בצ'אט ב-Android, ב-iOS, באינטרנט וב-macOS באמצעות Flutter.
- איך מאמתים משתמשים באמצעות אימות ב-Firebase ומסנכרנים נתונים עם Firestore.
|
|
|
|
מה צריך
אחד מהמכשירים הבאים:
- מכשיר פיזי של Android או מכשיר iOS שמחובר למחשב ומוגדר למצב פיתוח.
- סימולטור iOS (נדרשים כלי Xcode).
- אמולטור Android (נדרשת הגדרה ב-Android Studio).
צריך גם את הפרטים הבאים:
- דפדפן לבחירתכם, כמו Google Chrome.
- סביבת פיתוח משולבת (IDE) או עורך טקסט לבחירתכם, שמוגדרים עם הפלאגינים של Dart ו-Flutter, כמו Android Studio או Visual Studio Code.
- הגרסה האחרונה של Flutter (
stable) אוbetaאם אתם אוהבים להיות בחזית הטכנולוגיה. - חשבון Google ליצירה ולניהול של פרויקט Firebase.
- מחוברים לחשבון Google באמצעות
FirebaseCLI.
2. קבלת קוד לדוגמה
מורידים את הגרסה הראשונית של הפרויקט מ-GitHub:
- משכפלים משורת הפקודה את מאגר GitHub לספרייה
flutter-codelabs:
git clone https://github.com/flutter/codelabs.git flutter-codelabs
הספרייה flutter-codelabs מכילה את הקוד של אוסף של codelabs. הקוד של ה-Codelab הזה נמצא בספרייה flutter-codelabs/firebase-get-to-know-flutter. הספרייה מכילה סדרה של תמונות מצב שמראות איך הפרויקט צריך להיראות בסוף כל שלב. לדוגמה, אתם בשלב השני.
- מאתרים את הקבצים התואמים לשלב השני:
cd flutter-codelabs/firebase-get-to-know-flutter/step_02
אם רוצים לדלג קדימה או לראות איך משהו אמור להיראות אחרי שלב מסוים, צריך לחפש בספרייה שנקראת על שם השלב שמעניין אתכם.
ייבוא של אפליקציה לתחילת הדרך
- פותחים או מייבאים את ספריית
flutter-codelabs/firebase-get-to-know-flutter/step_02בסביבת הפיתוח המשולבת (IDE) המועדפת. בספרייה הזו יש קוד התחלתי לשיעור ה-Codelab, שכולל אפליקציית מפגש של Flutter שעדיין לא פועלת. כשפותחים את האפליקציה בסביבת הפיתוח המשולבת (IDE), אפשר לראות שגיאות בזמן ההידור, שיתוקנו בשלב 4.
איתור הקבצים שצריך לעבוד עליהם
הקוד באפליקציה הזו מפוזר על פני כמה ספריות. החלוקה הזו של הפונקציונליות מקלה על העבודה כי היא מקבצת את הקוד לפי פונקציונליות.
- מאתרים את הקבצים הבאים:
-
lib/main.dart: הקובץ הזה מכיל את נקודת הכניסה הראשית ואת הווידג'ט של האפליקציה. -
lib/home_page.dart: הקובץ הזה מכיל את הווידג'ט של דף הבית. -
lib/src/widgets.dart: הקובץ הזה מכיל כמה ווידג'טים שעוזרים ליצור סגנון אחיד לאפליקציה. הם מרכיבים את המסך של אפליקציית המתחילים. -
lib/src/authentication.dart: הקובץ הזה מכיל הטמעה חלקית של אימות עם קבוצה של ווידג'טים ליצירת חוויית משתמש להתחברות לאימות מבוסס-אימייל ב-Firebase. הווידג'טים האלה לתהליך האימות עדיין לא נמצאים בשימוש באפליקציה לתחילת הדרך, אבל תוכלו להוסיף אותם בקרוב.
-
מוסיפים קבצים נוספים לפי הצורך כדי לבנות את שאר האפליקציה.
בדיקת הקובץ lib/main.dart
האפליקציה הזו משתמשת בחבילה google_fonts כדי להגדיר את הגופן Roboto כברירת המחדל בכל האפליקציה. אתם יכולים לעיין בכתובת fonts.google.com ולהשתמש בגופנים שאתם מוצאים שם בחלקים שונים של האפליקציה.
אתם משתמשים בווידג'טים של העזרה מהקובץ lib/src/widgets.dart בצורה של Header, Paragraph ו-IconAndDetail. הווידג'טים האלה מבטלים קוד כפול כדי לצמצם את העומס בפריסת הדף שמתוארת בHomePage. כך גם אפשר לשמור על מראה ותחושה עקביים.
כך נראית האפליקציה ב-Android, ב-iOS, באינטרנט וב-macOS:
|
|
|
|
3. יצירה והגדרה של פרויקט Firebase
הצגת פרטי האירוע חשובה לאורחים, אבל היא לא מאוד שימושית בפני עצמה. צריך להוסיף לאפליקציה פונקציונליות דינמית. כדי לעשות זאת, צריך לקשר את Firebase לאפליקציה. כדי להתחיל להשתמש ב-Firebase, צריך ליצור ולהגדיר פרויקט Firebase.
יצירת פרויקט Firebase
- נכנסים למסוף Firebase באמצעות חשבון Google.
- לוחצים על הלחצן כדי ליצור פרויקט חדש, ואז מזינים שם לפרויקט (לדוגמה,
Firebase-Flutter-Codelab).
- לוחצים על המשך.
- אם מוצגת בקשה לעשות זאת, קוראים ומאשרים את התנאים של Firebase, ואז לוחצים על המשך.
- (אופציונלי) מפעילים את העזרה מבוססת-AI במסוף Firebase (שנקראת 'Gemini ב-Firebase').
- ב-codelab הזה לא צריך להשתמש ב-Google Analytics, ולכן משביתים את האפשרות של Google Analytics.
- לוחצים על יצירת פרויקט, מחכים שהפרויקט יוקצה ולוחצים על המשך.
מידע נוסף על פרויקטים ב-Firebase זמין במאמר הסבר על פרויקטים ב-Firebase.
הגדרת מוצרים של Firebase
האפליקציה משתמשת במוצרי Firebase הבאים, שזמינים לאפליקציות אינטרנט:
- אימות: מאפשר למשתמשים להיכנס לאפליקציה.
- Firestore: שומר נתונים מובנים בענן ומקבל התראות מיידיות כשנתונים משתנים.
- כללי אבטחה של Firebase: מאבטחים את מסד הנתונים.
כדי להשתמש בחלק מהמוצרים האלה, צריך לבצע הגדרה מיוחדת או להפעיל אותם במסוף Firebase.
הפעלת אימות לכניסה לחשבון באמצעות אימייל
- בחלונית Project overview במסוף Firebase, מרחיבים את התפריט Build.
- לוחצים על אימות > תחילת העבודה > שיטת הכניסה > אימייל/סיסמה > הפעלה > שמירה.

הגדרת Firestore
אפליקציית האינטרנט משתמשת ב-Firestore כדי לשמור הודעות בצ'אט ולקבל הודעות חדשות בצ'אט.
כך מגדירים את Firestore בפרויקט Firebase:
- בחלונית הימנית במסוף Firebase, מרחיבים את Build ובוחרים באפשרות Firestore database.
- לוחצים על יצירת מסד נתונים.
- בוחרים במהדורה Standard ולוחצים על Next (הבא).
- משאירים את הערך
(default)בשדה מזהה מסד נתונים. - בוחרים מיקום למסד הנתונים ולוחצים על הבא.
באפליקציה אמיתית, כדאי לבחור מיקום שקרוב למשתמשים. - לוחצים על התחלת השימוש במצב בדיקה. קוראים את כתב הוויתור בנוגע לכללי האבטחה.
בהמשך ה-codelab הזה, תוסיפו כללי אבטחה כדי לאבטח את הנתונים. אל תפיצו או תחשפו אפליקציה באופן ציבורי בלי להוסיף כללי אבטחה למסד הנתונים. - לוחצים על יצירה.
4. הגדרת Firebase
כדי להשתמש ב-Firebase עם Flutter, צריך לבצע את המשימות הבאות כדי להגדיר את פרויקט Flutter לשימוש נכון בספריות FlutterFire:
- מוסיפים את יחסי התלות
FlutterFireלפרויקט. - רושמים את הפלטפורמה שנבחרה בפרויקט Firebase.
- מורידים את קובץ ההגדרה שספציפי לפלטפורמה ואז מוסיפים אותו לקוד.
בספרייה ברמה העליונה של אפליקציית Flutter יש ספריות משנה android, ios, macos ו-web, שמכילות את קובצי ההגדרות הספציפיים לפלטפורמה עבור iOS ו-Android, בהתאמה.
הגדרת יחסי תלות
צריך להוסיף את ספריות FlutterFire של שני מוצרי Firebase שבהם אתם משתמשים באפליקציה הזו: Authentication ו-Firestore.
- משורת הפקודה, מוסיפים את יחסי התלות הבאים מהרמה הבסיסית (root) של האפליקציה (
.../firebase-get-to-know-flutter/step_02):
$ flutter pub add firebase_core firebase_auth cloud_firestore provider firebase_ui_auth
כדי להשתמש ב-Firebase באפליקציית Flutter, צריך לשלב כמה חבילות ייעודיות:
-
firebase_corepackage: זו נקודת ההתחלה החיונית. חובה להתקין את החבילה הזו, כי כל שאר כלי Firebase ל-Flutter תלויים בה. -
firebase_authpackage: החבילה הזו מיועדת לטיפול בחשבונות משתמשים. הוא מאפשר להוסיף תכונות כמו הרשמה, התחברות ויציאה מהחשבון. -
cloud_firestorepackage: משתמשים בחבילה הזו כדי לחבר את האפליקציה למסד הנתונים של Firestore, וכך אפשר לשמור את הנתונים של האפליקציה ולגשת אליהם. -
firebase_ui_authpackage: החבילה הזו מאפשרת להגדיר אימות הרבה יותר מהר. היא מספקת ווידג'טים מוכנים לשימוש (כמו מסכי כניסה מוכנים מראש), כך שלא צריך לבנות הכול מאפס. -
providerpackage: זוהי בחירה פופולרית לניהול מצב. הוא עוזר לאפליקציה לעקוב אחרי מידע (למשל, מי מחובר) ולהפוך את הנתונים האלה לזמינים בכל המסכים השונים שזקוקים להם.
הוספתם את החבילות הנדרשות, אבל אתם צריכים גם להגדיר את פרויקטי ה-runner של iOS, Android, macOS ו-Web כדי להשתמש ב-Firebase בצורה מתאימה. אתם גם משתמשים בחבילה provider שמאפשרת הפרדה בין הלוגיקה העסקית לבין לוגיקת התצוגה.
התקנת FlutterFire CLI
FlutterFire CLI תלוי ב-Firebase CLI הבסיסי.
- אם עדיין לא עשיתם זאת, מתקינים את Firebase CLI במחשב.
- מתקינים את FlutterFire CLI:
$ dart pub global activate flutterfire_cli
אחרי ההתקנה, הפקודה flutterfire זמינה בכל מקום.
הגדרת האפליקציות
ממשק ה-CLI מחלץ מידע מפרויקט Firebase ומאפליקציות נבחרות בפרויקט כדי ליצור את כל ההגדרות לפלטפורמה ספציפית.
בספריית הבסיס של האפליקציה (flutter-codelabs/firebase-get-to-know-flutter/step_02 ),מריצים את הפקודה configure:
$ flutterfire configure
פקודת ההגדרה מבצעת את התהליכים הבאים:
- בוחרים פרויקט ב-Firebase על סמך קובץ
.firebasercאו מתוך מסוף Firebase. - קובעים את הפלטפורמות להגדרה, כמו Android, iOS, macOS ואינטרנט.
- מזהים את האפליקציות ב-Firebase שמהן רוצים לחלץ את ההגדרות. כברירת מחדל, ממשק ה-CLI מנסה להתאים באופן אוטומטי אפליקציות ב-Firebase על סמך הגדרות הפרויקט הנוכחיות.
- יצירת קובץ
firebase_options.dartבפרויקט.
הגדרת macOS
ב-Flutter ב-macOS נוצרות אפליקציות עם ארגז חול מלא. האפליקציה הזו משולבת ברשת כדי לתקשר עם שרתי Firebase, ולכן צריך להגדיר אותה עם הרשאות של לקוח רשת.
macos/Runner/DebugProfile.entitlements
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
<!-- Add the following two lines -->
<key>com.apple.security.network.client</key>
<true/>
</dict>
</plist>
macos/Runner/Release.entitlements
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<!-- Add the following two lines -->
<key>com.apple.security.network.client</key>
<true/>
</dict>
</plist>
מידע נוסף זמין במאמר תמיכה ב-Flutter למחשבים.
5. הוספת אפשרות לאישור השתתפות
אחרי שמוסיפים את Firebase לאפליקציה, אפשר ליצור לחצן אישור השתתפות שמאפשר לאנשים להירשם באמצעות אימות. ב-Android native, ב-iOS native ובאינטרנט, יש חבילות FirebaseUI Auth מוכנות מראש, אבל צריך ליצור את היכולת הזו ב-Flutter.
הפרויקט שאחזרתם קודם כלל קבוצה של ווידג'טים שמיישמים את ממשק המשתמש עבור רוב תהליך האימות. אתם מטמיעים את הלוגיקה העסקית כדי לשלב את האימות באפליקציה.
הוספת לוגיקה עסקית באמצעות חבילת Provider
משתמשים בחבילה provider כדי להפוך אובייקט מרכזי של מצב האפליקציה לזמין בכל העץ של ווידג'טים של Flutter באפליקציה:
- יוצרים קובץ חדש בשם
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, שהיא דרך מצוינת להגדיר במהירות מסכי כניסה באפליקציות שלכם.
הטמעה של תהליך האימות
- משנים את ההגדרות של הייבוא בחלק העליון של הקובץ
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';
- מקשרים את מצב האפליקציה לאתחול האפליקציה, ואז מוסיפים את תהליך האימות ל-
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 יודעת מתי להציג מחדש ווידג'טים תלויים.
- כדי לעדכן את האפליקציה כך שתטפל בניווט למסכים שונים ש-
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,
),
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
textTheme: GoogleFonts.robotoTextTheme(
Theme.of(context).textTheme,
),
visualDensity: VisualDensity.adaptivePlatformDensity
),
routerConfig: _router, // new
);
}
}
לכל מסך משויך סוג פעולה שונה בהתאם למצב החדש של תהליך האימות. אחרי רוב השינויים בסטטוס האימות, אפשר להפנות חזרה למסך מועדף, בין אם זה מסך הבית או מסך אחר, כמו הפרופיל.
- בשיטת הבנייה של המחלקה
HomePage, משלבים את מצב האפליקציה עם הווידג'טAuthFunc:
lib/home_page.dart
import 'package:firebase_auth/firebase_auth.dart' // new
hide EmailAuthProvider, PhoneAuthProvider; // new
import 'package:flutter/material.dart'; // new
import 'package:provider/provider.dart'; // new
import 'app_state.dart'; // new
import 'src/authentication.dart'; // new
import 'src/widgets.dart';
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Firebase Meetup'),
),
body: ListView(
children: <Widget>[
Image.asset('assets/codelab.png'),
const SizedBox(height: 8),
const IconAndDetail(Icons.calendar_today, 'October 30'),
const IconAndDetail(Icons.location_city, 'San Francisco'),
// Add from here
Consumer<ApplicationState>(
builder: (context, appState, _) => AuthFunc(
loggedIn: appState.loggedIn,
signOut: () {
FirebaseAuth.instance.signOut();
}),
),
// to here
const Divider(
height: 8,
thickness: 1,
indent: 8,
endIndent: 8,
color: Colors.grey,
),
const Header("What we'll be doing"),
const Paragraph(
'Join us for a day full of Firebase Workshops and Pizza!',
),
],
),
);
}
}
יוצרים מופע של הווידג'ט AuthFunc ועוטפים אותו בווידג'ט Consumer. הווידג'ט Consumer הוא הדרך הרגילה להשתמש בחבילה provider כדי לבנות מחדש חלק מהעץ כשמצב האפליקציה משתנה. הווידג'ט AuthFunc הוא הווידג'ט הנוסף שאתם בודקים.
הפעלת האפליקציה כדי לבדוק את תהליך האימות

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

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

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

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

הוספת הודעות ל-Firestore
בקטע הזה מוסיפים את הפונקציונליות שמאפשרת למשתמשים לכתוב הודעות למסד הנתונים. קודם מוסיפים שדה טופס ולחצן שליחה, ואז מוסיפים את הקוד שמקשר בין הרכיבים האלה לבין מסד הנתונים.
- יוצרים קובץ חדש בשם
guest_book.dart, מוסיפים ווידג'ט עם מצבGuestBookכדי ליצור את רכיבי ממשק המשתמש של שדה הודעה ולחצן שליחה:
lib/guest_book.dart
import 'dart:async';
import 'package:flutter/material.dart';
import 'src/widgets.dart';
class GuestBook extends StatefulWidget {
const GuestBook({required this.addMessage, super.key});
final FutureOr<void> Function(String message) addMessage;
@override
State<GuestBook> createState() => _GuestBookState();
}
class _GuestBookState extends State<GuestBook> {
final _formKey = GlobalKey<FormState>(debugLabel: '_GuestBookState');
final _controller = TextEditingController();
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Form(
key: _formKey,
child: Row(
children: [
Expanded(
child: TextFormField(
controller: _controller,
decoration: const InputDecoration(
hintText: 'Leave a message',
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Enter your message to continue';
}
return null;
},
),
),
const SizedBox(width: 8),
StyledButton(
onPressed: () async {
if (_formKey.currentState!.validate()) {
await widget.addMessage(_controller.text);
_controller.clear();
}
},
child: Row(
children: const [
Icon(Icons.send),
SizedBox(width: 4),
Text('SEND'),
],
),
),
],
),
),
);
}
}
יש כאן כמה נקודות מעניינות. קודם כל, יוצרים מופע של טופס כדי לוודא שההודעה באמת מכילה תוכן, ואם לא, להציג למשתמש הודעת שגיאה. כדי לאמת טופס, ניגשים למצב הטופס מאחורי הטופס באמצעות GlobalKey. מידע נוסף על מפתחות ועל אופן השימוש בהם זמין במאמר מתי כדאי להשתמש במפתחות.
שימו לב גם לאופן שבו הווידג'טים מסודרים. יש Row עם TextFormField ו-StyledButton, שמכיל Row. שימו לב גם שהרכיב TextFormField עטוף בווידג'ט Expanded, שמכריח את הרכיב TextFormField למלא את כל השטח הנוסף בשורה. כדי להבין טוב יותר למה זה נדרש, אפשר לעיין במאמר הסבר על אילוצים.
עכשיו יש לכם ווידג'ט שמאפשר למשתמש להזין טקסט כדי להוסיף אותו לספר האורחים, ואתם צריכים להציג אותו במסך.
- עורכים את גוף התג
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)),
הקוד הזה מספיק כדי להציג את הווידג'ט, אבל לא מספיק כדי לבצע פעולות שימושיות. תצטרכו לעדכן את הקוד הזה בקרוב כדי שהוא יפעל.
תצוגה מקדימה של האפליקציה
|
|
|
|
כשמשתמש לוחץ על SEND, מופעל קטע הקוד הבא. התוכן של שדה להזנת קלט של ההודעה מתווסף לאוסף 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({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Firebase Meetup'),
),
body: ListView(
children: <Widget>[
Image.asset('assets/codelab.png'),
const SizedBox(height: 8),
const IconAndDetail(Icons.calendar_today, 'October 30'),
const IconAndDetail(Icons.location_city, 'San Francisco'),
Consumer<ApplicationState>(
builder: (context, appState, _) => AuthFunc(
loggedIn: appState.loggedIn,
signOut: () {
FirebaseAuth.instance.signOut();
}),
),
const Divider(
height: 8,
thickness: 1,
indent: 8,
endIndent: 8,
color: Colors.grey,
),
const Header("What we'll be doing"),
const Paragraph(
'Join us for a day full of Firebase Workshops and Pizza!',
),
// Modify from here...
Consumer<ApplicationState>(
builder: (context, appState, _) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (appState.loggedIn) ...[
const Header('Discussion'),
GuestBook(
addMessage: (message) =>
appState.addMessageToGuestBook(message),
),
],
],
),
),
// ...to here.
],
),
);
}
}
החלפתם את שתי השורות שהוספתם בתחילת השלב הזה בהטמעה המלאה. שוב משתמשים ב-Consumer<ApplicationState> כדי להפוך את מצב האפליקציה לזמין לחלק בעץ שאתם מציגים. כך תוכלו להגיב למישהו שמזין הודעה בממשק המשתמש ומפרסם אותה במסד הנתונים. בקטע הבא, בודקים אם ההודעות שנוספו מתפרסמות במסד הנתונים.
בדיקה של שליחת הודעות
- אם צריך, נכנסים לאפליקציה.
- כותבים את ההודעה, למשל
Hey there!, ולוחצים על שליחה.
הפעולה הזו כותבת את ההודעה למסד הנתונים של Firestore. עם זאת, ההודעה לא מוצגת באפליקציית Flutter בפועל כי עדיין צריך להטמיע את אחזור הנתונים, וזה מה שנעשה בשלב הבא. עם זאת, במרכז הבקרה של מסד הנתונים במסוף Firebase, אפשר לראות את ההודעה שנוספה באוסף guestbook. אם תשלחו עוד הודעות, תתווספו עוד מסמכים לאוסף guestbook. לדוגמה, קטע הקוד הבא:

7. קריאת הודעות
נחמד שאורחים יכולים לכתוב הודעות למסד הנתונים, אבל הם עדיין לא יכולים לראות אותן באפליקציה. הגיע הזמן לפתור את הבעיה.
סנכרון הודעות
כדי להציג הודעות, צריך להוסיף מאזינים שמופעלים כשנתונים משתנים, ואז ליצור רכיב בממשק המשתמש שמציג הודעות חדשות. מוסיפים קוד למצב האפליקציה שמחכה להודעות חדשות שנוספו מהאפליקציה.
- יוצרים קובץ חדש
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;
}
- מוסיפים את שורות הייבוא הבאות לקובץ
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
- בקטע
ApplicationStateשבו מגדירים את המצב ואת הפונקציות לקבלת ערכים, מוסיפים את השורות הבאות:
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.
- בקטע האתחול של
ApplicationState, מוסיפים את השורות הבאות כדי להירשם לחיפוש במאגר המסמכים כשמשתמש מתחבר לחשבון ולבטל את ההרשמה כשהוא מתנתק:
lib/app_state.dart
Future<void> init() async {
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform);
FirebaseUIAuth.configureProviders([
EmailAuthProvider(),
]);
FirebaseAuth.instance.userChanges().listen((user) {
if (user != null) {
_loggedIn = true;
// Add from here...
_guestBookSubscription = FirebaseFirestore.instance
.collection('guestbook')
.orderBy('timestamp', descending: true)
.snapshots()
.listen((snapshot) {
_guestBookMessages = [];
for (final document in snapshot.docs) {
_guestBookMessages.add(
GuestBookMessage(
name: document.data()['name'] as String,
message: document.data()['text'] as String,
),
);
}
notifyListeners();
});
// ...to here.
} else {
_loggedIn = false;
// Add from here...
_guestBookMessages = [];
_guestBookSubscription?.cancel();
// to here.
}
notifyListeners();
});
}
החלק הזה חשוב כי בו בונים שאילתה על אוסף guestbook ומטפלים בהרשמה לאוסף הזה ובהסרת ההרשמה ממנו. אתם מאזינים לשידור, שבו אתם בונים מחדש מטמון מקומי של ההודעות באוסף guestbook, וגם שומרים הפניה למינוי הזה כדי שתוכלו לבטל אותו מאוחר יותר. יש כאן הרבה דברים שקורים, ולכן כדאי לבדוק את הקוד הזה בכלי לניפוי באגים כדי להבין מה קורה ולקבל תמונה ברורה יותר. מידע נוסף זמין במאמר קבלת עדכונים בזמן אמת באמצעות Firestore.
- מוסיפים את הייבוא הבא לקובץ
lib/guest_book.dart:
import 'guest_book_message.dart';
- בווידג'ט
GuestBook, מוסיפים רשימה של הודעות כחלק מההגדרה כדי לקשר את מצב השינוי הזה לממשק המשתמש:
lib/guest_book.dart
class GuestBook extends StatefulWidget {
// Modify the following line:
const GuestBook({
super.key,
required this.addMessage,
required this.messages,
});
final FutureOr<void> Function(String message) addMessage;
final List<GuestBookMessage> messages; // new
@override
State<GuestBook> createState() => _GuestBookState();
}
- ב-
_GuestBookState, משנים את השיטהbuildבאופן הבא כדי לחשוף את ההגדרה הזו:
lib/guest_book.dart
class _GuestBookState extends State<GuestBook> {
final _formKey = GlobalKey<FormState>(debugLabel: '_GuestBookState');
final _controller = TextEditingController();
@override
Widget build(BuildContext context) {
// Modify from here...
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// ...to here.
Padding(
padding: const EdgeInsets.all(8.0),
child: Form(
key: _formKey,
child: Row(
children: [
Expanded(
child: TextFormField(
controller: _controller,
decoration: const InputDecoration(
hintText: 'Leave a message',
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Enter your message to continue';
}
return null;
},
),
),
const SizedBox(width: 8),
StyledButton(
onPressed: () async {
if (_formKey.currentState!.validate()) {
await widget.addMessage(_controller.text);
_controller.clear();
}
},
child: Row(
children: [
Icon(Icons.send),
SizedBox(width: 4),
Text('SEND'),
],
),
),
],
),
),
),
// Modify from here...
const SizedBox(height: 8),
for (var message in widget.messages)
Paragraph('${message.name}: ${message.message}'),
const SizedBox(height: 8),
],
// ...to here.
);
}
}
עוטפים את התוכן הקודם של השיטה build() בווידג'ט Column, ואז מוסיפים collection for בסוף הצאצאים של Column כדי ליצור Paragraph חדש לכל הודעה ברשימת ההודעות.
- מעדכנים את גוף התג
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 מסנכרן אוטומטית ובאופן מיידי את הנתונים עם לקוחות שמנויים למסד הנתונים.
בדיקת סנכרון ההודעות:
- באפליקציה, מוצאים את ההודעות שיצרתם קודם במסד הנתונים.
- לכתוב הודעות חדשות. הן מופיעות באופן מיידי.
- פותחים את סביבת העבודה בכמה חלונות או כרטיסיות. ההודעות מסונכרנות בזמן אמת בין החלונות והכרטיסיות.
- אופציונלי: בתפריט Database (מסד נתונים) במסוף Firebase, אפשר למחוק, לשנות או להוסיף הודעות חדשות באופן ידני. כל השינויים מופיעים בממשק המשתמש.
כל הכבוד! קראתם מסמכי Firestore באפליקציה!
תצוגה מקדימה של האפליקציה
|
|
|
|
8. הגדרת כללי אבטחה בסיסיים
הגדרתם את Firestore כך שישתמש במצב בדיקה, כלומר מסד הנתונים שלכם פתוח לקריאה ולכתיבה. עם זאת, מומלץ להשתמש במצב בדיקה רק בשלבים הראשונים של הפיתוח. מומלץ להגדיר כללי אבטחה למסד הנתונים במהלך פיתוח האפליקציה. האבטחה היא חלק בלתי נפרד מהמבנה וההתנהגות של האפליקציה.
כללי האבטחה של Firebase מאפשרים לכם לשלוט בגישה למסמכים ולאוספים במסד הנתונים. תחביר הכללים הגמיש מאפשר ליצור כללים שתואמים לכל דבר, החל מכל פעולות הכתיבה במסד הנתונים כולו ועד לפעולות במסמך ספציפי.
הגדרת כללי אבטחה בסיסיים:
- בתפריט Develop (פיתוח) במסוף Firebase, לוחצים על Database > Rules (מסד נתונים > כללים). יוצגו לכם כללי האבטחה הבאים שמוגדרים כברירת מחדל, ואזהרה לגבי הכללים שגלויים לכולם:

- מזהים את האוספים שהאפליקציה כותבת אליהם נתונים:
ב-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 של האימות ולוודא שלכל מי שמנסה לכתוב במסמך יש 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. שלב בונוס: תרגול של מה שלמדתם
תיעוד סטטוס אישור ההשתתפות של משתתף
כרגע, באפליקציה שלך אנשים יכולים לשוחח בצ'אט רק אם הם מתעניינים באירוע. בנוסף, הדרך היחידה לדעת אם מישהו מגיע היא אם הוא אומר זאת בצ'אט.
בשלב הזה, מארגנים את האירוע ומודיעים לאנשים כמה אנשים מגיעים. מוסיפים כמה יכולות למצב האפליקציה. האפשרות הראשונה היא שמשתמש שמחובר לחשבון יוכל לציין אם הוא ישתתף בפגישה. השני הוא מונה שמראה כמה אנשים משתתפים.
- בקובץ
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});
}
}
- כדי לעדכן את שיטת
ApplicationStateinit():
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();
});
}
הקוד הזה מוסיף שאילתה שתמיד מופעלת כדי לקבוע את מספר המשתתפים, ושאילתה שנייה שמופעלת רק בזמן שהמשתמש מחובר כדי לקבוע אם הוא משתתף.
- מוסיפים את המידע הבא לחלק העליון של הקובץ
lib/app_state.dart.
lib/app_state.dart
enum Attending { yes, no, unknown }
- יוצרים קובץ חדש
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'),
),
],
),
);
}
}
}
הוא מתחיל במצב לא מוגדר, בלי שתיבחר האפשרות כן או לא. אחרי שהמשתמש בוחר אם הוא ישתתף, האפשרות הזו מוצגת עם לחצן מלא והאפשרות השנייה מוצגת עם לחצן שטוח.
- כדי ליהנות מ
YesNoSelection, לעדכן את השיטה שלHomePagebuild()כך שמשתמשים מחוברים יוכלו לציין אם הם משתתפים באירוע, ולהציג את מספר המשתתפים באירוע:
lib/home_page.dart
import 'yes_no_selection.dart'; // new
Consumer<ApplicationState>(
builder: (context, appState, _) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Add from here...
switch (appState.attendees) {
1 => const Paragraph('1 person going'),
>= 2 => Paragraph('${appState.attendees} people going'),
_ => const Paragraph('No one going'),
},
// ...to here.
if (appState.loggedIn) ...[
// Add from here...
YesNoSelection(
state: appState.attending,
onSelection: (attending) => appState.attending = attending,
),
// ...to here.
const Header('Discussion'),
GuestBook(
addMessage: (message) =>
appState.addMessageToGuestBook(message),
messages: appState.guestBookMessages,
),
],
],
),
),
הוספת כללים
כבר הגדרתם כמה כללים, לכן הנתונים שתוסיפו באמצעות הלחצנים יידחו. צריך לעדכן את הכללים כדי לאפשר הוספות לאוסף attendees.
- באוסף
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;
}
}
}
- אופציונלי: באפליקציה, לוחצים על הלחצנים כדי לראות את התוצאות בלוח הבקרה של Firestore במסוף Firebase.
תצוגה מקדימה של האפליקציה
|
|
|
|
10. כל הכבוד!
השתמשתם ב-Firebase כדי לבנות אפליקציית אינטרנט אינטראקטיבית בזמן אמת!















