إعداد مصادقة Firebase وتدفقات البريد الإلكتروني وكلمة المرور
إعداد مصادقة Firebase وتدفقات البريد الإلكتروني وكلمة المرور
Firebase Authentication هي خدمة الخلفية المعيارية في الصناعة للتعامل مع هوية المستخدم في تطبيقات Flutter. توفر نظام مصادقة آمناً وقابلاً للتوسع دون الحاجة إلى بناء خادم مصادقة خاص بك والحفاظ عليه. في هذا الدرس ستقوم بتهيئة SDK لـ Firebase Auth من الصفر وتنفيذ التدفقات الأساسية الأربعة: التسجيل، وتسجيل الدخول، وتسجيل الخروج، والتحقق من البريد الإلكتروني.
١. إعداد المشروع: إضافة Firebase إلى Flutter
قبل كتابة أي كود Dart، يجب توصيل مشروع Flutter بمشروع Firebase. النهج الموصى به هو FlutterFire CLI:
- أنشئ مشروع Firebase على console.firebase.google.com وفعّل موفر تسجيل الدخول البريد الإلكتروني/كلمة المرور تحت Authentication > Sign-in method.
- ثبّت FlutterFire CLI:
dart pub global activate flutterfire_cli - نفّذ
flutterfire configureفي مجلد المشروع. هذا يُنزّل ملفات الإعداد الخاصة بكل منصة (google-services.jsonلـ Android وGoogleService-Info.plistلـ iOS) ويولّدlib/firebase_options.dart. - أضف الحزم المطلوبة إلى
pubspec.yaml:firebase_coreوfirebase_auth.
pubspec.yaml — التبعيات المطلوبة
dependencies:
flutter:
sdk: flutter
firebase_core: ^3.6.0
firebase_auth: ^5.3.1
٢. تهيئة Firebase في main.dart
يجب تهيئة Firebase قبل استدعاء runApp(). لأن التهيئة غير متزامنة، يجب أن تكون main() بصيغة async وأن تنتظر Firebase.initializeApp() باستخدام await. تمرير DefaultFirebaseOptions.currentPlatform (المولّد بواسطة CLI) يختار إعداد المنصة الصحيح تلقائياً.
main.dart — تهيئة Firebase
import 'package:firebase_core/firebase_core.dart';
import 'firebase_options.dart';
import 'package:flutter/material.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
runApp(const MyApp());
}
WidgetsFlutterBinding.ensureInitialized() قبل أي عمل مع الإضافات الأصلية (بما فيها Firebase). إغفال هذا الاستدعاء يتسبب في استثناء وقت التشغيل قبل أن يُعرض التطبيق.٣. تسجيل مستخدم جديد
استخدم FirebaseAuth.instance.createUserWithEmailAndPassword() لإنشاء حساب جديد. تعيد الدالة UserCredential الذي تحتوي خاصية .user الخاصة به على كائن User المنشأ حديثاً. دائماً لفّ استدعاءات Firebase في كتلة try/catch وتعامل مع FirebaseAuthException تحديداً — فهي تكشف عن سلسلة نصية .code (مثل 'email-already-in-use'، 'weak-password') تتيح لك عرض رسائل خطأ ذات معنى.
auth_service.dart — تدفق التسجيل
import 'package:firebase_auth/firebase_auth.dart';
class AuthService {
final FirebaseAuth _auth = FirebaseAuth.instance;
/// ينشئ حساباً جديداً ويرسل رابط التحقق من البريد الإلكتروني.
Future<UserCredential> register({
required String email,
required String password,
}) async {
try {
final credential = await _auth.createUserWithEmailAndPassword(
email: email,
password: password,
);
// طلب التحقق من البريد الإلكتروني فوراً بعد التسجيل.
await credential.user?.sendEmailVerification();
return credential;
} on FirebaseAuthException catch (e) {
throw _mapAuthException(e);
}
}
/// تسجيل دخول مستخدم موجود.
Future<UserCredential> login({
required String email,
required String password,
}) async {
try {
return await _auth.signInWithEmailAndPassword(
email: email,
password: password,
);
} on FirebaseAuthException catch (e) {
throw _mapAuthException(e);
}
}
/// تسجيل خروج المستخدم الحالي.
Future<void> logout() => _auth.signOut();
/// إعادة تحميل المستخدم والتحقق من حالة التحقق من البريد الإلكتروني.
Future<bool> isEmailVerified() async {
await _auth.currentUser?.reload();
return _auth.currentUser?.emailVerified ?? false;
}
String _mapAuthException(FirebaseAuthException e) {
switch (e.code) {
case 'email-already-in-use':
return 'يوجد حساب بهذا البريد الإلكتروني بالفعل.';
case 'weak-password':
return 'كلمة المرور يجب أن تكون 6 أحرف على الأقل.';
case 'user-not-found':
case 'wrong-password':
return 'البريد الإلكتروني أو كلمة المرور غير صحيحة.';
case 'too-many-requests':
return 'محاولات كثيرة جداً. يرجى المحاولة لاحقاً.';
default:
return e.message ?? 'فشلت المصادقة.';
}
}
}
٤. الاستماع إلى تغييرات حالة المصادقة
يُعيد FirebaseAuth.instance.authStateChanges() تدفق Stream<User?> يُصدر كائن User عند تسجيل الدخول وnull عند تسجيل الخروج. لفّ الودجت الجذري بـ StreamBuilder على هذا التدفق للتوجيه التفاعلي بين شاشات المصادقة وغير المصادقة — دون الحاجة لمنطق تنقل يدوي.
التوجيه التفاعلي للمصادقة باستخدام StreamBuilder
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: StreamBuilder<User?>(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
}
if (snapshot.hasData) {
final user = snapshot.data!;
// التوجيه إلى شاشة التحقق من البريد إذا لم يتم التحقق بعد.
return user.emailVerified
? const HomeScreen()
: const VerifyEmailScreen();
}
return const LoginScreen();
},
),
);
}
}
authStateChanges() الحدث فقط عند تسجيل الدخول والخروج. للكشف عن اكتمال التحقق من البريد الإلكتروني دون تشغيل من الخلفية، استخدم مؤقتاً لاستدعاء user.reload() أو افعل ذلك عندما يعود المستخدم إلى الواجهة الأمامية، ثم تحقق من user.emailVerified.٥. تدفق التحقق من البريد الإلكتروني
بعد التسجيل، يُرسل Firebase بريداً للتحقق تلقائياً عند استدعاء user.sendEmailVerification(). أظهر شاشة VerifyEmailScreen مخصصة حيث يُوجَّه المستخدم للتحقق من صندوق بريده. أضف زر إعادة الإرسال وزر لقد تحققت يُطلق user.reload() ثم الفحص:
VerifyEmailScreen — الاستطلاع للتحقق
class VerifyEmailScreen extends StatefulWidget {
const VerifyEmailScreen({super.key});
@override
State<VerifyEmailScreen> createState() => _VerifyEmailScreenState();
}
class _VerifyEmailScreenState extends State<VerifyEmailScreen> {
bool _isSending = false;
Future<void> _checkVerification() async {
await FirebaseAuth.instance.currentUser?.reload();
final verified =
FirebaseAuth.instance.currentUser?.emailVerified ?? false;
if (verified && mounted) {
// لا يُطلق authStateChanges() هنا؛ انتقل يدوياً.
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (_) => const HomeScreen()),
);
} else if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('لم يتم التحقق من البريد بعد.')),
);
}
}
Future<void> _resendEmail() async {
setState(() => _isSending = true);
await FirebaseAuth.instance.currentUser?.sendEmailVerification();
setState(() => _isSending = false);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('تحقق من بريدك الإلكتروني')),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('تم إرسال بريد التحقق. تحقق من صندوق الوارد.'),
ElevatedButton(
onPressed: _checkVerification,
child: const Text('لقد تحققت'),
),
TextButton(
onPressed: _isSending ? null : _resendEmail,
child: Text(_isSending ? 'جارٍ الإرسال...' : 'إعادة إرسال البريد'),
),
],
),
);
}
}
authStateChanges() حدثاً جديداً عند تغيّر emailVerified من false إلى true — هذا الحقل جزء من ملف المستخدم، وليس من حالة المصادقة. يجب استدعاء user.reload() صراحةً ثم التحقق من الحقل، ثم التنقل برمجياً.ملخص
في هذا الدرس قمت بـ:
- تهيئة
firebase_coreوfirebase_authباستخدام FlutterFire CLI. - تهيئة Firebase في
main()قبلrunApp(). - تنفيذ التسجيل (
createUserWithEmailAndPassword)، وتسجيل الدخول (signInWithEmailAndPassword)، وتسجيل الخروج (signOut) مع معالجة صحيحة لـFirebaseAuthException. - استخدام
authStateChanges()معStreamBuilderللتوجيه التفاعلي. - إرسال التحقق من البريد الإلكتروني واستطلاعه، والتنقل يدوياً عند تغيّر الحالة.
AuthService يُبقي جميع استدعاءات Firebase خارج الودجات. تستدعي الودجات دوال الخدمة وتتفاعل مع تدفق authStateChanges() — هذا الفصل يجعل منطق المصادقة قابلاً للاختبار وواجهة المستخدم نظيفة.