1. לפני שמתחילים
ב-codelab הזה תלמדו איך להוסיף אימות Firebase לאפליקציית Flutter באמצעות חבילת ממשק המשתמש של FlutterFire. בעזרת החבילה הזו תוכלו להוסיף לאפליקציית Flutter אימות באמצעות אימייל וסיסמה ואימות באמצעות כניסה לחשבון Google. בנוסף, תלמדו איך להגדיר פרויקט Firebase ואיך להשתמש ב-FlutterFire CLI כדי להפעיל את Firebase באפליקציית Flutter.
דרישות מוקדמות
ב-codelab הזה אנחנו מניחים שיש לכם ניסיון מסוים ב-Flutter. אם לא, כדאי קודם ללמוד את היסודות. הקישורים הבאים יכולים לעזור:
- סיור במסגרת הווידג'טים של Flutter
- כדאי לנסות את שיעור ה-Lab Write Your First Flutter App, part 1
כדאי גם שיהיה לכם ניסיון מסוים ב-Firebase, אבל לא נורא אם אף פעם לא הוספתם את Firebase לפרויקט Flutter. אם אתם לא מכירים את מסוף Firebase, או אם אתם משתמשים חדשים ב-Firebase, כדאי לעיין קודם בקישורים הבאים:
מה תיצרו
בשיעור הזה תלמדו איך ליצור את תהליך האימות באפליקציית Flutter באמצעות Firebase לאימות. לאפליקציה יהיו מסך כניסה, מסך 'הרשמה', מסך לשחזור סיסמה ומסך פרופיל משתמש.
מה תלמדו
ה-Codelab הזה עוסק בנושאים הבאים:
- הוספת Firebase לאפליקציית Flutter
- הגדרה של מסוף Firebase
- שימוש ב-Firebase CLI כדי להוסיף את Firebase לאפליקציה
- שימוש ב-FlutterFire CLI כדי ליצור הגדרות Firebase ב-Dart
- הוספת אימות ב-Firebase לאפליקציית Flutter
- הגדרה של אימות ב-Firebase במסוף
- הוספת כניסה באמצעות כתובת אימייל וסיסמה עם החבילה
firebase_ui_auth
- הוספת רישום משתמשים באמצעות חבילת
firebase_ui_auth
- הוספת הדף 'שכחת את הסיסמה?'
- הוספת כניסה באמצעות חשבון Google עם
firebase_ui_auth
- הגדרת האפליקציה כך שתפעל עם כמה ספקי כניסה.
- הוספת מסך פרופיל משתמש לאפליקציה באמצעות חבילת
firebase_ui_auth
ב-codelab הזה נתמקד בהוספה של מערכת אימות חזקה באמצעות חבילת firebase_ui_auth
. כפי שאפשר לראות, אפשר להטמיע את כל האפליקציה הזו, עם כל התכונות שצוינו למעלה, באמצעות כ-100 שורות קוד.
מה צריך להכין
- ידע בסיסי ב-Flutter, וה-SDK מותקן
- עורך טקסט (Flutter תומך ב-JetBrains IDE, ב-Android Studio וב-VS Code)
- דפדפן Google Chrome או יעד פיתוח מועדף אחר ל-Flutter. (חלק מהפקודות במסוף ב-codelab הזה מניחות שאתם מריצים את האפליקציה ב-Chrome)
2. יצירה והגדרה של פרויקט Firebase
המשימה הראשונה שצריך לבצע היא יצירת פרויקט Firebase במסוף האינטרנט של Firebase.
יצירת פרויקט Firebase
- נכנסים למסוף Firebase באמצעות חשבון Google.
- לוחצים על הלחצן כדי ליצור פרויקט חדש, ואז מזינים שם לפרויקט (לדוגמה,
FlutterFire-UI-Codelab
).
- לוחצים על המשך.
- אם מוצגת בקשה לעשות זאת, קוראים ומאשרים את התנאים של Firebase, ואז לוחצים על המשך.
- (אופציונלי) מפעילים את העזרה מבוססת-AI במסוף Firebase (שנקראת Gemini ב-Firebase).
- ב-codelab הזה לא צריך להשתמש ב-Google Analytics, ולכן משביתים את האפשרות Google Analytics.
- לוחצים על יצירת פרויקט, מחכים שהפרויקט יוקצה ולוחצים על המשך.
מידע נוסף על פרויקטים ב-Firebase זמין במאמר הסבר על פרויקטים ב-Firebase.
הפעלת כניסה באמצעות אימייל ב-Firebase Authentication
האפליקציה שאתם בונים משתמשת באימות ב-Firebase כדי לאפשר למשתמשים להיכנס לאפליקציה. היא גם מאפשרת למשתמשים חדשים להירשם דרך אפליקציית Flutter.
צריך להפעיל את Firebase Authentication באמצעות מסוף Firebase, ונדרשת הגדרה מיוחדת אחרי ההפעלה.
כדי לאפשר למשתמשים להיכנס לאפליקציית האינטרנט, קודם צריך להשתמש בשיטת הכניסה אימייל/סיסמה. בהמשך, תוסיפו את שיטת הכניסה באמצעות חשבון Google.
- ב-Firebase Console, מרחיבים את התפריט Build בחלונית הימנית.
- לוחצים על אימות, ואז על הלחצן תחילת העבודה, ואז על הכרטיסייה שיטת הכניסה (או עוברים ישירות לכרטיסייה שיטת הכניסה).
- ברשימה ספקי כניסה, לוחצים על אימייל/סיסמה, מעבירים את המתג הפעלה למצב מופעל ואז לוחצים על שמירה.
3. הגדרת אפליקציית Flutter
לפני שמתחילים, צריך להוריד את קוד ההתחלה ולהתקין את Firebase CLI.
קבלת קוד לתחילת הדרך
משכפלים את המאגר ב-GitHub משורת הפקודה:
git clone https://github.com/flutter/codelabs.git flutter-codelabs
לחלופין, אם כלי ה-CLI של GitHub מותקן:
gh repo clone flutter/codelabs flutter-codelabs
צריך לשכפל את הקוד לדוגמה לספרייה flutter-codelabs
במחשב, שמכילה את הקוד של אוסף של סדנאות קוד. הקוד של ה-Codelab הזה נמצא בספריית המשנה flutter-codelabs/firebase-auth-flutterfire-ui
.
הספרייה flutter-codelabs/firebase-auth-flutterfire-ui
מכילה שני פרויקטים של Flutter. אחת נקראת complete
והשנייה נקראת start
. הספרייה start
מכילה פרויקט לא שלם, ובה תבלו את רוב הזמן.
cd flutter-codelabs/firebase-auth-flutterfire-ui/start
אם רוצים לדלג קדימה או לראות איך משהו אמור להיראות כשהוא מושלם, אפשר לעיין בספרייה שנקראת complete כדי להשוות.
אם רוצים לעקוב אחרי ההוראות ב-codelab ולהוסיף קוד בעצמכם, צריך להתחיל עם אפליקציית Flutter בכתובת flutter-codelabs/firebase-auth-flutterfire-ui/start
ולהוסיף קוד לפרויקט הזה במהלך ה-codelab. פותחים את הספרייה או מייבאים אותה לסביבת הפיתוח המשולבת (IDE) המועדפת.
התקנת Firebase CLI
ממשק Firebase CLI מספק כלים לניהול פרויקטים ב-Firebase. ה-CLI נדרש ל-FlutterFire CLI, שתתקינו בהמשך.
יש כמה דרכים להתקין את ה-CLI. אפשר לעיין בכל האפשרויות הזמינות למערכת ההפעלה שלכם בכתובת firebase.google.com/docs/cli.
אחרי שמתקינים את ה-CLI, צריך לבצע אימות ב-Firebase.
- מריצים את הפקודה הבאה כדי להתחבר ל-Firebase באמצעות חשבון Google:
firebase login
- הפקודה הזו מקשרת את המחשב המקומי ל-Firebase ומעניקה לכם גישה לפרויקטים שלכם ב-Firebase.
- כדי לבדוק שה-CLI מותקן בצורה תקינה ושיש לו גישה לחשבון שלכם, מריצים את הפקודה לרשימת הפרויקטים ב-Firebase. מריצים את הפקודה הבאה:
firebase projects:list
- הרשימה שמוצגת צריכה להיות זהה לרשימת הפרויקטים ב-Firebase שמופיעה במסוף Firebase. אתם אמורים לראות לפחות
flutterfire-ui-codelab.
התקנת FlutterFire CLI
ממשק FlutterFire CLI הוא כלי שמפשט את תהליך ההתקנה של Firebase בכל הפלטפורמות הנתמכות באפליקציית Flutter. הוא מבוסס על Firebase CLI.
קודם כול, מתקינים את ה-CLI:
dart pub global activate flutterfire_cli
מוודאים שה-CLI הותקן. מריצים את הפקודה הבאה ומוודאים שתפריט העזרה מוצג ב-CLI.
flutterfire --help
הוספת פרויקט Firebase לאפליקציית Flutter
הגדרת FlutterFire
אתם יכולים להשתמש ב-FlutterFire כדי ליצור את קוד ה-Dart שנדרש לשימוש ב-Firebase באפליקציית Flutter.
flutterfire configure
כשמריצים את הפקודה הזו, מוצגת בקשה לבחור את פרויקט Firebase שרוצים להשתמש בו ואת הפלטפורמות שרוצים להגדיר.
בצילומי המסך הבאים מוצגות ההנחיות שצריך לענות עליהן.
- בוחרים את הפרויקט שבו רוצים להשתמש. במקרה כזה, משתמשים ב-
flutterfire-ui-codelab
- בוחרים את הפלטפורמות שבהן רוצים להשתמש. ב-codelab הזה יש שלבים להגדרת אימות ב-Firebase עבור Flutter לאינטרנט, ל-iOS ול-Android, אבל אתם יכולים להגדיר את פרויקט Firebase כך שישתמש בכל האפשרויות.
- בצילום המסך הזה מוצג הפלט בסוף התהליך. אם אתם מכירים את Firebase, תשימו לב שלא הייתם צריכים ליצור אפליקציות לפלטפורמות (לדוגמה, אפליקציית Android) במסוף, ו-FlutterFire CLI עשה את זה בשבילכם.
אחרי שהתהליך מסתיים, בודקים את אפליקציית Flutter בעורך הטקסט. ה-CLI של FlutterFire שינה קובץ בשם firebase_options.dart
. הקובץ הזה מכיל מחלקה בשם FirebaseOptions
, שיש לה משתנים סטטיים שמכילים את הגדרות Firebase שנדרשות לכל פלטפורמה. אם בחרתם את כל הפלטפורמות כשביצעתם את הפקודה flutterfire configure
, יוצגו ערכים סטטיים בשמות web
, android
, ios
ו-macos
.
lib/firebase_options.dart
import 'package:firebase_core/firebase_core.dart' show FirebaseOptions;
import 'package:flutter/foundation.dart'
show defaultTargetPlatform, kIsWeb, TargetPlatform;
class DefaultFirebaseOptions {
static FirebaseOptions get currentPlatform {
if (kIsWeb) {
return web;
}
switch (defaultTargetPlatform) {
case TargetPlatform.android:
return android;
case TargetPlatform.iOS:
return ios;
case TargetPlatform.macOS:
return macos;
default:
throw UnsupportedError(
'DefaultFirebaseOptions are not supported for this platform.',
);
}
}
static const FirebaseOptions web = FirebaseOptions(
apiKey: 'AIzaSyCqFjCV_9CZmYeIvcK9FVy4drmKUlSaIWY',
appId: '1:963656261848:web:7219f7fca5fc70afb237ad',
messagingSenderId: '963656261848',
projectId: 'flutterfire-ui-codelab',
authDomain: 'flutterfire-ui-codelab.firebaseapp.com',
storageBucket: 'flutterfire-ui-codelab.firebasestorage.app',
measurementId: 'G-DGF0CP099H',
);
static const FirebaseOptions android = FirebaseOptions(
apiKey: 'AIzaSyDconZaCQpkxIJ5KQBF-3tEU0rxYsLkIe8',
appId: '1:963656261848:android:c939ccc86ab2dcdbb237ad',
messagingSenderId: '963656261848',
projectId: 'flutterfire-ui-codelab',
storageBucket: 'flutterfire-ui-codelab.firebasestorage.app',
);
static const FirebaseOptions ios = FirebaseOptions(
apiKey: 'AIzaSyBqLWsqFjYAdGyihKTahMRDQMo0N6NVjAs',
appId: '1:963656261848:ios:d9e01cfe8b675dfcb237ad',
messagingSenderId: '963656261848',
projectId: 'flutterfire-ui-codelab',
storageBucket: 'flutterfire-ui-codelab.firebasestorage.app',
iosClientId: '963656261848-v7r3vq1v6haupv0l1mdrmsf56ktnua60.apps.googleusercontent.com',
iosBundleId: 'com.example.complete',
);
static const FirebaseOptions macos = FirebaseOptions(
apiKey: 'AIzaSyBqLWsqFjYAdGyihKTahMRDQMo0N6NVjAs',
appId: '1:963656261848:ios:d9e01cfe8b675dfcb237ad',
messagingSenderId: '963656261848',
projectId: 'flutterfire-ui-codelab',
storageBucket: 'flutterfire-ui-codelab.firebasestorage.app',
iosClientId: '963656261848-v7r3vq1v6haupv0l1mdrmsf56ktnua60.apps.googleusercontent.com',
iosBundleId: 'com.example.complete',
);
}
ב-Firebase, המילה 'אפליקציה' מתייחסת לגרסה ספציפית לפלטפורמה ספציפית בפרויקט Firebase. לדוגמה, בפרויקט Firebase שנקרא FlutterFire-ui-codelab יש כמה אפליקציות: אחת ל-Android, אחת ל-iOS, אחת ל-macOS ואחת לאינטרנט.
השיטה DefaultFirebaseOptions.currentPlatform
משתמשת ב-enum TargetPlatform
שנחשף על ידי Flutter כדי לזהות את הפלטפורמה שבה האפליקציה פועלת, ואז מחזירה את ערכי ההגדרה של Firebase שנדרשים לאפליקציית Firebase הנכונה.
הוספת חבילות Firebase לאפליקציית Flutter
בשלב האחרון בהגדרה מוסיפים את חבילות Firebase הרלוונטיות לפרויקט Flutter. צריכות להיות שגיאות בקובץ firebase_options.dart
, כי הוא מסתמך על חבילות Firebase שעדיין לא נוספו. במסוף, מוודאים שאתם נמצאים בתיקיית הבסיס של פרויקט Flutter בנתיב flutter-codelabs/firebase-emulator-suite/start
. לאחר מכן, מריצים את שלוש הפקודות הבאות:
flutter pub add firebase_core firebase_auth firebase_ui_auth
אלה החבילות היחידות שאתם צריכים בשלב הזה.
הפעלה של Firebase
כדי להשתמש בחבילות שנוספו, צריך לעדכן את הקוד בפונקציה main
בקובץ main.dart
.DefaultFirebaseOptions.currentPlatform,
lib/main.dart
import 'package:firebase_core/firebase_core.dart'; // Add this import
import 'package:flutter/material.dart';
import 'app.dart';
import 'firebase_options.dart'; // And this import
// TODO(codelab user): Get API key
const clientId = 'YOUR_CLIENT_ID';
void main() async {
// Add from here...
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
// To here.
runApp(const MyApp(clientId: clientId));
}
הקוד הזה עושה שני דברים.
-
WidgetsFlutterBinding.ensureInitialized()
אומר ל-Flutter לא להתחיל להריץ את קוד הווידג'ט של האפליקציה עד שהמסגרת של Flutter תופעל באופן מלא. Firebase משתמש בערוצים של פלטפורמות מקוריות, שנדרש להפעיל את המסגרת כדי להשתמש בהם. -
Firebase.initializeApp
מגדיר חיבור בין אפליקציית Flutter לפרויקט Firebase. ה-DefaultFirebaseOptions.currentPlatform
מיובא מקובץ ה-firebase_options.dart
שנוצר. הערך הסטטי הזה מזהה את הפלטפורמה שבה אתם מפעילים את התכונה, ומעביר את המפתחות המתאימים של Firebase.
4. הוספת דף ראשוני של Firebase UI Auth
ב-Firebase UI for Auth יש ווידג'טים שמייצגים מסכים שלמים באפליקציה. המסכים האלה מטפלים בתהליכי אימות שונים באפליקציה, כמו כניסה, הרשמה, שחזור סיסמה, פרופיל משתמש ועוד. כדי להתחיל, מוסיפים לאפליקציה דף נחיתה שמשמש כאמצעי הגנה לאימות הכניסה לאפליקציה הראשית.
אפליקציית Material או Cupertino
ממשק המשתמש של FlutterFire מחייב שהאפליקציה תהיה עטופה ב-MaterialApp
או ב-CupertinoApp
. בהתאם לבחירה שלכם, ממשק המשתמש ישקף באופן אוטומטי את ההבדלים בין ווידג'טים של Material או של Cupertino. ב-codelab הזה משתמשים ב-MaterialApp
, שכבר נוסף לאפליקציה ב-app.dart
.
lib/app.dart
import 'package:flutter/material.dart';
import 'auth_gate.dart';
class MyApp extends StatelessWidget {
const MyApp({super.key, required this.clientId});
final String clientId;
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
),
home: AuthGate(clientId: clientId),
);
}
}
בדיקת מצב האימות
לפני שמציגים מסך כניסה, צריך לקבוע אם המשתמש עבר אימות. הדרך הנפוצה ביותר לבדוק את זה היא להאזין ל-FirebaseAuth
של authStateChanges
באמצעות התוסף Firebase Auth.
בדוגמת הקוד שלמעלה, הפונקציה MaterialApp
יוצרת את הווידג'ט AuthGate
בשיטה build
שלה. (זהו ווידג'ט בהתאמה אישית, שלא מסופק על ידי FlutterFire UI).
צריך לעדכן את הווידג'ט כדי לכלול את השידור של authStateChanges
.
ה-API authStateChanges
מחזיר Stream
עם המשתמש הנוכחי (אם הוא מחובר), או null אם הוא לא מחובר. כדי להירשם למצב הזה באפליקציה שלנו, אפשר להשתמש בווידג'ט StreamBuilder של Flutter ולהעביר אליו את הזרם.
StreamBuilder
הוא ווידג'ט שנוצר על סמך תמונת המצב האחרונה של הנתונים ממקור נתונים שמעבירים לו. הוא נבנה מחדש באופן אוטומטי כש-Stream
יוצר תמונת מצב חדשה.
מעדכנים את הקוד ב-auth_gate.dart
.
lib/auth_gate.dart
import 'package:firebase_auth/firebase_auth.dart' hide EmailAuthProvider; // Add this import
import 'package:firebase_ui_auth/firebase_ui_auth.dart'; // And this import
import 'package:flutter/material.dart';
import 'home.dart';
class AuthGate extends StatelessWidget {
const AuthGate({super.key, required this.clientId});
final String clientId;
@override
Widget build(BuildContext context) {
return StreamBuilder<User?>( // Modify from here...
stream: FirebaseAuth.instance.authStateChanges(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return SignInScreen(providers: []);
}
return const HomeScreen();
},
); // To here.
}
}
-
StreamBuilder.stream
מועברFirebaseAuth.instance.authStateChanged
, הסטרים שצוין למעלה, שיחזיר אובייקט FirebaseUser
אם המשתמש עבר אימות, אחרת הוא יחזירnull
. - לאחר מכן, הקוד משתמש ב-
snapshot.hasData
כדי לבדוק אם הערך מהזרם מכיל את האובייקטUser
. - אם אין כזה, יוחזר ווידג'ט
SignInScreen
. בשלב הזה, המסך הזה לא יעשה כלום, הוא יעודכן בשלב הבא. - אחרת, הפונקציה מחזירה
HomeScreen
, שהוא החלק העיקרי של האפליקציה שרק משתמשים מאומתים יכולים לגשת אליו.
SignInScreen
הוא ווידג'ט שמגיע מחבילת FlutterFire UI. זה יהיה הנושא של השלב הבא ב-codelab הזה. אם מריצים את האפליקציה בשלב הזה, אמור להופיע מסך כניסה ריק.
5. מסך הכניסה
הווידג'ט SignInScreen
, שסופק על ידי FlutterFire UI, מוסיף את הפונקציונליות הבאה:
- המשתמשים יכולים להיכנס
- אם משתמשים שכחו את הסיסמה שלהם, הם יכולים להקיש על 'שכחת את הסיסמה?' ולעבור לטופס לאיפוס הסיסמה.
- אם המשתמש עדיין לא רשום, הוא יכול להקיש על 'הרשמה' ולעבור לטופס אחר שבו הוא יכול להירשם.
שוב, צריך רק כמה שורות קוד. נזכרים בקוד בווידג'ט AuthGate
:
lib/auth_gate.dart
import 'package:firebase_auth/firebase_auth.dart' hide EmailAuthProvider;
import 'package:firebase_ui_auth/firebase_ui_auth.dart';
import 'package:flutter/material.dart';
import 'home.dart';
class AuthGate extends StatelessWidget {
const AuthGate({super.key, required this.clientId});
final String clientId;
@override
Widget build(BuildContext context) {
return StreamBuilder<User?>(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return SignInScreen(providers: [EmailAuthProvider()]); // Modify this line
}
return const HomeScreen();
},
);
}
}
הווידג'ט SignInScreen
והארגומנט providers
שלו הם הקוד היחיד שנדרש כדי לקבל את כל הפונקציונליות שצוינה למעלה. עכשיו אמור להופיע מסך כניסה עם שדות להזנת כתובת אימייל וסיסמה, וגם לחצן 'כניסה'.
הוא פועל, אבל חסר בו עיצוב. הווידג'ט חושף פרמטרים להתאמה אישית של המראה של מסך הכניסה. לדוגמה, אפשר להוסיף את הלוגו של החברה.
התאמה אישית של מסך הכניסה
headerBuilder
באמצעות הארגומנט SignInScreen.headerBuilder
, אפשר להוסיף אילו ווידג'טים שרוצים מעל טופס הכניסה. הווידג'ט הזה מוצג רק במסכים צרים, כמו במכשירים ניידים. במסכים רחבים, אפשר להשתמש ב-SignInScreen.sideBuilder
, שמוסבר בהמשך במעבדת התכנות הזו.
מעדכנים את הקובץ lib/auth_gate.dart
באמצעות הקוד הזה:
lib/auth_gate.dart
import 'package:firebase_auth/firebase_auth.dart' hide EmailAuthProvider;
import 'package:firebase_ui_auth/firebase_ui_auth.dart';
import 'package:flutter/material.dart';
import 'home.dart';
class AuthGate extends StatelessWidget {
const AuthGate({super.key, required this.clientId});
final String clientId;
@override
Widget build(BuildContext context) {
return StreamBuilder<User?>(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return SignInScreen( // Modify from here...
providers: [EmailAuthProvider()],
headerBuilder: (context, constraints, shrinkOffset) {
return Padding(
padding: const EdgeInsets.all(20),
child: AspectRatio(
aspectRatio: 1,
child: Image.asset('assets/flutterfire_300x.png'),
),
);
},
); // To here.
}
return const HomeScreen();
},
);
}
}```
The headerBuilder argument requires a function of the type HeaderBuilder, which
is defined in the FlutterFire UI package.
```dart
typedef HeaderBuilder = Widget Function(
BuildContext context,
BoxConstraints constraints,
double shrinkOffset,
);
מכיוון שמדובר בקריאה חוזרת (callback), היא חושפת ערכים שאפשר להשתמש בהם, כמו BuildContext
ו-BoxConstraints
, ומחייבת להחזיר ווידג'ט. הווידג'ט שתחזירו יוצג בחלק העליון של המסך. בדוגמה הזו, הקוד החדש מוסיף תמונה לחלק העליון של המסך. האפליקציה אמורה להיראות עכשיו כך.
כלי ליצירת כתוביות
במסך הכניסה מוצגים שלושה פרמטרים נוספים שמאפשרים להתאים אישית את המסך: subtitleBuilder
, footerBuilder
ו-sideBuilder
.
הפונקציה subtitleBuilder
שונה מעט, כי הארגומנטים של הקריאה החוזרת כוללים פעולה, שהסוג שלה הוא AuthAction
. AuthAction
הוא enum שאפשר להשתמש בו כדי לזהות אם המסך שבו המשתמש נמצא הוא מסך הכניסה או מסך ההרשמה.
מעדכנים את הקוד בקובץ auth_gate.dart כדי להשתמש ב-subtitleBuilder
.
lib/auth_gate.dart
import 'package:firebase_auth/firebase_auth.dart' hide EmailAuthProvider;
import 'package:firebase_ui_auth/firebase_ui_auth.dart';
import 'package:flutter/material.dart';
import 'home.dart';
class AuthGate extends StatelessWidget {
const AuthGate({super.key, required this.clientId});
final String clientId;
@override
Widget build(BuildContext context) {
return StreamBuilder<User?>(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return SignInScreen(
providers: [EmailAuthProvider()],
headerBuilder: (context, constraints, shrinkOffset) {
return Padding(
padding: const EdgeInsets.all(20),
child: AspectRatio(
aspectRatio: 1,
child: Image.asset('assets/flutterfire_300x.png'),
),
);
},
subtitleBuilder: (context, action) { // Add from here...
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: action == AuthAction.signIn
? const Text('Welcome to FlutterFire, please sign in!')
: const Text('Welcome to Flutterfire, please sign up!'),
);
}, // To here.
);
}
return const HomeScreen();
},
);
}
}
כלי ליצירת כותרות תחתונות
הארגומנט footerBuilder זהה לארגומנט subtitleBuilder. הוא לא חושף את BoxConstraints
או shrinkOffset
, כי הוא מיועד לטקסט ולא לתמונות. כמובן שאפשר להוסיף כל ווידג'ט שרוצים.
מוסיפים כותרת תחתונה למסך הכניסה באמצעות הקוד הזה.
lib/auth_gate.dart
import 'package:firebase_auth/firebase_auth.dart' hide EmailAuthProvider;
import 'package:firebase_ui_auth/firebase_ui_auth.dart';
import 'package:flutter/material.dart';
import 'home.dart';
class AuthGate extends StatelessWidget {
const AuthGate({super.key, required this.clientId});
final String clientId;
@override
Widget build(BuildContext context) {
return StreamBuilder<User?>(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return SignInScreen(
providers: [EmailAuthProvider()],
headerBuilder: (context, constraints, shrinkOffset) {
return Padding(
padding: const EdgeInsets.all(20),
child: AspectRatio(
aspectRatio: 1,
child: Image.asset('assets/flutterfire_300x.png'),
),
);
},
subtitleBuilder: (context, action) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: action == AuthAction.signIn
? const Text('Welcome to FlutterFire, please sign in!')
: const Text('Welcome to Flutterfire, please sign up!'),
);
},
footerBuilder: (context, action) { // Add from here...
return const Padding(
padding: EdgeInsets.only(top: 16),
child: Text(
'By signing in, you agree to our terms and conditions.',
style: TextStyle(color: Colors.grey),
),
);
}, // To here.
);
}
return const HomeScreen();
},
);
}
}
Side Builder
הארגומנט SignInScreen.sidebuilder מקבל קריאה חוזרת, והפעם הארגומנטים של הקריאה החוזרת הם BuildContext
ו-double shrinkOffset
. הווידג'ט שמוחזר על ידי sideBuilder
יוצג מימין לטופס הכניסה, ורק במסכים רחבים. בפועל, המשמעות היא שהווידג'ט יוצג רק במחשבים ובאפליקציות אינטרנט.
באופן פנימי, ממשק המשתמש של FlutterFire משתמש בנקודת עצירה כדי לקבוע אם להציג את תוכן הכותרת (במסכים אנכיים, כמו בנייד) או את התוכן הצדדי (במסכים רחבים, במחשב או באינטרנט). במילים אחרות, אם רוחב המסך הוא יותר מ-800 פיקסלים, מוצג התוכן של סרגל הצד של הכלי לבניית אתרים, ולא מוצג התוכן של הכותרת. אם רוחב המסך קטן מ-800 פיקסלים, המצב הפוך.
מעדכנים את הקוד בקובץ auth_gate.dart כדי להוסיף רכיבי sideBuilder
.
lib/auth_gate.dart
import 'package:firebase_auth/firebase_auth.dart' hide EmailAuthProvider;
import 'package:firebase_ui_auth/firebase_ui_auth.dart';
import 'package:flutter/material.dart';
import 'home.dart';
class AuthGate extends StatelessWidget {
const AuthGate({super.key, required this.clientId});
final String clientId;
@override
Widget build(BuildContext context) {
return StreamBuilder<User?>(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return SignInScreen(
providers: [EmailAuthProvider()],
headerBuilder: (context, constraints, shrinkOffset) {
return Padding(
padding: const EdgeInsets.all(20),
child: AspectRatio(
aspectRatio: 1,
child: Image.asset('assets/flutterfire_300x.png'),
),
);
},
subtitleBuilder: (context, action) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: action == AuthAction.signIn
? const Text('Welcome to FlutterFire, please sign in!')
: const Text('Welcome to Flutterfire, please sign up!'),
);
},
footerBuilder: (context, action) {
return const Padding(
padding: EdgeInsets.only(top: 16),
child: Text(
'By signing in, you agree to our terms and conditions.',
style: TextStyle(color: Colors.grey),
),
);
},
sideBuilder: (context, shrinkOffset) {
return Padding(
padding: const EdgeInsets.all(20),
child: AspectRatio(
aspectRatio: 1,
child: Image.asset('flutterfire_300x.png'),
),
);
},
);
}
return const HomeScreen();
},
);
}
}
עכשיו האפליקציה אמורה להיראות כך כשמרחיבים את רוחב החלון (אם משתמשים ב-Flutter web או ב-MacOS).
יצירת משתמש
בשלב הזה, כל הקוד של המסך הזה מוכן. אבל לפני שתוכלו להיכנס, תצטרכו ליצור משתמש. אפשר לעשות את זה במסך 'הרשמה', או ליצור משתמש במסוף Firebase.
כדי להשתמש במסוף:
- עוברים אל הטבלה 'משתמשים' במסוף Firebase. בוחרים באפשרות flutterfire-ui-codelab או בפרויקט אחר אם השתמשתם בשם אחר. תופיע הטבלה הזו:
- לוחצים על הלחצן 'הוספת משתמש'.
- מזינים כתובת אימייל וסיסמה למשתמש החדש. יכול להיות שמדובר בכתובת אימייל ובסיסמה מזויפות, כמו שמופיע בתמונה שלמטה. זה יעבוד, אבל אם תשתמשו בכתובת אימייל מזויפת, לא תוכלו להשתמש באפשרות 'שכחתי את הסיסמה'.
- לוחצים על 'הוספת משתמש'
עכשיו אפשר לחזור לאפליקציית Flutter ולהיכנס לחשבון של משתמש באמצעות דף הכניסה. האפליקציה אמורה להיראות כך:
6. מסך הפרופיל
בנוסף, FlutterFire UI מספק ווידג'ט ProfileScreen
, שמאפשר לכם להשתמש בהרבה פונקציות בכמה שורות קוד.
הוספת הווידג'ט ProfileScreen
עוברים לקובץ home.dart
בכלי לעריכת טקסט. מעדכנים אותו באמצעות הקוד הזה:
lib/home.dart
import 'package:firebase_ui_auth/firebase_ui_auth.dart';
import 'package:flutter/material.dart';
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: [
IconButton(
icon: const Icon(Icons.person),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute<ProfileScreen>(
builder: (context) => const ProfileScreen(),
),
);
},
),
],
automaticallyImplyLeading: false,
),
body: Center(
child: Column(
children: [
SizedBox(width: 250, child: Image.asset('assets/dash.png')),
Text('Welcome!', style: Theme.of(context).textTheme.displaySmall),
const SignOutButton(),
],
),
),
);
}
}
הקוד החדש של ההערה הוא הקריאה החוזרת שמועברת לשיטה IconButton.isPressed
. כשלוחצים על IconButton
, האפליקציה יוצרת מסלול אנונימי חדש ועוברת אליו. במסלול הזה יוצג הווידג'ט ProfileScreen
, שמוחזר מהקריאה החוזרת MaterialPageRoute.builder
.
טוענים מחדש את האפליקציה, לוחצים על הסמל בפינה השמאלית העליונה (בסרגל האפליקציות) ומוצג דף כמו זה:
זהו ממשק המשתמש הרגיל שמופיע בדף FlutterFire UI. כל הכפתורים ושדות הטקסט מחוברים ל-Firebase Auth ופועלים באופן מיידי. לדוגמה, אפשר להזין שם בשדה הטקסט 'שם', וממשק המשתמש של FlutterFire יקרא לשיטה FirebaseAuth.instance.currentUser?.updateDisplayName
, שתשמור את השם הזה ב-Firebase.
יציאה
בשלב הזה, אם תלחצו על הלחצן 'יציאה מהחשבון', לא יחול שינוי באפליקציה. תצאו מהחשבון, אבל לא תועברו חזרה לווידג'ט AuthGate. כדי להטמיע את זה, משתמשים בפרמטר ProfileScreen.actions.
קודם כול, מעדכנים את הקוד בקובץ home.dart.
lib/home.dart
import 'package:firebase_ui_auth/firebase_ui_auth.dart';
import 'package:flutter/material.dart';
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: [
IconButton(
icon: const Icon(Icons.person),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute<ProfileScreen>(
builder: (context) => ProfileScreen(
actions: [
SignedOutAction((context) {
Navigator.of(context).pop();
}),
],
),
),
);
},
),
],
automaticallyImplyLeading: false,
),
body: Center(
child: Column(
children: [
SizedBox(width: 250, child: Image.asset('assets/dash.png')),
Text('Welcome!', style: Theme.of(context).textTheme.displaySmall),
const SignOutButton(),
],
),
),
);
}
}
עכשיו, כשיוצרים מופע של ProfileScreen
, מעבירים לו גם רשימה של פעולות לארגומנט ProfileScreen.actions
. הפעולות האלה הן מסוג FlutterFireUiAction
. יש הרבה מחלקות שונות שהן תת-סוגים של FlutterFireUiAction
, ובאופן כללי משתמשים בהן כדי להגדיר לאפליקציה להגיב לשינויים שונים בסטטוס האימות. הפונקציה SignedOutAction קוראת לפונקציית קריאה חוזרת שאתם מציינים לה כשמצב האימות של Firebase משתנה למצב שבו currentUser הוא null.
אם מוסיפים פונקציית callback שקוראת ל-Navigator.of(context).pop()
כש-SignedOutAction
מופעל, האפליקציה תעבור לדף הקודם. באפליקציה לדוגמה הזו יש רק נתיב קבוע אחד, שמוצג בו מסך הכניסה אם אין משתמש מחובר, ודף הבית אם יש משתמש מחובר. הפעולה הזו מתרחשת כשהמשתמש מתנתק מהחשבון, ולכן באפליקציה יוצג מסך הכניסה.
התאמה אישית של דף הפרופיל
בדומה למסך הכניסה, אפשר להתאים אישית את דף הפרופיל. קודם כל, בדף הנוכחי אין אפשרות לנווט חזרה לדף הבית אחרי שהמשתמש מגיע לדף הפרופיל. כדי לפתור את הבעיה, צריך להוסיף AppBar לווידג'ט ProfileScreen.
lib/home.dart
import 'package:firebase_ui_auth/firebase_ui_auth.dart';
import 'package:flutter/material.dart';
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: [
IconButton(
icon: const Icon(Icons.person),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute<ProfileScreen>(
builder: (context) => ProfileScreen(
appBar: AppBar(title: const Text('User Profile')),
actions: [
SignedOutAction((context) {
Navigator.of(context).pop();
}),
],
),
),
);
},
),
],
automaticallyImplyLeading: false,
),
body: Center(
child: Column(
children: [
SizedBox(width: 250, child: Image.asset('assets/dash.png')),
Text('Welcome!', style: Theme.of(context).textTheme.displaySmall),
const SignOutButton(),
],
),
),
);
}
}
הארגומנט ProfileScreen.appBar
מקבל ווידג'ט AppBar
מחבילת Flutter Material, כך שאפשר להתייחס אליו כמו לכל ווידג'ט AppBar
אחר שבניתם והעברתם ל-Scaffold
. בדוגמה הזו, הפונקציונליות שמוגדרת כברירת מחדל של הוספת לחצן 'חזרה' באופן אוטומטי נשמרת, ועכשיו יש למסך כותרת.
הוספת ילדים למסך הפרופיל
לווידג'ט ProfileScreen
יש גם ארגומנט אופציונלי בשם children. הארגומנט הזה מקבל רשימה של ווידג'טים, והווידג'טים האלה יוצבו בצורה אנכית בתוך ווידג'ט Column
שכבר נמצא בשימוש פנימי כדי ליצור את ProfileScreen
. ווידג'ט Column
זה בשיטת הבנייה ProfileScreen
יציב את רכיבי הצאצא שמעבירים אליו מעל הכפתור 'יציאה מהחשבון'.
מעדכנים את הקוד ב-home.dart
כדי שהלוגו של החברה יוצג כאן, בדומה למסך הכניסה.
lib/home.dart
import 'package:firebase_ui_auth/firebase_ui_auth.dart';
import 'package:flutter/material.dart';
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: [
IconButton(
icon: const Icon(Icons.person),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute<ProfileScreen>(
builder: (context) => ProfileScreen(
appBar: AppBar(title: const Text('User Profile')),
actions: [
SignedOutAction((context) {
Navigator.of(context).pop();
}),
],
children: [
const Divider(),
Padding(
padding: const EdgeInsets.all(2),
child: AspectRatio(
aspectRatio: 1,
child: Image.asset('flutterfire_300x.png'),
),
),
],
),
),
);
},
),
],
automaticallyImplyLeading: false,
),
body: Center(
child: Column(
children: [
SizedBox(width: 250, child: Image.asset('assets/dash.png')),
Text('Welcome!', style: Theme.of(context).textTheme.displaySmall),
const SignOutButton(),
],
),
),
);
}
}
טוענים מחדש את האפליקציה, והמסך הבא יופיע:
7. כניסה באמצעות אימות גוגל במגוון פלטפורמות
ב-FlutterFire UI יש גם ווידג'טים ופונקציות לאימות באמצעות ספקי צד שלישי, כמו Google, Twitter, Facebook, Apple ו-GitHub.
כדי לשלב אימות של Google, צריך להתקין את הפלאגין הרשמי firebase_ui_oauth_google ואת התלות שלו, שיטפלו בתהליך האימות המקורי. בטרמינל, עוברים אל ספריית הבסיס של פרויקט Flutter ומזינים את הפקודה הבאה:
flutter pub add google_sign_in firebase_ui_oauth_google
הפעלת ספק הכניסה באמצעות חשבון Google
לאחר מכן, מפעילים את ספק Google במסוף Firebase:
- עוברים למסך Authentication sign-in providers במסוף.
- לוחצים על 'הוספת ספק חדש'.
- בוחרים באפשרות Google.
- מחליפים את מצב המתג 'הפעלה' ומקישים על 'שמירה'.
- אם מופיע חלון קופץ עם מידע על הורדת קובצי הגדרה, לוחצים על 'סיום'.
- מוודאים שספק הכניסה באמצעות חשבון Google נוסף.
הוספת כפתור לכניסה באמצעות חשבון Google
אחרי שמפעילים את הכניסה באמצעות חשבון Google, מוסיפים את הווידג'ט שנדרש כדי להציג במסך הכניסה לחצן כניסה מעוצב באמצעות חשבון Google. עוברים לקובץ auth_gate.dart
ומעדכנים את הקוד כך שיהיה זהה לקוד הבא:
lib/auth_gate.dart
import 'package:firebase_auth/firebase_auth.dart' hide EmailAuthProvider;
import 'package:firebase_ui_auth/firebase_ui_auth.dart';
import 'package:firebase_ui_oauth_google/firebase_ui_oauth_google.dart'; // Add this import
import 'package:flutter/material.dart';
import 'home.dart';
class AuthGate extends StatelessWidget {
const AuthGate({super.key, required this.clientId});
final String clientId;
@override
Widget build(BuildContext context) {
return StreamBuilder<User?>(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return SignInScreen(
providers: [
EmailAuthProvider(),
GoogleProvider(clientId: clientId), // Add this line
],
headerBuilder: (context, constraints, shrinkOffset) {
return Padding(
padding: const EdgeInsets.all(20),
child: AspectRatio(
aspectRatio: 1,
child: Image.asset('assets/flutterfire_300x.png'),
),
);
},
subtitleBuilder: (context, action) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: action == AuthAction.signIn
? const Text('Welcome to FlutterFire, please sign in!')
: const Text('Welcome to Flutterfire, please sign up!'),
);
},
footerBuilder: (context, action) {
return const Padding(
padding: EdgeInsets.only(top: 16),
child: Text(
'By signing in, you agree to our terms and conditions.',
style: TextStyle(color: Colors.grey),
),
);
},
sideBuilder: (context, shrinkOffset) {
return Padding(
padding: const EdgeInsets.all(20),
child: AspectRatio(
aspectRatio: 1,
child: Image.asset('flutterfire_300x.png'),
),
);
},
);
}
return const HomeScreen();
},
);
}
}
הקוד החדש היחיד כאן הוא התוספת של GoogleProvider(clientId: "YOUR_WEBCLIENT_ID")
להגדרת הווידג'ט SignInScreen
.
אחרי שמוסיפים את הקוד, טוענים מחדש את האפליקציה ומופיע לחצן הכניסה באמצעות חשבון Google.
הגדרת כפתור הכניסה
הלחצן לא פועל בלי הגדרה נוספת. אם אתם מפתחים באמצעות Flutter Web, זה השלב היחיד שצריך להוסיף כדי שהתכונה הזו תפעל. בפלטפורמות אחרות נדרשים שלבים נוספים, שנסביר עליהם בהמשך.
- עוברים לדף 'ספקי אימות' במסוף Firebase.
- לוחצים על ספק Google.
- לוחצים על חלונית ההרחבה 'הגדרת Web SDK'.
- מעתיקים את הערך מ-Web client ID (מזהה לקוח אינטרנט).
- חוזרים לעורך הטקסט ומעדכנים את המופע של
GoogleProvider
בקובץauth_gate.dart
על ידי העברת המזהה הזה לפרמטר בעל השםclientId
.
GoogleProvider(
clientId: "YOUR_WEBCLIENT_ID"
)
אחרי שמזינים את מזהה הלקוח של האתר, צריך לטעון מחדש את האפליקציה. כשלוחצים על הלחצן 'כניסה באמצעות חשבון Google', ייפתח חלון חדש (אם משתמשים באתר) עם הסבר על תהליך הכניסה לחשבון Google. בהתחלה, הוא נראה כך:
הגדרת iOS
כדי שהאפשרות הזו תפעל ב-iOS, צריך לבצע תהליך הגדרה נוסף.
- עוברים למסך הגדרות הפרויקט במסוף Firebase. יוצג כרטיס עם רשימה של האפליקציות שלכם ב-Firebase, שייראה כך:
- בוחרים באפשרות iOS. שימו לב שהשם של האפליקציה שלכם יהיה שונה מהשם שמופיע בצילום המסך. אם השתמשתם בפרויקט
flutter-codelabs/firebase-auth-flutterfire-ui/start
כדי לעקוב אחרי ה-codelab הזה, בצילום המסך כתוב 'השלמה', אבל אצלכם יהיה כתוב 'התחלה'. - לוחצים על הלחצן
GoogleServices-Info.plist
כדי להוריד את קובץ ההגדרות שנדרש. - גוררים את הקובץ שהורד לתיקייה בשם
/ios/Runner
בפרויקט Flutter. - פותחים את Xcode על ידי הפעלת פקודת הטרמינל הבאה מהשורש של הפרויקט:
open ios/Runner.xcworkspace
- לוחצים לחיצה ימנית על ספריית Runner ובוחרים באפשרות Add Files to "Runner" (הוספת קבצים ל-Runner).
- בוחרים באפשרות
GoogleService-Info.plist
במנהל הקבצים. - חוזרים לעורך הטקסט (שאינו Xcode) ומוסיפים את מאפייני
CFBundleURLTypes
שבהמשך לקובץios/Runner/Info.plist
.<!-- Put me in the [my_project]/ios/Runner/Info.plist file --> <!-- Google Sign-in Section --> <key>CFBundleURLTypes</key> <array> <dict> <key>CFBundleTypeRole</key> <string>Editor</string> <key>CFBundleURLSchemes</key> <array> <!-- TODO Replace this value: --> <!-- Copied from GoogleService-Info.plist key REVERSED_CLIENT_ID --> <string>com.googleusercontent.apps.861823949799-vc35cprkp249096uujjn0vvnmcvjppkn</string> </array> </dict> </array> <!-- End of the Google Sign-in Section -->
- צריך להחליף את
GoogleProvider.clientId
שהוספתם בהגדרות לאתר במזהה הלקוח שמשויך למזהה הלקוח שלכם ב-Firebase ל-iOS. קודם כל, אפשר למצוא את המזהה הזה בקובץfirebase_options.dart
כחלק מהקבועiOS
. מעתיקים את הערך שמועבר אלiOSClientId
.static const FirebaseOptions ios = FirebaseOptions( apiKey: 'YOUR API KEY', appId: 'YOUR APP ID', messagingSenderId: '', projectId: 'PROJECT_ID', storageBucket: 'PROJECT_ID.firebasestorage.app', iosClientId: 'IOS CLIENT ID', // Find your iOS client Id here. iosBundleId: 'com.example.BUNDLE', );
- מדביקים את הערך הזה במשתנה
clientId
בקובץlib/main.dart
.
lib/main.dart
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'app.dart';
import 'firebase_options.dart';
const clientId = 'YOUR_CLIENT_ID'; // Replace this value with your Client ID.
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
runApp(const MyApp(clientId: clientId));
}
אם אפליקציית Flutter שלכם כבר פועלת ב-iOS, אתם צריכים לסגור אותה לגמרי ואז להפעיל אותה מחדש. אם לא, מפעילים את האפליקציה ב-iOS.
8. כל הכבוד!
סיימתם את ה-Codelab בנושא ממשק המשתמש של Firebase Auth ל-Flutter . אפשר למצוא את הקוד המלא של ה-Codelab הזה בספרייה firebase-auth-flutterfire-ui/complete
ב-GitHub.
מה נכלל
- הגדרת אפליקציית Flutter לשימוש ב-Firebase
- הגדרת פרויקט Firebase במסוף Firebase
- FlutterFire CLI
- Firebase CLI
- שימוש באימות ב-Firebase
- שימוש ב-FlutterFire UI לטיפול באימות ב-Firebase באפליקציית Flutter
השלבים הבאים
- מידע נוסף על שימוש ב-Firestore ובאימות ב-Flutter: הכרת Firebase ל-Flutter
- כדאי לעיין בכלים אחרים של Firebase לבניית אפליקציית Flutter:
מידע נוסף
- אתר Firebase: firebase.google.com
- אתר Flutter: flutter.dev
- רכיבי widget של Firebase Flutter: firebase.flutter.dev
- ערוץ YouTube של Firebase
- ערוץ YouTube של Flutter
ספארקי הגיע לחגוג איתכם!