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 שעדיין לא פעילה.
אתר את הקבצים לעבוד עליהם
הקוד באפליקציה זו מתפזר על פני מספר ספריות. פיצול זה של פונקציונליות נועד להקל על העבודה, על ידי קיבוץ קוד לפי פונקציונליות.
אתר את הקבצים הבאים בפרויקט:
-
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
- היכנס ל- Firebase .
- במסוף Firebase, לחץ על הוסף פרויקט (או צור פרויקט ), ושם לפרויקט Firebase שלך Firebase-Flutter-Codelab .
- לחץ על אפשרויות יצירת הפרויקט. קבל את תנאי Firebase אם תתבקש. דלג על הגדרת Google Analytics, כי לא תשתמש ב-Analytics עבור האפליקציה הזו.
למידע נוסף על פרויקטים של Firebase, ראה הבנת פרויקטי Firebase .
האפליקציה שאתה בונה משתמשת במספר מוצרי Firebase הזמינים עבור אפליקציות אינטרנט:
- אימות Firebase כדי לאפשר למשתמשים שלך להיכנס לאפליקציה שלך.
- Cloud Firestore כדי לשמור נתונים מובנים בענן ולקבל הודעה מיידית כאשר הנתונים משתנים.
- כללי אבטחה של Firebase לאבטחת מסד הנתונים שלך.
חלק מהמוצרים האלה זקוקים לתצורה מיוחדת או שצריך להפעיל אותם באמצעות מסוף Firebase.
אפשר כניסה בדוא"ל עבור אימות Firebase
כדי לאפשר למשתמשים להיכנס לאפליקציית האינטרנט, תשתמש בשיטת הכניסה לדוא"ל/סיסמה עבור מעבדת קוד זה:
- במסוף Firebase, הרחב את תפריט Build בחלונית השמאלית.
- לחץ על אימות ולאחר מכן לחץ על הלחצן התחל , ולאחר מכן על הכרטיסייה שיטת כניסה (או לחץ כאן כדי לעבור ישירות לכרטיסייה שיטת כניסה ).
- לחץ על דואר אלקטרוני/סיסמה ברשימת ספקי הכניסה , הגדר את המתג הפעל למצב מופעל ולאחר מכן לחץ על שמור .
הפעל את Cloud Firestore
אפליקציית האינטרנט משתמשת ב- Cloud Firestore כדי לשמור הודעות צ'אט ולקבל הודעות צ'אט חדשות.
הפעל את Cloud Firestore:
- בקטע Build של מסוף Firebase, לחץ על Cloud Firestore .
- לחץ על צור מסד נתונים .
- בחר באפשרות התחל במצב בדיקה . קרא את כתב הוויתור על כללי האבטחה. מצב בדיקה מבטיח שאתה יכול לכתוב בחופשיות למסד הנתונים במהלך הפיתוח. לחץ על הבא .
- בחר את המיקום עבור מסד הנתונים שלך (אתה יכול פשוט להשתמש בברירת המחדל). שים לב שלא ניתן לשנות את המיקום הזה מאוחר יותר.
- לחץ על הפעל .
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
פקודת התצורה תדריך אותך במספר תהליכים:
- בחירת פרויקט Firebase (בהתבסס על קובץ ה-.firebaserc או ממסוף Firebase).
- בקש מהי הפלטפורמות (למשל אנדרואיד, 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>
ראה הרשאות ו-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
הוא ממשק המשתמש של האימות שתבדוק כעת.
בדיקת זרימת האימות
הנה ההתחלה של זרימת האימות, שבה המשתמש יכול להקיש על כפתור ה-RSVP, כדי להפעיל את טופס הדוא"ל.
עם הזנת המייל, המערכת מאשרת אם המשתמש כבר רשום, ובמקרה זה מתבקש המשתמש להזין סיסמה, לחילופין אם המשתמש אינו רשום, הוא עובר בטופס ההרשמה.
הקפד לנסות להזין סיסמה קצרה (פחות משישה תווים) כדי לבדוק את זרימת הטיפול בשגיאות. אם המשתמש רשום, הוא יראה את הסיסמה עבור במקום זאת.
בדף זה הקפד להזין סיסמאות שגויות כדי לבדוק את הטיפול בשגיאות בדף זה. לבסוף, ברגע שהמשתמש התחבר, תראה את חוויית הכניסה שמציעה למשתמש את האפשרות להתנתק שוב.
ועם זה, יישמת זרימת אימות. מזל טוב!
6. כתוב הודעות ל-Cloud Firestore
לדעת שמשתמשים מגיעים זה נהדר, אבל בואו ניתן לאורחים משהו אחר לעשות באפליקציה. מה אם הם יכלו להשאיר הודעות בספר אורחים? הם יכולים לשתף מדוע הם מתרגשים להגיע או את מי הם מקווים לפגוש.
כדי לאחסן את הודעות הצ'אט שמשתמשים כותבים באפליקציה, תשתמש ב- Cloud Firestore .
מודל נתונים
Cloud Firestore הוא מסד נתונים NoSQL, והנתונים המאוחסנים במסד הנתונים מפוצלים לאוספים, מסמכים, שדות ותתי-אוספים. תשמור כל הודעה בצ'אט כמסמך באוסף ברמה העליונה שנקרא guestbook
.
הוסף הודעות ל-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>
כדי להפוך את מצב היישום לזמין לחלק של העץ שאתה מעבד. זה מאפשר לך להגיב למישהו שמזין הודעה בממשק המשתמש ולפרסם אותה במסד הנתונים. בחלק הבא תבדוק אם ההודעות שנוספו מתפרסמות במסד הנתונים.
בדיקת שליחת הודעות
- ודא שאתה מחובר לאפליקציה.
- הזן הודעה כגון "היי לך!", ולאחר מכן לחץ על שלח .
פעולה זו כותבת את ההודעה למסד הנתונים של Cloud Firestore שלך. עם זאת, עדיין לא תראה את ההודעה באפליקציית Flutter בפועל שלך מכיוון שאתה עדיין צריך ליישם אחזור הנתונים. אתה תעשה את זה בשלב הבא.
אבל אתה יכול לראות את ההודעה החדשה שנוספה במסוף Firebase.
במסוף Firebase, בלוח המחוונים של מסד הנתונים , אתה אמור לראות את אוסף guestbook
עם ההודעה החדשה שהוספת. אם תמשיך לשלוח הודעות, אוסף ספר האורחים שלך יכיל מסמכים רבים, כמו זה:
קונסולת Firebase
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 מסנכרן נתונים באופן אוטומטי ומיידי עם לקוחות הרשומים למסד הנתונים.
- ההודעות שיצרת קודם לכן במסד הנתונים צריכות להיות מוצגות באפליקציה. אתם מוזמנים לכתוב הודעות חדשות; הם צריכים להופיע באופן מיידי.
- אם תפתח את סביבת העבודה שלך במספר חלונות או כרטיסיות, הודעות יסונכרנו בזמן אמת בין כרטיסיות.
- (אופציונלי) תוכל לנסות למחוק, לשנות או להוסיף הודעות חדשות באופן ידני ישירות במקטע מסד הנתונים של מסוף Firebase; כל שינוי אמור להופיע בממשק המשתמש.
מזל טוב! אתה קורא מסמכי Cloud Firestore באפליקציה שלך!
סקירת אפליקציה
8. הגדר כללי אבטחה בסיסיים
תחילה הגדרת את Cloud Firestore לשימוש במצב בדיקה, כלומר מסד הנתונים שלך פתוח לקריאה וכתיבה. עם זאת, עליך להשתמש במצב בדיקה רק בשלבים מוקדמים מאוד של פיתוח. בתור שיטות עבודה מומלצות, עליך להגדיר כללי אבטחה עבור מסד הנתונים שלך בזמן שאתה מפתח את האפליקציה שלך. האבטחה צריכה להיות חלק בלתי נפרד מהמבנה וההתנהגות של האפליקציה שלך.
כללי אבטחה מאפשרים לך לשלוט בגישה למסמכים ואוספים במסד הנתונים שלך. תחביר הכללים הגמיש מאפשר לך ליצור כללים התואמים כל דבר, החל מכל הכתיבה למסד הנתונים כולו ועד לפעולות על מסמך ספציפי.
אתה יכול לכתוב כללי אבטחה עבור Cloud Firestore במסוף Firebase:
- במקטע פיתוח של מסוף Firebase, לחץ על מסד נתונים ולאחר מכן בחר בכרטיסייה כללים (או לחץ כאן כדי לעבור ישירות לכרטיסייה כללים ).
- אתה אמור לראות את כללי האבטחה הבאים כברירת מחדל, יחד עם אזהרה על כך שהכללים פומביים.
זיהוי אוספים
ראשית, זהה את האוספים אליהם האפליקציה כותבת נתונים.
ב- 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 !
למד עוד
- אתר Firebase: firebase.google.com
- אתר פלאטר: flutter.dev
- ווידג'טים של FlutterFire Firebase Flutter: firebase.flutter.dev
- ערוץ YouTube של Firebase
- ערוץ יוטיוב פלאטר
איך זה הלך?
נשמח למשוב שלך! אנא מלא טופס קצר (מאוד) כאן .