Flutter: تكامل Firebase

Firebase هو المساعد الخارق لـ Flutter — Auth وقاعدة بيانات وتخزين وتحليلات وإشعارات فورية، كل شيء جاهز من الصندوق.

📋 المتطلبات السابقة: يجب أن تكون ملمًا بـ

1. ما ستتعلمه


2. قصة حقيقية عن انهيار خادم ذاتي البناء

(1) المشكلة: تكلفة تشغيل عالية للخادم الذاتي

خادم ShopApp لبوب الذاتي يستخدم Node.js + PostgreSQL + S3، بفريق تشغيل من 3 أشهر وتكلفة شهرية 20 ألف دولار. ترحيل قاعدة بيانات واحد سبب 4 ساعات توقف، بخسارة 50 ألف دولار في الطلبات. مزامنة مخزون المنتجات الفورية تحتاج WebSocket، الذي كان معقدًا وغير مستقر في البنية الذاتية. الإشعارات الفورية استخدمت خدمة خارجية بتأخيرات متكررة.

(2) حل حزمة Firebase

يوفر Firebase Auth + Firestore + Storage + Analytics + FCM كخادم شامل — لا حاجة لتشغيل خوادم، البيانات الفورية تتزامن تلقائيًا.

DART
import 'package:cloud_firestore/cloud_firestore.dart';

// ⚙️ تثبيت التبعيات: flutter pub add cloud_firestore

// Custom class definition source:
// - Product: see Lesson 11 json_serializable model (add fromFirestore factory constructor)

// Firestore real-time product updates
FirebaseFirestore.instance
  .collection('products')
  .where('stock', isGreaterThan: 0)
  .snapshots()
  .listen((snapshot) {
    // UI auto-updates when stock changes
    products = snapshot.docs.map((d) => Product.fromFirestore(d)).toList();
  });
TEXT
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.

(3) الفائدة: تشغيل صفري + مزامنة فورية + تكاليف مخفضة للنصف

بعد انتقال بوب لـ Firebase، تقلص فريق التشغيل بشخصين، وانخفضت التكلفة الشهرية من 20 ألف دولار إلى 8 آلاف، وانخفضت زمن انتقال البيانات الفورية من ثوانٍ إلى ملي ثانية.


3. نظرة عامة على بنية Firebase

100%
graph TD
    FB[Firebase] --> AUTH[Authentication]
    FB --> FS[Cloud Firestore]
    FB --> ST[Cloud Storage]
    FB --> AN[Analytics]
    FB --> CM[Cloud Messaging]
    FS --> |snapshots| PL[ProductList Realtime]
    AUTH --> |authStateChanges| LP[LoginPage]
    CM --> |onMessage| NP[NotificationPopup]
    AN --> |logEvent| DA[Dashboard Analytics]
TEXT
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.

(1) مصفوفة خدمات Firebase

الخدمة الاستخدام التسعير
Authentication مصادقة المستخدم طبقة مجانية سخية
Cloud Firestore قاعدة بيانات NoSQL فورية طبقة مجانية: 50 ألف قراءة/يوم
Cloud Storage تخزين الملفات طبقة مجانية: 5 غيغابايت
Analytics تحليلات سلوك المستخدم مجاني
Cloud Messaging إشعارات فورية مجاني
Crashlytics تقارير الأعطال مجاني

4. إعدادات مشروع Firebase

▶ مثال

TEXT
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.

: تهيئة FlutterFire

BASH
# Install FlutterFire CLI
dart pub global activate flutterfire_cli

# Configure Firebase project
flutterfire configure

# Add dependencies
flutter pub add firebase_core firebase_auth cloud_firestore firebase_storage firebase_analytics firebase_messaging
TEXT
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.
DART
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';

// ⚙️ تثبيت التبعيات: flutter pub add firebase_core
// ⚙️ إعدادات المشروع: dart pub global activate flutterfire_cli && flutterfire configure

// lib/main.dart
import 'firebase_options.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
  runApp(const ShopApp());
}
TEXT
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.

5. المصادقة

(1) مقارنة طرق تسجيل الدخول

الطريقة التعقيد تجربة المستخدم حالة الاستخدام
بريد إلكتروني/كلمة مرور منخفض متوسطة عام
Google Sign-In متوسط عالية مفضلة على Android
Apple Sign-In متوسط عالية مطلوبة على iOS
تسجيل دخول مجهول منخفض عالية (بدون احتكاك) تجربة قبل التسجيل
التحقق من الهاتف مرتفع متوسطة التحقق من الهوية

▶ مثال

TEXT
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.

: غلاف AuthService

DART
import 'package:firebase_auth/firebase_auth.dart';
import 'package:google_sign_in/google_sign_in.dart';

// ⚙️ تثبيت التبعيات: flutter pub add firebase_auth google_sign_in

class AuthService {
  final FirebaseAuth _auth = FirebaseAuth.instance;

  Stream<User?> get authStateChanges => _auth.authStateChanges();

  Future<UserCredential> signInWithEmail(String email, String password) =>
      _auth.signInWithEmailAndPassword(email: email, password: password);

  Future<UserCredential> signUpWithEmail(String email, String password) =>
      _auth.createUserWithEmailAndPassword(email: email, password: password);

  Future<UserCredential> signInWithGoogle() async {
    final googleUser = await GoogleSignIn().signIn();
    final googleAuth = await googleUser?.authentication;
    final credential = GoogleAuthProvider.credential(
      accessToken: googleAuth?.accessToken,
      idToken: googleAuth?.idToken,
    );
    return _auth.signInWithCredential(credential);
  }

  Future<UserCredential> signInAnonymously() => _auth.signInAnonymously();

  Future<void> signOut() => _auth.signOut();

  User? get currentUser => _auth.currentUser;
}
TEXT
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.

▶ مثال

TEXT
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.

: Riverpod Auth Notifier + Firebase

DART
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

// ⚙️ تثبيت التبعيات: flutter pub add firebase_auth flutter_riverpod riverpod_annotation
// ⚙️ تبعيات تطوير: flutter pub add --dev riverpod_generator build_runner

// Custom class definition source:
// - AuthService: see Section 5 AuthService wrapper in this lesson

@riverpod
class FirebaseAuth extends _$FirebaseAuth {
  @override
  AsyncValue<User?> build() {
    final service = AuthService();
    // Listen to auth state changes
    service.authStateChanges.listen((user) {
      state = AsyncData(user);
    });
    return AsyncData(service.currentUser);
  }

  Future<void> login(String email, String password) async {
    state = const AsyncLoading();
    state = await AsyncValue.guard(() => AuthService().signInWithEmail(email, password));
  }

  Future<void> loginWithGoogle() async {
    state = const AsyncLoading();
    state = await AsyncValue.guard(() => AuthService().signInWithGoogle());
  }

  Future<void> logout() async {
    await AuthService().signOut();
  }
}
TEXT
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.

6. Cloud Firestore

(1) نمذجة البيانات

TEXT
firestore/
├── products/{productId}
│   ├── name: "Pro Laptop"
│   ├── price: 1299.99
│   ├── stock: 150
│   ├── category: "Electronics"
│   └── imageUrl: "gs://shopapp.appspot.com/products/laptop.jpg"
├── users/{userId}
│   ├── name: "Bob"
│   ├── email: "alice@shopapp.com"
│   ├── addresses: [{...}]
│   └── preferences: {theme: "dark", currency: "USD"}
├── orders/{orderId}
│   ├── userId: "alice_123"
│   ├── items: [{productId, quantity, price}]
│   ├── total: 99.99
│   ├── status: "shipped"
│   └── createdAt: Timestamp
└── reviews/{reviewId}
    ├── productId: "laptop_001"
    ├── userId: "bob_456"
    ├── rating: 4.5
    └── comment: "Great product!"
TEXT
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.

▶ مثال

TEXT
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.

: CRUD في Firestore

DART
import 'package:cloud_firestore/cloud_firestore.dart';

// ⚙️ تثبيت التبعيات: flutter pub add cloud_firestore

// Custom class definition source:
// - Product: see Lesson 11 json_serializable model (add fromFirestore factory constructor)

class ProductFirestore {
  final _db = FirebaseFirestore.instance;

  Future<List<Product>> getProducts({String? category, int limit = 20}) async {
    Query query = _db.collection('products').orderBy('rating', descending: true).limit(limit);
    if (category != null) query = query.where('category', isEqualTo: category);
    final snapshot = await query.get();
    return snapshot.docs.map((d) => Product.fromFirestore(d)).toList();
  }

  Future<Product> getProduct(String id) async {
    final doc = await _db.collection('products').doc(id).get();
    return Product.fromFirestore(doc);
  }

  // Real-time listener
  Stream<List<Product>> watchProducts({String? category}) {
    Query query = _db.collection('products').where('stock', isGreaterThan: 0);
    if (category != null) query = query.where('category', isEqualTo: category);
    return query.snapshots().map((s) => s.docs.map((d) => Product.fromFirestore(d)).toList());
  }

  Future<void> updateStock(String productId, int newStock) =>
      _db.collection('products').doc(productId).update({'stock': newStock});
}
TEXT
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.

▶ مثال

TEXT
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.

: قواعد أمان Firestore

JAVASCRIPT
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    // Products: anyone can read, only admins can write
    match /products/{productId} {
      allow read: if true;
      allow write: if isAdmin();
    }
    // Orders: users can read/write own orders
    match /orders/{orderId} {
      allow read, write: if request.auth != null && request.auth.uid == resource.data.userId;
      allow create: if request.auth != null;
    }
    // Reviews: anyone can read, authenticated users can create
    match /reviews/{reviewId} {
      allow read: if true;
      allow create: if request.auth != null;
    }
  }
}
TEXT
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.

7. Cloud Storage

▶ مثال

TEXT
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.

: رفع وتنزيل صور المنتجات

DART
import 'package:firebase_storage/firebase_storage.dart';
import 'dart:io';

// ⚙️ تثبيت التبعيات: flutter pub add firebase_storage

class StorageService {
  final _storage = FirebaseStorage.instance;

  Future<String> uploadProductImage(String filePath, String productId) async {
    final ref = _storage.ref().child('products/$productId/${DateTime.now().millisecondsSinceEpoch}.jpg');
    final uploadTask = ref.putFile(File(filePath));
    final snapshot = await uploadTask;
    return await snapshot.ref.getDownloadURL();
  }

  Future<String> getDownloadUrl(String path) =>
      _storage.ref().child(path).getDownloadURL();

  // Upload with progress
  Stream<double> uploadWithProgress(String filePath, String productId) {
    final ref = _storage.ref().child('products/$productId/main.jpg');
    return ref.putFile(File(filePath)).snapshotEvents.map((s) {
      return s.bytesTransferred / s.totalBytes;
    });
  }
}
TEXT
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.

8. Analytics + Cloud Messaging

▶ مثال

TEXT
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.

: تتبع أحداث Analytics

DART
import 'package:firebase_analytics/firebase_analytics.dart';

// ⚙️ تثبيت التبعيات: flutter pub add firebase_analytics

// Custom class definition source:
// - Product: see Lesson 11 json_serializable model
// - Order: see Lesson 14 Order model

class ShopAnalytics {
  static final _analytics = FirebaseAnalytics.instance;

  static Future<void> logViewProduct(Product product) =>
      _analytics.logEvent(name: 'view_product', parameters: {
        'product_id': product.id,
        'product_name': product.name,
        'price': product.price,
        'category': product.category,
      });

  static Future<void> logAddToCart(Product product, int quantity) =>
      _analytics.logEvent(name: 'add_to_cart', parameters: {
        'product_id': product.id,
        'quantity': quantity,
        'price': product.price,
        'currency': 'USD',
      });

  static Future<void> logPurchase(Order order) =>
      _analytics.logEvent(name: 'purchase', parameters: {
        'order_id': order.id,
        'value': order.total,
        'currency': 'USD',
        'item_count': order.items.length,
      });

  static Future<void> setUserProperty(String name, String value) =>
      _analytics.setUserProperty(name: name, value: value);
}
TEXT
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.

▶ مثال

TEXT
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.

: إشعارات FCM الفورية

DART
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/foundation.dart';

// ⚙️ تثبيت التبعيات: flutter pub add firebase_messaging

class NotificationService {
  static final _messaging = FirebaseMessaging.instance;

  static Future<void> init() async {
    // Request permission
    await _messaging.requestPermission();

    // Get FCM token
    final token = await _messaging.getToken();
    debugPrint('FCM Token: $token');

    // Foreground messages
    FirebaseMessaging.onMessage.listen((message) {
      debugPrint('Foreground message: ${message.notification?.title}');
    });

    // Background messages
    FirebaseMessaging.onMessageOpenedApp.listen((message) {
      // Handle notification tap
      final productId = message.data['product_id'];
      if (productId != null) {
        // Navigate to product detail
      }
    });
  }
}
TEXT
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.

❓ أسئلة شائعة

س هل طبقة Firebase المجانية كافية؟
ج Firestore يوفر 50 ألف قراءة/20 ألف كتابة/يوم مجانًا — كافية للتطبيقات صغيرة النطاق. مليون مستخدم نشط يوميًا يحتاج خطة مدفوعة.
س Firestore أم Realtime Database؟
ج استخدم Firestore للمشاريع الجديدة (استعلامات أقوى، نموذج بيانات أفضل). Realtime Database فقط للسيناريوهات التي تحتاج زمن انتقال فائق الانخفاض.
س كيف أدمج رموز Firebase Auth مع واجهة API خلفية؟
ج احصل على JWT بـ await user.getIdToken() وأرسله للخادم للتحقق. الخادم يستخدم Firebase Admin SDK للتحقق من الرمز.
س هل استعلامات Firestore لها حدود فهرسة؟
ج الاستعلامات المركبة تحتاج فهارس مركبة. أول تنفيذ سيفشل بخطأ يحتوي رابطًا لإنشاء الفهرس.
س هل إشعارات FCM الفورية تعمل في الصين القارية؟
ج لا. الصين القارية تحتاج تكامل إشعارات خاصة بالبائع (هواوي/شاومي/أوبو/فيفو) أو خدمات خارجية مثل JPush.
س كيف أُنقح قواعد أمان Firestore؟
ج Rules Playground في Firebase Console يمكنه محاكاة طلبات قراءة/كتابة لاختبار القواعد. ضع قواعد صارمة دائمًا في الإنتاج.

📖 ملخص


📝 تمارين

  1. أساسي (الصعوبة ⭐): أعد مشروع Firebase، نفذ تسجيل ودخول بالبريد الإلكتروني/كلمة المرور، وراقب authStateChanges لعرض حالة المستخدم.
  2. متوسط (الصعوبة ⭐⭐): نفذ CRUD لمنتجات Firestore + استماع فوري، مع تحديث UI تلقائي عند تغير مخزون المنتج.
  3. متقدم (الصعوبة ⭐⭐⭐): نفذ تكامل Firebase كامل: Google Sign-In + منتجات Firestore الفورية + رفع صور Storage (مع تقدم) + تتبع أحداث Analytics + إشعارات FCM الفورية.

← الدرس السابق | الدرس التالي →

Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%