Dart: تعاون Dart و Flutter — منطق مشترك ومنصة

آخر تحديث: 2026-08-26

Dart هي قلب Flutter — لغة واحدة تقود واجهة المستخدم والمنطق والتواصل الأصلي.

1. ما ستتعلمه


2. قصة مطور حقيقية

(1) نقطة الألم: CLI و Flutter App ينفذان منطق أعمال منفصل

يحتوي خط أنابيب البيانات الخاص ببوب على واجهتين أماميتين: أداة CLI (للمطورين) ولوحة تحكم Flutter (لعملاء SaaS لعرض التقارير). نفذ النظامان منطق معالجة البيانات الخاص بهما، مما أدى إلى: إصلاح خطأ حساب الضريبة تطلب تغييرات في مكانين؛ تغيير واحد فائت مرة تسبب في أن يكون تقرير CLI صحيحاً ولكن لوحة التحكم خاطئة، مما أدى إلى شكاوى العملاء.

(2) الحل: حزمة منطق مشتركة

استخرج منطق الأعمال الأساسي في حزمة Dart خالصة، يعتمد عليها كل من CLI و Flutter. إصلاح خطأ يتطلب تغييراً في مكان واحد فقط.

100%
graph TD
  A[جوهر خط أنابيب البيانات<br/>Dart خالصة] --> B[تطبيق CLI]
  A --> C[لوحة تحكم Flutter]
  C --> D[MethodChannel]
  D --> E[iOS أصلي]
  D --> F[Android أصلي]
  C --> G[FFI]
  G --> H[مكتبة C]
TEXT 📖 للعرض فقط
> **الإخراج:** شغّل محلياً في DartPad أو عبر `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x؛ قد تختلف النتائج قليلاً بسبب إصدارات SDK.

(3) الفوائد


3. دور Dart في بنية Flutter

(1) طبقات البنية

الطبقة التقنية المسؤولية
طبقة واجهة المستخدم Flutter Widget عرض الواجهة والتفاعل
طبقة المنطق Dart (خالصة) منطق الأعمال، معالجة البيانات
طبقة المنصة Native (Kotlin/Swift) وظائف خاصة بالمنصة
طبقة التواصل MethodChannel / FFI جسر Dart ↔ Native

▶ مثال

TEXT 📖 للعرض فقط
> **الإخراج:** شغّل محلياً في DartPad أو عبر `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x؛ قد تختلف النتائج قليلاً بسبب إصدارات SDK.

: منطق Dart في مشروع Flutter

DART
// يعمل هذا الكود في كل من بيئتي CLI و Flutter
// lib/data_pipeline_core.dart

class OrderAnalyzer {
  final double taxRate;

  OrderAnalyzer({this.taxRate = 0.08});

  double calculateTax(double amount) => amount * taxRate;
  double calculateTotal(double amount) => amount * (1 + taxRate);

  Map<String, double> groupByCategory(List<Order> orders) {
    final result = <String, double>{};
    for (final order in orders) {
      result.update(order.category, (v) => v + order.amount, ifAbsent: () => order.amount);
    }
    return result;
  }
}

class Order {
  final String id;
  final double amount;
  final String category;

  Order({required this.id, required this.amount, required this.category});
}
TEXT 📖 للعرض فقط
> **الإخراج:** شغّل محلياً في DartPad أو عبر `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x؛ قد تختلف النتائج قليلاً بسبب إصدارات SDK.

4. تصميم حزمة منطق الأعمال المشتركة

(1) بنية حزمة Dart خالصة

▶ مثال

TEXT 📖 للعرض فقط
> **الإخراج:** شغّل محلياً في DartPad أو عبر `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x؛ قد تختلف النتائج قليلاً بسبب إصدارات SDK.

: بنية الحزمة المشتركة

TEXT 📖 للعرض فقط
packages/
  data_pipeline_core/
    lib/
      src/
        models/
          order.dart
          product.dart
          customer.dart
        services/
          analyzer.dart
          aggregator.dart
          transformer.dart
        utils/
          formatters.dart
          validators.dart
      data_pipeline_core.dart  # تصدير شامل
    test/
      analyzer_test.dart
      aggregator_test.dart
    pubspec.yaml
```text


```text
> **الإخراج:** شغّل محلياً في DartPad أو عبر `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x؛ قد تختلف النتائج قليلاً بسبب إصدارات SDK.

▶ مثال

TEXT 📖 للعرض فقط
> **الإخراج:** شغّل محلياً في DartPad أو عبر `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x؛ قد تختلف النتائج قليلاً بسبب إصدارات SDK.

: التصدير الشامل

DART
// lib/data_pipeline_core.dart
// ملف barrel - يصدر جميع واجهات API العامة

// النماذج
export 'src/models/order.dart';
export 'src/models/product.dart';
export 'src/models/customer.dart';

// الخدمات
export 'src/services/analyzer.dart';
export 'src/services/aggregator.dart';
export 'src/services/transformer.dart';

// الأدوات
export 'src/utils/formatters.dart';
export 'src/utils/validators.dart';
TEXT 📖 للعرض فقط
> **الإخراج:** شغّل محلياً في DartPad أو عبر `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x؛ قد تختلف النتائج قليلاً بسبب إصدارات SDK.

▶ مثال

TEXT 📖 للعرض فقط
> **الإخراج:** شغّل محلياً في DartPad أو عبر `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x؛ قد تختلف النتائج قليلاً بسبب إصدارات SDK.

: pubspec.yaml لحزمة Dart خالصة

YAML
name: data_pipeline_core
description: Core business logic for DataPipeline - pure Dart, no Flutter dependency
version: 1.0.0

environment:
  sdk: ^3.0.0

dependencies:
  json_annotation: ^4.8.0

dev_dependencies:
  test: ^1.24.0
  json_serializable: ^6.7.0
  build_runner: ^2.4.0

▶ مثال

TEXT 📖 للعرض فقط
> **الإخراج:** شغّل محلياً في DartPad أو عبر `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x؛ قد تختلف النتائج قليلاً بسبب إصدارات SDK.

: مستهلكان

YAML
# CLI app pubspec.yaml
dependencies:
  data_pipeline_core:
    path: ../packages/data_pipeline_core
  args: ^2.4.2

# Flutter app pubspec.yaml
dependencies:
  data_pipeline_core:
    path: ../packages/data_pipeline_core
  flutter:
    sdk: flutter
المبدأ الوصف
لا تبعية على Flutter pubspec.yaml لا يعتمد على flutter
لا استخدام مباشر لـ dart:io استخدم واجهات مجردة لعمليات الملف/الشبكة
لا كود واجهة مستخدم منطق معالجة بيانات خالص
اختبار كامل قابل للاختبار بدون وقت تشغيل Flutter

5. MethodChannel

(1) تواصل Dart ↔ Native

▶ مثال

TEXT 📖 للعرض فقط
> **الإخراج:** شغّل محلياً في DartPad أو عبر `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x؛ قد تختلف النتائج قليلاً بسبب إصدارات SDK.

: MethodChannel يستدعي Native

DART
import 'package:flutter/services.dart';

class NativeService {
  static const _channel = MethodChannel('com.datapipeline/native');

  // استدعاء طريقة أصلية
  Future<String> getDeviceId() async {
    try {
      final deviceId = await _channel.invokeMethod<String>('getDeviceId');
      return deviceId ?? 'unknown';
    } on PlatformException catch (e) {
      print('Failed to get device ID: ${e.message}');
      return 'error';
    }
  }

  // استدعاء مع وسائط
  Future<bool> saveToFile(String path, String content) async {
    try {
      final result = await _channel.invokeMethod<bool>('saveToFile', {
        'path': path,
        'content': content,
      });
      return result ?? false;
    } on PlatformException catch (e) {
      print('Failed to save: ${e.message}');
      return false;
    }
  }
}
TEXT 📖 للعرض فقط
> **الإخراج:** شغّل محلياً في DartPad أو عبر `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x؛ قد تختلف النتائج قليلاً بسبب إصدارات SDK.

(2) التنفيذ على الجانب الأصلي

▶ مثال

TEXT 📖 للعرض فقط
> **الإخراج:** شغّل محلياً في DartPad أو عبر `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x؛ قد تختلف النتائج قليلاً بسبب إصدارات SDK.

: جانب Android (Kotlin)

KOTLIN
// تنفيذ Android
class MainActivity : FlutterActivity() {
    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)

        MethodChannel(flutterEngine.dartExecutor.binaryMessenger,
            "com.datapipeline/native").setMethodCallHandler { call, result ->
            when (call.method) {
                "getDeviceId" -> {
                    val deviceId = Settings.Secure.getString(
                        contentResolver, Settings.Secure.ANDROID_ID)
                    result.success(deviceId)
                }
                "saveToFile" -> {
                    val path = call.argument<String>("path")
                    val content = call.argument<String>("content")
                    // منطق حفظ الملف
                    result.success(true)
                }
                else -> result.notImplemented()
            }
        }
    }
}
TEXT 📖 للعرض فقط
> **الإخراج:** شغّل محلياً في DartPad أو عبر `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x؛ قد تختلف النتائج قليلاً بسبب إصدارات SDK.
طريقة التواصل الاتجاه نوع البيانات حالة الاستخدام
MethodChannel Dart ← Native أنواع قياسية استدعاء طرق أصلية
EventChannel Native ← Dart Stream تدفقات الأحداث الأصلية
BasicMessageChannel ثنائي الاتجاه String/Bytes رسائل ثنائية الاتجاه

6. FFI — استدعاء مكتبات C

▶ مثال

TEXT 📖 للعرض فقط
> **الإخراج:** شغّل محلياً في DartPad أو عبر `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x؛ قد تختلف النتائج قليلاً بسبب إصدارات SDK.

: dart:ffi يستدعي دالة C

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

// توقيع دالة C: double process_data(double* data, int length)
typedef ProcessDataNative = Double Function(Pointer<Double>, Int32);
typedef ProcessDataDart = double Function(Pointer<Double>, int);

void main() {
  // تحميل المكتبة الديناميكية
  final dylib = DynamicLibrary.open('libdatapipeline.so');

  // البحث عن الدالة
  final processData = dylib.lookupFunction<ProcessDataNative, ProcessDataDart>('process_data');

  // تحضير البيانات
  final dataPtr = calloc<Double>(5);
  for (var i = 0; i < 5; i++) {
    dataPtr[i] = (i + 1) * 100.0;
  }

  // استدعاء دالة C
  final result = processData(dataPtr, 5);
  print('Result from C: $result');

  // تحرير الذاكرة
  calloc.free(dataPtr);
}
TEXT 📖 للعرض فقط
> **الإخراج:** شغّل محلياً في DartPad أو عبر `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x؛ قد تختلف النتائج قليلاً بسبب إصدارات SDK.
مفهوم FFI الوصف
DynamicLibrary تحميل مكتبات الربط الديناميكي
lookupFunction البحث عن دالة C
Pointer<T> يشير إلى ذاكرة أصلية
calloc / free تخصيص/تحرير الذاكرة الأصلية
NativeType تعيين نوع C

7. سيناريو بوب: بنية الواجهة الأمامية المزدوجة لخط أنابيب البيانات

▶ مثال

TEXT 📖 للعرض فقط
> **الإخراج:** شغّل محلياً في DartPad أو عبر `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x؛ قد تختلف النتائج قليلاً بسبب إصدارات SDK.

: جوهر مشترك + واجهات أمامية مزدوجة

DART
// packages/data_pipeline_core/lib/src/services/analyzer.dart

class OrderAnalyzer {
  final double taxRate;

  OrderAnalyzer({this.taxRate = 0.08});

  AnalysisResult analyze(List<Order> orders) {
    final completed = orders.where((o) => o.status == 'completed').toList();
    final revenue = completed.fold<double>(0, (s, o) => s + o.amount);
    final tax = revenue * taxRate;

    final byCategory = <String, double>{};
    for (final o in completed) {
      byCategory.update(o.category, (v) => v + o.amount, ifAbsent: () => o.amount);
    }

    return AnalysisResult(
      totalOrders: orders.length,
      completedOrders: completed.length,
      revenue: revenue,
      tax: tax,
      revenueByCategory: byCategory,
    );
  }
}

class AnalysisResult {
  final int totalOrders;
  final int completedOrders;
  final double revenue;
  final double tax;
  final Map<String, double> revenueByCategory;

  AnalysisResult({
    required this.totalOrders,
    required this.completedOrders,
    required this.revenue,
    required this.tax,
    required this.revenueByCategory,
  });

  double get totalWithTax => revenue + tax;
  double get averageOrderValue => completedOrders > 0 ? revenue / completedOrders : 0;
}

class Order {
  final String id;
  final double amount;
  final String status;
  final String category;

  Order({required this.id, required this.amount, required this.status, required this.category});
}

// مستهلك CLI
void main() {
  final analyzer = OrderAnalyzer(taxRate: 0.08);
  final orders = [
    Order(id: 'ORD-001', amount: 1500.0, status: 'completed', category: 'Electronics'),
    Order(id: 'ORD-002', amount: 890.0, status: 'completed', category: 'Clothing'),
  ];

  final result = analyzer.analyze(orders);
  print('Revenue: \$${result.revenue.toStringAsFixed(2)} USD');
  print('Tax: \$${result.tax.toStringAsFixed(2)} USD');
}

// مستهلك Flutter سيستخدم نفس OrderAnalyzer
// ولكن يعرض النتائج في Widget بدلاً من print()
TEXT 📖 للعرض فقط
> **الإخراج:** شغّل محلياً في DartPad أو عبر `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x؛ قد تختلف النتائج قليلاً بسبب إصدارات SDK.

8. المثال الكامل: البنية المشتركة لخط أنابيب البيانات

DART
// ============================================
// البنية المشتركة لخط أنابيب البيانات
// جوهر Dart خالص + مستهلك CLI + مستهلك Flutter
// ============================================

// ---- المكتبة الأساسية (Dart خالصة) ----

// lib/src/models/order.dart
class Order {
  final String id;
  final double amount;
  final String status;
  final String category;
  final String region;

  const Order({
    required this.id,
    required this.amount,
    required this.status,
    required this.category,
    this.region = 'US',
  });

  double get tax => amount * _taxRate(region);
  double get total => amount + tax;
  String get formatAmount => '\$${amount.toStringAsFixed(2)} USD';

  static double _taxRate(String region) => switch (region) {
    'US' => 0.08,
    'EU' => 0.20,
    'UK' => 0.15,
    _ => 0.10,
  };
}

// lib/src/services/analyzer.dart
class OrderAnalyzer {
  const OrderAnalyzer();

  AnalysisResult analyze(List<Order> orders) {
    final completed = orders.where((o) => o.status == 'completed').toList();
    final revenue = completed.fold<double>(0, (s, o) => s + o.amount);
    final tax = completed.fold<double>(0, (s, o) => s + o.tax);
    final total = revenue + tax;

    final byCategory = <String, CategoryResult>{};
    for (final o in completed) {
      byCategory.update(
        o.category,
        (v) => v.addOrder(o),
        ifAbsent: () => CategoryResult.fromOrder(o),
      );
    }

    return AnalysisResult(
      totalOrders: orders.length,
      completedOrders: completed.length,
      revenue: revenue,
      tax: tax,
      total: total,
      byCategory: byCategory,
    );
  }
}

class CategoryResult {
  final int count;
  final double revenue;

  const CategoryResult({required this.count, required this.revenue});

  factory CategoryResult.fromOrder(Order order) =>
      CategoryResult(count: 1, revenue: order.amount);

  CategoryResult addOrder(Order order) =>
      CategoryResult(count: count + 1, revenue: revenue + order.amount);

  double get average => count > 0 ? revenue / count : 0;
}

class AnalysisResult {
  final int totalOrders;
  final int completedOrders;
  final double revenue;
  final double tax;
  final double total;
  final Map<String, CategoryResult> byCategory;

  const AnalysisResult({
    required this.totalOrders,
    required this.completedOrders,
    required this.revenue,
    required this.tax,
    required this.total,
    required this.byCategory,
  });

  double get averageOrderValue => completedOrders > 0 ? revenue / completedOrders : 0;
}

// ---- مستهلك CLI ----
void main() {
  final analyzer = OrderAnalyzer();
  final orders = [
    Order(id: 'ORD-001', amount: 1500.0, status: 'completed', category: 'Electronics'),
    Order(id: 'ORD-002', amount: 890.0, status: 'completed', category: 'Clothing'),
    Order(id: 'ORD-003', amount: 3200.0, status: 'pending', category: 'Electronics'),
    Order(id: 'ORD-004', amount: 2100.0, status: 'completed', category: 'Electronics', region: 'EU'),
  ];

  final result = analyzer.analyze(orders);

  print('=== DataPipeline Analytics ===');
  print('Orders:    ${result.completedOrders}/${result.totalOrders}');
  print('Revenue:   \$${result.revenue.toStringAsFixed(2)} USD');
  print('Tax:       \$${result.tax.toStringAsFixed(2)} USD');
  print('Total:     \$${result.total.toStringAsFixed(2)} USD');
  print('Average:   \$${result.averageOrderValue.toStringAsFixed(2)} USD');

  print('\nBy Category:');
  final sorted = result.byCategory.entries.toList()
    ..sort((a, b) => b.value.revenue.compareTo(a.value.revenue));
  for (final entry in sorted) {
    final cat = entry.value;
    print('  ${entry.key}: ${cat.count} orders, '
        '\$${cat.revenue.toStringAsFixed(2)} USD (avg: \$${cat.average.toStringAsFixed(2)})');
  }

  print('\n--- Platform Compatibility ---');
  print('Core logic:  Pure Dart (CLI + Flutter + Web)');
  print('CLI:         dart:io + args package');
  print('Flutter:     Widgets + MethodChannel + FFI');
}
TEXT 📖 للعرض فقط
> **الإخراج:** شغّل محلياً في DartPad أو عبر `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x؛ قد تختلف النتائج قليلاً بسبب إصدارات SDK.

الإخراج:

TEXT 📖 للعرض فقط
=== DataPipeline Analytics ===
Orders:    3/4
Revenue:   $4490.00 USD
Tax:       $798.00 USD
Total:     $5288.00 USD
Average:   $1496.67 USD

By Category:
  Electronics: 2 orders, $3600.00 USD (avg: $1800.00)
  Clothing: 1 orders, $890.00 USD (avg: $890.00)

--- Platform Compatibility ---
Core logic:  Pure Dart (CLI + Flutter + Web)
CLI:         dart:io + args package
Flutter:     Widgets + MethodChannel + FFI

❓ أسئلة شائعة

س: هل يمكن لحزمة Dart خالصة العمل على Flutter Web؟ ج: نعم، طالما لم يتم استخدام dart:io. منصة الويب لا تدعم dart:io؛ استخدم واجهات مجردة (مثل Client في حزمة http) لاستبدال عمليات الملف/الشبكة المباشرة.

س: هل هناك حد لحجم نقل البيانات عبر MethodChannel؟ ج: لا يوجد حد صارم، لكن الكائنات الكبيرة تتطلب تسلسل/إلغاء تسلسل، مما له أداء ضعيف. للبيانات الكبيرة، فكر في استخدام FFI أو ملفات مؤقتة.

س: هل FFI يدعم iOS؟ ج: نعم. على iOS، حمّل .dylib أو .framework. FFI في Flutter 3.0+ يدعم جميع المنصات الرئيسية.

س: متى أستخدم MethodChannel مقابل FFI؟ ج: MethodChannel مناسب لاستدعاء واجهات API الخاصة بالمنصة (المستشعرات، إشعارات الدفع، حفظ الملفات)؛ FFI مناسب لاستدعاء مكتبات C (معالجة الصور، التشفير، محركات قواعد البيانات).

س: هل يتطلب اختبار حزمة مشتركة بيئة Flutter؟ ج: لا. يمكن اختبار حزمة Dart خالصة باستخدام dart test وحدها ولا تعتمد على Flutter. هذه فائدة رئيسية لاستخراج حزمة مشتركة.

س: كيف يمكنني ضمان ألا تقدم حزمة مشتركة تبعيات Flutter عن طريق الخطأ؟ ج: لا تعتمد على flutter في pubspec.yaml، ولا تستورد حزم flutter. في CI، شغّل الاختبارات باستخدام dart test (وليس flutter test) للتحقق.

س: هل يجب أن تستخدم حزمة DataPipeline الأساسية monorepo أو مستودع منفصل؟ ج: يُنصح بـ monorepo (باستخدام Melos أو تبعيات المسار) لسهولة المزامنة بين الحزمة الأساسية والمستهلكين. المستودع المنفصل مناسب للتوزيع مفتوح المصدر.


📖 ملخص


📝 تمارين

  1. أساسي (صعوبة ⭐): أنشئ حزمة Dart خالصة (dart create -t package-simple) تحتوي على دالة formatUSD. ارجع إليها واستدعها من مشروع CLI ومشروع Flutter.
  2. متوسط (صعوبة ⭐⭐): صمم بنية حزمة DataPipeline الأساسية: 3 فئات نموذج + 2 فئة خدمة + 1 تصدير شامل. تأكد من أن pubspec.yaml ليس له تبعية Flutter، واكتب اختبارات قابلة للتشغيل باستخدام dart test.
  3. تحدي (صعوبة ⭐⭐⭐): نفّذ واجهة DataStorage مجردة (Dart خالصة)، ثم نفذها باستخدام dart:io (CLI) و SharedPreferences (Flutter) بشكل منفصل. يجب أن تعتمد الحزمة الأساسية فقط على الواجهة، مع التنفيذات الملموسة التي يحقنها المستهلكون.

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

Web-Tutorial.com

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

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

100%