1. قبل أن تبدأ
في مختبر الأكواد هذا ، ستتعلم بعض أساسيات Firebase لإنشاء تطبيقات Flutter للأجهزة المحمولة لنظامي Android و iOS.
المتطلبات الأساسية
- الإلمام برفرفة
- رفرفة SDK
- محرر نصوص من اختيارك
ماذا ستتعلم
- كيفية بناء حدث RSVP وتطبيق دردشة سجل الزوار على Android و iOS والويب و macOS باستخدام Flutter.
- كيفية مصادقة المستخدمين باستخدام مصادقة Firebase ومزامنة البيانات مع Firestore.
ماذا ستحتاج
أي من الأجهزة التالية:
- جهاز Android أو iOS فعلي متصل بجهاز الكمبيوتر الخاص بك ويتم ضبطه على وضع المطور.
- محاكي iOS (يتطلب أدوات Xcode ).
- محاكي Android (يتطلب الإعداد في Android Studio ).
تحتاج أيضًا إلى ما يلي:
- متصفح من اختيارك ، مثل Google Chrome.
- IDE أو محرر نصوص من اختيارك تم تكوينه باستخدام الإضافات Dart و Flutter ، مثل Android Studio أو Visual Studio Code .
- أحدث إصدار
stable
من Flutter أوbeta
إذا كنت تستمتع بالعيش على الحافة. - حساب Google لإنشاء مشروع Firebase وإدارته.
- قام
Firebase
CLI بتسجيل الدخول إلى حساب Google الخاص بك.
2. احصل على نموذج التعليمات البرمجية
قم بتنزيل الإصدار الأولي لمشروعك من GitHub:
- من سطر الأوامر ، قم باستنساخ مستودع GitHub في دليل
flutter-codelabs
:
git clone https://github.com/flutter/codelabs.git flutter-codelabs
يحتوي دليل flutter-codelabs
على رمز مجموعة من مختبرات الرموز. الكود الخاص بمعمل الرموز هذا موجود في دليل 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 المفضل لديك. يحتوي هذا الدليل على الكود المبدئي لمختبر الرموز ، والذي يتكون من تطبيق لقاء Flutter غير فعال بعد.
حدد الملفات التي تحتاج إلى عمل
تنتشر التعليمات البرمجية في هذا التطبيق على عدة أدلة. هذا التقسيم في الوظائف يجعل العمل أسهل لأنه يجمع الكود حسب الوظيفة.
- حدد موقع الملفات التالية:
-
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 .
- في وحدة التحكم ، انقر فوق إضافة مشروع أو إنشاء مشروع .
- في حقل اسم المشروع ، أدخل Firebase-Flutter-Codelab ثم انقر فوق متابعة .
- انقر من خلال خيارات إنشاء المشروع. إذا طُلب منك ذلك ، اقبل شروط Firebase ، لكن تخطي إعداد Google Analytics لأنك لن تستخدمه لهذا التطبيق.
لمعرفة المزيد حول مشاريع Firebase ، راجع فهم مشاريع Firebase .
يستخدم التطبيق منتجات Firebase التالية المتوفرة لتطبيقات الويب:
- المصادقة: تتيح للمستخدمين تسجيل الدخول إلى تطبيقك.
- Firestore: يحفظ البيانات المنظمة على السحابة ويحصل على إشعارات فورية عندما تتغير البيانات.
- قواعد أمان Firebase: يؤمن قاعدة البيانات الخاصة بك.
تحتاج بعض هذه المنتجات إلى تكوين خاص أو تحتاج إلى تمكينها في وحدة تحكم Firebase.
تفعيل مصادقة تسجيل الدخول إلى البريد الإلكتروني
- في جزء نظرة عامة على المشروع بوحدة تحكم Firebase ، قم بتوسيع قائمة الإنشاء .
- انقر فوق المصادقة> البدء> طريقة تسجيل الدخول> البريد الإلكتروني / كلمة المرور> تمكين> حفظ .
تمكين Firestore
يستخدم تطبيق الويب Firestore لحفظ رسائل الدردشة وتلقي رسائل دردشة جديدة.
تمكين Firestore:
- في قائمة الإنشاء ، انقر على Cloud Firestore> إنشاء قاعدة بيانات .
- حدد البدء في وضع الاختبار ثم اقرأ إخلاء المسؤولية حول قواعد الأمان. يضمن وضع الاختبار أنه يمكنك الكتابة بحرية إلى قاعدة البيانات أثناء التطوير.
- انقر فوق التالي ثم حدد موقع قاعدة البيانات الخاصة بك. يمكنك استخدام الافتراضي. لا يمكنك تغيير الموقع لاحقًا.
- انقر فوق تمكين .
4. تكوين Firebase
لاستخدام Firebase مع Flutter ، يلزمك إكمال المهام التالية لتهيئة مشروع Flutter لاستخدام مكتبات FlutterFire
بشكل صحيح:
- أضف تبعيات
FlutterFire
إلى مشروعك. - سجل النظام الأساسي المطلوب في مشروع Firebase.
- قم بتنزيل ملف التكوين الخاص بالنظام الأساسي ثم قم بإضافته إلى الكود.
في دليل المستوى الأعلى لتطبيق Flutter الخاص بك ، هناك أدلة فرعية android
و ios
و macos
web
، والتي تحتوي على ملفات التكوين الخاصة بالنظام الأساسي لنظامي iOS و Android ، على التوالي.
تكوين التبعيات
تحتاج إلى إضافة مكتبات FlutterFire
لمنتجي Firebase اللذين تستخدمهما في هذا التطبيق: المصادقة و Firestore.
- من سطر الأوامر ، أضف التبعيات التالية:
$ flutter pub add firebase_core
حزمة firebase_core
هي الرمز الشائع المطلوب لجميع مكونات Firebase Flutter الإضافية.
$ flutter pub add firebase_auth
تتيح حزمة firebase_auth
التكامل مع المصادقة.
$ flutter pub add cloud_firestore
تتيح حزمة cloud_firestore
الوصول إلى تخزين بيانات Firestore.
$ flutter pub add provider
توفر حزمة firebase_ui_auth
مجموعة من الأدوات والأدوات المساعدة لزيادة سرعة المطور من خلال تدفقات المصادقة.
$ flutter pub add firebase_ui_auth
لقد أضفت الحزم المطلوبة ، ولكنك تحتاج أيضًا إلى تكوين مشاريع iOS و Android و macOS و Web runner لاستخدام Firebase بشكل مناسب. يمكنك أيضًا استخدام حزمة provider
التي تتيح فصل منطق الأعمال عن منطق العرض.
قم بتثبيت FlutterFire CLI
يعتمد FlutterFire CLI على Firebase CLI الأساسي.
- إذا لم تكن قد قمت بذلك بالفعل ، فقم بتثبيت Firebase CLI على جهازك.
- قم بتثبيت FlutterFire CLI:
$ dart pub global activate flutterfire_cli
بمجرد التثبيت ، يكون أمر flutterfire
متاحًا عالميًا.
تكوين تطبيقاتك
يستخرج CLI المعلومات من مشروع Firebase وتطبيقات المشروع المحددة لإنشاء كل التهيئة لمنصة معينة.
في جذر التطبيق الخاص بك ، قم بتشغيل أمر configure
:
$ flutterfire configure
يرشدك أمر التكوين خلال العمليات التالية:
- حدد مشروع Firebase استنادًا إلى ملف
.firebaserc
أو من Firebase Console. - تحديد الأنظمة الأساسية للتكوين ، مثل 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 / عداء / Release.entitlements
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<!-- Add the following two lines -->
<key>com.apple.security.network.client</key>
<true/>
</dict>
</plist>
لمزيد من المعلومات ، راجع دعم سطح المكتب لـ Flutter .
5. إضافة وظيفة RSVP
الآن بعد أن أضفت Firebase إلى التطبيق ، يمكنك إنشاء زر RSVP الذي يسجل الأشخاص باستخدام المصادقة . بالنسبة لنظام التشغيل Android الأصلي ، ونظام التشغيل iOS الأصلي ، والويب ، توجد حزم مصادقة 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 and 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.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,
),
primarySwatch: Colors.deepPurple,
textTheme: GoogleFonts.robotoTextTheme(
Theme.of(context).textTheme,
),
visualDensity: VisualDensity.adaptivePlatformDensity,
useMaterial3: true,
),
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
. تعد أداة المستهلك هي الطريقة المعتادة التي يمكن من خلالها استخدام حزمة provider
لإعادة إنشاء جزء من الشجرة عندما تتغير حالة التطبيق. عنصر واجهة مستخدم AuthFunc
هو عناصر واجهة تعامل تكميلية تقوم باختبارها.
اختبر تدفق المصادقة
- في التطبيق ، انقر فوق الزر RSVP لبدء
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)),
في حين أن هذا يكفي لعرض الأداة ، إلا أنه لا يكفي القيام بأي شيء مفيد. قمت بتحديث هذا الرمز قريبًا لجعله فعالاً.
معاينة التطبيق
عندما ينقر المستخدم فوق إرسال ، يتم تشغيل مقتطف الشفرة التالي. يضيف محتويات حقل إدخال الرسالة إلى مجموعة guestbook
لقاعدة البيانات. على وجه التحديد ، تضيف طريقة addMessageToGuestBook
محتوى الرسالة إلى مستند جديد بمعرف تم إنشاؤه تلقائيًا في مجموعة guestbook
.
لاحظ أن FirebaseAuth.instance.currentUser.uid
هو مرجع إلى المعرف الفريد الذي تم إنشاؤه تلقائيًا والذي توفره المصادقة لجميع المستخدمين الذين قاموا بتسجيل الدخول.
- في ملف
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({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Firebase Meetup'),
),
body: ListView(
children: <Widget>[
Image.asset('assets/codelab.png'),
const SizedBox(height: 8),
const IconAndDetail(Icons.calendar_today, 'October 30'),
const IconAndDetail(Icons.location_city, 'San Francisco'),
Consumer<ApplicationState>(
builder: (context, appState, _) => AuthFunc(
loggedIn: appState.loggedIn,
signOut: () {
FirebaseAuth.instance.signOut();
}),
),
const Divider(
height: 8,
thickness: 1,
indent: 8,
endIndent: 8,
color: Colors.grey,
),
const Header("What we'll be doing"),
const Paragraph(
'Join us for a day full of Firebase Workshops and Pizza!',
),
// Modify from here...
Consumer<ApplicationState>(
builder: (context, appState, _) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (appState.loggedIn) ...[
const Header('Discussion'),
GuestBook(
addMessage: (message) =>
appState.addMessageToGuestBook(message),
),
],
],
),
),
// ...to here.
],
),
);
}
}
لقد استبدلت السطرين اللذين أضفتهما في بداية هذه الخطوة بالتنفيذ الكامل. يمكنك مرة أخرى استخدام Consumer<ApplicationState>
لإتاحة حالة التطبيق لجزء الشجرة الذي تعرضه. يتيح لك هذا الرد على أي شخص يقوم بإدخال رسالة في واجهة المستخدم ونشرها في قاعدة البيانات. في القسم التالي ، تختبر ما إذا كانت الرسائل المضافة قد تم نشرها في قاعدة البيانات.
اختبار إرسال الرسائل
- إذا لزم الأمر ، قم بتسجيل الدخول إلى التطبيق.
- أدخل رسالة ، مثل
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;
_guestBookSubscription = FirebaseFirestore.instance
.collection('guestbook')
.orderBy('timestamp', descending: true)
.snapshots()
.listen((snapshot) {
_guestBookMessages = [];
for (final document in snapshot.docs) {
_guestBookMessages.add(
GuestBookMessage(
name: document.data()['name'] as String,
message: document.data()['text'] as String,
),
);
}
notifyListeners();
});
} else {
_loggedIn = false;
_guestBookMessages = [];
_guestBookSubscription?.cancel();
}
notifyListeners();
});
}
هذا القسم مهم لأنه المكان الذي تقوم فيه بإنشاء استعلام على مجموعة guestbook
، والتعامل مع الاشتراك وإلغاء الاشتراك في هذه المجموعة. أنت تستمع إلى الدفق ، حيث تقوم بإعادة بناء ذاكرة تخزين مؤقت محلية للرسائل الموجودة في مجموعة guestbook
وأيضًا تخزين مرجع لهذا الاشتراك بحيث يمكنك إلغاء الاشتراك منه لاحقًا. هناك الكثير مما يحدث هنا ، لذا يجب عليك استكشافه في مصحح الأخطاء لفحص ما يحدث للحصول على نموذج عقلي أوضح. لمزيد من المعلومات ، راجع الحصول على تحديثات في الوقت الفعلي باستخدام 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
_GuestBookState createState() => _GuestBookState();
}
- في
_GuestBookState
، عدّل طريقةbuild
على النحو التالي لفضح هذا التكوين:
lib / guest_book.dart
class _GuestBookState extends State<GuestBook> {
final _formKey = GlobalKey<FormState>(debugLabel: '_GuestBookState');
final _controller = TextEditingController();
@override
// Modify from here...
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// ...to here.
Padding(
padding: const EdgeInsets.all(8.0),
child: Form(
key: _formKey,
child: Row(
children: [
Expanded(
child: TextFormField(
controller: _controller,
decoration: const InputDecoration(
hintText: 'Leave a message',
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Enter your message to continue';
}
return null;
},
),
),
const SizedBox(width: 8),
StyledButton(
onPressed: () async {
if (_formKey.currentState!.validate()) {
await widget.addMessage(_controller.text);
_controller.clear();
}
},
child: Row(
children: const [
Icon(Icons.send),
SizedBox(width: 4),
Text('SEND'),
],
),
),
],
),
),
),
// Modify from here...
const SizedBox(height: 8),
for (var message in widget.messages)
Paragraph('${message.name}: ${message.message}'),
const SizedBox(height: 8),
],
// ...to here.
);
}
}
تقوم بلف المحتوى السابق للطريقة build()
باستخدام عنصر واجهة مستخدم Column
ثم تقوم بإضافة مجموعة في ذيل عناصر Column
الفرعية لإنشاء Paragraph
جديدة لكل رسالة في قائمة الرسائل.
- قم بتحديث نص
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 تلقائيًا وعلى الفور بمزامنة البيانات مع العملاء المشتركين في قاعدة البيانات.
اختبار مزامنة الرسائل:
- في التطبيق ، ابحث عن الرسائل التي أنشأتها مسبقًا في قاعدة البيانات.
- اكتب رسائل جديدة. تظهر على الفور.
- افتح مساحة العمل الخاصة بك في نوافذ أو علامات تبويب متعددة. تتم مزامنة الرسائل في الوقت الفعلي عبر النوافذ وعلامات التبويب.
- اختياري: في قائمة قاعدة البيانات الخاصة بوحدة تحكم Firebase ، احذف أو عدل أو أضف رسائل جديدة يدويًا. تظهر جميع التغييرات في واجهة المستخدم.
تهانينا! تقرأ مستندات Firestore في تطبيقك!
معاينة التطبيق
8. قم بإعداد قواعد الأمان الأساسية
قمت في البداية بإعداد 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 للمصادقة والتحقق من أن أي شخص يحاول الكتابة إلى المستند لديه 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
، أضف الأسطر التالية إلى قسم الموصلات في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});
}
}
- قم بتحديث طريقة
init()
الخاصة بتطبيقApplicationState
كما يلي:
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) {
_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();
});
}
يضيف هذا الرمز استعلامًا مشتركًا دائمًا لتحديد عدد الحاضرين واستعلام ثانٍ يكون نشطًا فقط أثناء تسجيل دخول المستخدم لتحديد ما إذا كان المستخدم سيحضر أم لا.
- أضف التعداد التالي في أعلى ملف
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'),
),
],
),
);
}
}
}
يبدأ في حالة غير محددة مع عدم تحديد "نعم" ولا " لا" . بمجرد أن يحدد المستخدم ما إذا كان سيحضر ، فإنك تعرض هذا الخيار مميزًا بزر مملوء ويتراجع الخيار الآخر مع عرض مسطح.
- قم بتحديث طريقة
build()
HomePage
للاستفادة منYesNoSelection
، وتمكين المستخدم الذي قام بتسجيل الدخول لترشيح ما إذا كان سيحضر ، وعرض عدد الحاضرين للحدث:
lib / home_page.dart
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 لإنشاء تطبيق ويب تفاعلي في الوقت الفعلي!