Dart: تعاون Dart و Flutter — منطق مشترك ومنصة
آخر تحديث: 2026-08-26
Dart هي قلب Flutter — لغة واحدة تقود واجهة المستخدم والمنطق والتواصل الأصلي.
1. ما ستتعلمه
- تحديد موقع طبقة Dart في بنية Flutter
- تصميم حزمة منطق أعمال مشتركة (حزمة Dart خالصة)
- MethodChannel: تواصل Dart ↔ Native
- FFI: Dart يستدعي مكتبات C
- سيناريو بوب: المنطق الأساسي لخط أنابيب البيانات يخدم كلاً من CLI ولوحة تحكم Flutter
2. قصة مطور حقيقية
(1) نقطة الألم: CLI و Flutter App ينفذان منطق أعمال منفصل
يحتوي خط أنابيب البيانات الخاص ببوب على واجهتين أماميتين: أداة CLI (للمطورين) ولوحة تحكم Flutter (لعملاء SaaS لعرض التقارير). نفذ النظامان منطق معالجة البيانات الخاص بهما، مما أدى إلى: إصلاح خطأ حساب الضريبة تطلب تغييرات في مكانين؛ تغيير واحد فائت مرة تسبب في أن يكون تقرير CLI صحيحاً ولكن لوحة التحكم خاطئة، مما أدى إلى شكاوى العملاء.
(2) الحل: حزمة منطق مشتركة
استخرج منطق الأعمال الأساسي في حزمة Dart خالصة، يعتمد عليها كل من CLI و Flutter. إصلاح خطأ يتطلب تغييراً في مكان واحد فقط.
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]
> **الإخراج:** شغّل محلياً في DartPad أو عبر `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x؛ قد تختلف النتائج قليلاً بسبب إصدارات SDK.
(3) الفوائد
- تتم صيانة المنطق الأساسي في مكان واحد، مع تزامن CLI و Flutter تلقائياً
- حزمة Dart الخالصة يمكنها العمل على جميع المنصات (VM/Web/الأصلية)
- يسمح MethodChannel لـ Flutter بالوصول إلى القدرات الأصلية (المستشعرات، إشعارات الدفع، إلخ)
3. دور Dart في بنية Flutter
(1) طبقات البنية
| الطبقة | التقنية | المسؤولية |
|---|---|---|
| طبقة واجهة المستخدم | Flutter Widget | عرض الواجهة والتفاعل |
| طبقة المنطق | Dart (خالصة) | منطق الأعمال، معالجة البيانات |
| طبقة المنصة | Native (Kotlin/Swift) | وظائف خاصة بالمنصة |
| طبقة التواصل | MethodChannel / FFI | جسر Dart ↔ Native |
▶ مثال
> **الإخراج:** شغّل محلياً في DartPad أو عبر `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x؛ قد تختلف النتائج قليلاً بسبب إصدارات SDK.
: منطق Dart في مشروع Flutter
// يعمل هذا الكود في كل من بيئتي 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});
}
> **الإخراج:** شغّل محلياً في DartPad أو عبر `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x؛ قد تختلف النتائج قليلاً بسبب إصدارات SDK.
4. تصميم حزمة منطق الأعمال المشتركة
(1) بنية حزمة Dart خالصة
▶ مثال
> **الإخراج:** شغّل محلياً في DartPad أو عبر `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x؛ قد تختلف النتائج قليلاً بسبب إصدارات SDK.
: بنية الحزمة المشتركة
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.
▶ مثال
> **الإخراج:** شغّل محلياً في DartPad أو عبر `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x؛ قد تختلف النتائج قليلاً بسبب إصدارات SDK.
: التصدير الشامل
// 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';
> **الإخراج:** شغّل محلياً في DartPad أو عبر `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x؛ قد تختلف النتائج قليلاً بسبب إصدارات SDK.
▶ مثال
> **الإخراج:** شغّل محلياً في DartPad أو عبر `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x؛ قد تختلف النتائج قليلاً بسبب إصدارات SDK.
: pubspec.yaml لحزمة Dart خالصة
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
▶ مثال
> **الإخراج:** شغّل محلياً في DartPad أو عبر `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x؛ قد تختلف النتائج قليلاً بسبب إصدارات SDK.
: مستهلكان
# 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
▶ مثال
> **الإخراج:** شغّل محلياً في DartPad أو عبر `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x؛ قد تختلف النتائج قليلاً بسبب إصدارات SDK.
: MethodChannel يستدعي Native
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;
}
}
}
> **الإخراج:** شغّل محلياً في DartPad أو عبر `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x؛ قد تختلف النتائج قليلاً بسبب إصدارات SDK.
(2) التنفيذ على الجانب الأصلي
▶ مثال
> **الإخراج:** شغّل محلياً في DartPad أو عبر `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x؛ قد تختلف النتائج قليلاً بسبب إصدارات SDK.
: جانب Android (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()
}
}
}
}
> **الإخراج:** شغّل محلياً في DartPad أو عبر `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x؛ قد تختلف النتائج قليلاً بسبب إصدارات SDK.
| طريقة التواصل | الاتجاه | نوع البيانات | حالة الاستخدام |
|---|---|---|---|
| MethodChannel | Dart ← Native | أنواع قياسية | استدعاء طرق أصلية |
| EventChannel | Native ← Dart | Stream | تدفقات الأحداث الأصلية |
| BasicMessageChannel | ثنائي الاتجاه | String/Bytes | رسائل ثنائية الاتجاه |
6. FFI — استدعاء مكتبات C
▶ مثال
> **الإخراج:** شغّل محلياً في DartPad أو عبر `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x؛ قد تختلف النتائج قليلاً بسبب إصدارات SDK.
: dart:ffi يستدعي دالة C
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);
}
> **الإخراج:** شغّل محلياً في DartPad أو عبر `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x؛ قد تختلف النتائج قليلاً بسبب إصدارات SDK.
| مفهوم FFI | الوصف |
|---|---|
DynamicLibrary |
تحميل مكتبات الربط الديناميكي |
lookupFunction |
البحث عن دالة C |
Pointer<T> |
يشير إلى ذاكرة أصلية |
calloc / free |
تخصيص/تحرير الذاكرة الأصلية |
NativeType |
تعيين نوع C |
7. سيناريو بوب: بنية الواجهة الأمامية المزدوجة لخط أنابيب البيانات
▶ مثال
> **الإخراج:** شغّل محلياً في DartPad أو عبر `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x؛ قد تختلف النتائج قليلاً بسبب إصدارات SDK.
: جوهر مشترك + واجهات أمامية مزدوجة
// 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()
> **الإخراج:** شغّل محلياً في DartPad أو عبر `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x؛ قد تختلف النتائج قليلاً بسبب إصدارات SDK.
8. المثال الكامل: البنية المشتركة لخط أنابيب البيانات
// ============================================
// البنية المشتركة لخط أنابيب البيانات
// جوهر 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');
}
> **الإخراج:** شغّل محلياً في DartPad أو عبر `dart run`. جميع الأمثلة في دورة Dart مبنية على Dart 3.x / Flutter 3.x؛ قد تختلف النتائج قليلاً بسبب إصدارات SDK.
الإخراج:
=== 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 أو تبعيات المسار) لسهولة المزامنة بين الحزمة الأساسية والمستهلكين. المستودع المنفصل مناسب للتوزيع مفتوح المصدر.
📖 ملخص
- Dart هي اللغة الأساسية لـ Flutter، تقود كلاً من طبقتي واجهة المستخدم والمنطق
- حزمة المنطق المشترك (Dart خالصة) تسمح لـ CLI و Flutter بإعادة استخدام نفس كود الأعمال
- ينفذ MethodChannel تواصل Dart ↔ Native للوظائف الخاصة بالمنصة
- يستدعي FFI مكتبات C مباشرة، مناسب للحسابات عالية الأداء ودمج كود C الموجود
- مبدأ البنية: المنطق الأساسي في Dart خالصة، طبقة واجهة المستخدم مخصصة لكل منصة
📝 تمارين
- أساسي (صعوبة ⭐): أنشئ حزمة Dart خالصة (
dart create -t package-simple) تحتوي على دالةformatUSD. ارجع إليها واستدعها من مشروع CLI ومشروع Flutter. - متوسط (صعوبة ⭐⭐): صمم بنية حزمة DataPipeline الأساسية: 3 فئات نموذج + 2 فئة خدمة + 1 تصدير شامل. تأكد من أن pubspec.yaml ليس له تبعية Flutter، واكتب اختبارات قابلة للتشغيل باستخدام
dart test. - تحدي (صعوبة ⭐⭐⭐): نفّذ واجهة
DataStorageمجردة (Dart خالصة)، ثم نفذها باستخدام dart:io (CLI) و SharedPreferences (Flutter) بشكل منفصل. يجب أن تعتمد الحزمة الأساسية فقط على الواجهة، مع التنفيذات الملموسة التي يحقنها المستهلكون.