دارتدُكس
توثيق شامل

لغة دارت - الأساسيات والمفاهيم المتقدمة

دليل شامل للغة البرمجة دارت بالعربية، من الأساسيات وحتى المفاهيم المتقدمة بشرح بسيط وأمثلة واضحة

مقدمة إلى لغة دارت

دارت (Dart) هي لغة برمجة مفتوحة المصدر طورتها شركة جوجل، وهي اللغة الرسمية لتطوير تطبيقات فلاتر (Flutter). تجمع دارت بين ميزات لغات البرمجة الشائعة مثل جافا، جافاسكربت، و C#، وتوفر تجربة برمجية سلسة ومريحة.

مميزات لغة دارت

  • سهلة التعلم وسريعة التطوير
  • تعمل على منصات متعددة (متعددة المنصات)
  • مناسبة لبناء واجهات المستخدم
  • أداء عالي وتحسين تلقائي (JIT و AOT)
  • دعم قوي للبرمجة غير المتزامنة
  • أمان القيم الفارغة (Null Safety)
  • تدعم البرمجة الكائنية والوظيفية

تستخدم دارت لبناء:

  • تطبيقات الهواتف الذكية (باستخدام فلاتر)
  • تطبيقات الويب (باستخدام مترجم دارت إلى جافاسكربت)
  • تطبيقات سطح المكتب
  • تطبيقات الخادم

في هذا الدليل، سنتعرف على جميع أساسيات لغة دارت وحتى المفاهيم المتقدمة بطريقة مبسطة ومباشرة.

التثبيت والإعداد

هناك عدة طرق للبدء باستخدام دارت:

1. استخدام DartPad (بدون تثبيت)

DartPad هي بيئة تطوير عبر الإنترنت تتيح لك تجربة كود دارت مباشرة:

2. تثبيت SDK بشكل مباشر

لتثبيت دارت على جهازك، اتبع الخطوات التالية حسب نظام التشغيل:

Windows


# قم بتثبيت Chocolatey (إذا لم يكن مثبتًا)
# ثم قم بتثبيت Dart SDK
choco install dart-sdk

macOS


# باستخدام Homebrew
brew tap dart-lang/dart
brew install dart

Linux


# باستخدام apt على Ubuntu/Debian
$ sudo apt-get update
$ sudo apt-get install apt-transport-https
$ sudo sh -c 'wget -qO- https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add -'
$ sudo sh -c 'wget -qO- https://storage.googleapis.com/download.dartlang.org/linux/debian/dart_stable.list > /etc/apt/sources.list.d/dart_stable.list'
$ sudo apt-get update
$ sudo apt-get install dart

3. تثبيت فلاتر (يتضمن دارت)

إذا كنت تخطط لاستخدام فلاتر، فيمكنك تثبيته وسيأتي مع دارت SDK مدمجًا:

التحقق من التثبيت

للتأكد من نجاح التثبيت، افتح موجه الأوامر (Terminal) واكتب:

dart --version

بيئات التطوير المتكاملة (IDEs)

أفضل البيئات لتطوير دارت هي:

  • Visual Studio Code مع إضافة Dart
  • Android Studio أو IntelliJ IDEA مع إضافة Dart

أساسيات دارت

لنبدأ بالتعرف على أساسيات لغة دارت من خلال البرنامج التقليدي "Hello, World!".

برنامج Hello World

// أول برنامج بلغة دارت
void main() {
  print('Hello, World!');
}

دعنا نشرح هذا البرنامج البسيط:

  • void main() - هذه هي نقطة بداية البرنامج، وهي دالة يجب أن توجد في كل برنامج دارت.
  • print() - دالة مدمجة تقوم بعرض النص على وحدة التحكم.
  • النص بين علامات الاقتباس المفردة هو سلسلة نصية (String).
  • كل تعليمة في دارت يجب أن تنتهي بفاصلة منقوطة (;).

هيكل البرنامج في دارت

تتميز برامج دارت بهذه العناصر الأساسية:

  • التعليقات: تبدأ بـ // للتعليقات السطرية، أو بين /* */ للتعليقات متعددة الأسطر.
  • الاستيراد: لاستخدام المكتبات باستخدام import.
  • الدالة الرئيسية: main() التي تبدأ تنفيذ البرنامج.
  • المتغيرات والثوابت: لتخزين البيانات.

نموذج برنامج أكثر تعقيدًا

// استيراد مكتبة للحصول على وظائف إضافية
import 'dart:math';

// البرنامج الرئيسي
void main() {
  // تعريف متغير
  String name = 'المتعلم';
  
  // استدعاء دالة وطباعة النتيجة
  print('مرحبًا يا ${name}!');
  print('عدد عشوائي: ${generateRandomNumber(100)}');
}

// دالة لإنشاء رقم عشوائي
int generateRandomNumber(int max) {
  Random random = Random();
  return random.nextInt(max);
}

هذا البرنامج يوضح المزيد من ميزات دارت، مثل استيراد المكتبات، وتعريف الدوال، واستخدام المتغيرات، والسلاسل النصية المركبة (التي تسمح لك بتضمين قيم المتغيرات داخل النص).

المتغيرات والأنواع

المتغيرات هي حاويات لتخزين القيم. في دارت، كل متغير له نوع محدد من البيانات.

أنواع البيانات الأساسية

دارت توفر عدة أنواع أساسية من البيانات:

  • int - الأعداد الصحيحة (مثل: 42، -1)
  • double - الأعداد العشرية (مثل: 3.14، -2.5)
  • num - أي نوع من الأرقام (int أو double)
  • String - السلاسل النصية (مثل: 'مرحبا'، "عالم")
  • bool - القيم المنطقية (true أو false)
  • List - القوائم أو المصفوفات
  • Map - مجموعات المفتاح-قيمة
  • Set - مجموعات من القيم الفريدة
  • Symbol - رموز غير قابلة للتغيير تمثل معرّفات

إعلان المتغيرات

يمكنك إعلان المتغيرات بعدة طرق:

// إعلان مع تحديد النوع
int age = 30;
String name = 'أحمد';
bool isActive = true;

// إعلان باستخدام var (سيُستنتج النوع تلقائيًا)
var score = 85;  // سيكون من نوع int
var message = 'مرحبا';  // سيكون من نوع String

// استخدام dynamic للمتغيرات التي قد يتغير نوعها
dynamic value = 100;
value = 'تغير النوع';  // مسموح لأن النوع ديناميكي

نصيحة:

يُفضل استخدام الأنواع المحددة (مثل int، String) بدلاً من var حيثما أمكن، لتحسين وضوح الكود وتسهيل اكتشاف الأخطاء.

الثوابت (const و final)

للقيم التي لا تتغير، يمكنك استخدام const و final:

// final: قيمة ثابتة يتم تحديدها وقت التشغيل
final currentTime = DateTime.now();

// const: قيمة ثابتة يتم تحديدها وقت التجميع
const pi = 3.14159;
const daysInWeek = 7;

// الفرق الرئيسي في العمل
void main() {
  // يمكن استخدام final مع قيم تُحدد في وقت التشغيل
  final now = DateTime.now();  // صحيح
  
  // لا يمكن استخدام const مع قيم تُحدد في وقت التشغيل
  // const today = DateTime.now();  // خطأ!
  
  // لكن يمكن استخدامه مع قيم ثابتة وقت التجميع
  const sum = 10 + 20;  // صحيح
}

العمليات والمشغلات

المشغلات (Operators) هي رموز تُستخدم لإجراء عمليات على المتغيرات والقيم، وتصنف إلى عدة أنواع.

العمليات الحسابية

void main() {
  // العمليات الحسابية الأساسية
  int a = 10;
  int b = 3;
  
  print('الجمع: ${a + b}');       // 13
  print('الطرح: ${a - b}');       // 7
  print('الضرب: ${a * b}');       // 30
  print('القسمة: ${a / b}');      // 3.3333333333333335 (نتيجة double)
  print('القسمة الصحيحة: ${a ~/ b}');  // 3 (نتيجة int)
  print('باقي القسمة: ${a % b}');  // 1
  
  // زيادة ونقصان
  int x = 5;
  x++;    // زيادة بمقدار 1
  print(x);  // 6
  
  int y = 8;
  y--;    // نقصان بمقدار 1
  print(y);  // 7
  
  // عمليات مختصرة
  int c = 10;
  c += 5;  // نفس c = c + 5
  print(c);  // 15
  
  c -= 3;  // نفس c = c - 3
  print(c);  // 12
  
  c *= 2;  // نفس c = c * 2
  print(c);  // 24
  
  c ~/= 5;  // نفس c = c ~/ 5
  print(c);  // 4
}

عمليات المقارنة

void main() {
  int a = 5;
  int b = 10;
  
  print('a == b: ${a == b}');  // false (هل a يساوي b)
  print('a != b: ${a != b}');  // true (هل a لا يساوي b)
  print('a > b: ${a > b}');    // false (هل a أكبر من b)
  print('a < b: ${a < b}');    // true (هل a أصغر من b)
  print('a >= b: ${a >= b}');  // false (هل a أكبر من أو يساوي b)
  print('a <= b: ${a <= b}');  // true (هل a أصغر من أو يساوي b)
}

العمليات المنطقية

void main() {
  bool x = true;
  bool y = false;
  
  // عملية AND (و) المنطقية
  print('x && y: ${x && y}');  // false
  
  // عملية OR (أو) المنطقية
  print('x || y: ${x || y}');  // true
  
  // عملية NOT (نفي) المنطقية
  print('!x: ${!x}');         // false
  
  // أمثلة مركبة
  bool condition1 = true;
  bool condition2 = false;
  bool condition3 = true;
  
  print((condition1 && condition3) || condition2);  // true
  print(condition1 && (condition2 || condition3));  // true
}

العمليات على البت (Bitwise)

void main() {
  // عمليات Bitwise تعمل على مستوى البت
  int a = 5;   // 101 في الثنائي
  int b = 3;   // 011 في الثنائي
  
  print('a & b: ${a & b}');    // 1 (AND)
  print('a | b: ${a | b}');    // 7 (OR)
  print('a ^ b: ${a ^ b}');    // 6 (XOR)
  print('~a: ${~a}');          // -6 (NOT)
  print('a << 1: ${a << 1}');  // 10 (إزاحة لليسار)
  print('a >> 1: ${a >> 1}');  // 2 (إزاحة لليمين)
}

عمليات الشرط

void main() {
  // العملية الشرطية الثلاثية ? :
  int a = 10;
  int b = 5;
  
  String result = a > b ? 'a أكبر من b' : 'a أصغر من أو يساوي b';
  print(result);  // a أكبر من b
  
  // عملية ?? (اختبار القيمة الفارغة)
  String? nullableStr;
  String nonNullStr = nullableStr ?? 'قيمة افتراضية';
  print(nonNullStr);  // قيمة افتراضية
  
  nullableStr = 'قيمة موجودة';
  nonNullStr = nullableStr ?? 'قيمة افتراضية';
  print(nonNullStr);  // قيمة موجودة
}

عمليات أخرى

void main() {
  // عملية is (اختبار النوع)
  var value = 'نص';
  print(value is String);  // true
  print(value is int);     // false
  
  // عملية as (تحويل النوع)
  dynamic dynamicValue = 'هذا نص';
  String str = dynamicValue as String;
  print(str);  // هذا نص
  
  // عملية ?. (الوصول الآمن)
  String? nullableString;
  print(nullableString?.length);  // null
  
  nullableString = 'مرحبا';
  print(nullableString?.length);  // 5
}

هياكل التحكم

هياكل التحكم تتحكم في تدفق البرنامج وتسمح لك بتنفيذ كود معين بناءً على شروط أو تكرار كود عدة مرات.

if وif-else

void main() {
  int age = 18;
  
  // جملة if بسيطة
  if (age >= 18) {
    print('أنت بالغ');
  }
  
  // جملة if-else
  if (age >= 18) {
    print('يمكنك التصويت');
  } else {
    print('لا يمكنك التصويت');
  }
  
  // جملة if-else if-else متعددة
  if (age < 13) {
    print('طفل');
  } else if (age < 18) {
    print('مراهق');
  } else if (age < 65) {
    print('بالغ');
  } else {
    print('كبير في السن');
  }
  
  // استخدام الأقواس {} ليست إلزامية للتعليمات المفردة
  if (age >= 18)
    print('بالغ');
  else
    print('قاصر');
}

التبديل والحالات (switch-case)

void main() {
  String grade = 'A';
  
  switch (grade) {
    case 'A':
      print('ممتاز');
      break;
    case 'B':
      print('جيد جدًا');
      break;
    case 'C':
      print('جيد');
      break;
    case 'D':
      print('مقبول');
      break;
    case 'F':
      print('راسب');
      break;
    default:
      print('درجة غير صالحة');
  }
  
  // منذ Dart 2.17+، يمكن استخدام ميزة كلمة break تلقائيًا
  int day = 3;
  switch (day) {
    case 1:
    case 2:
    case 3:
    case 4:
    case 5:
      print('يوم عمل'); // ستنفذ للأيام من 1 إلى 5
      break;
    case 6:
    case 7:
      print('عطلة نهاية الأسبوع'); // ستنفذ للأيام 6 و 7
      break;
    default:
      print('يوم غير صالح');
  }
}

حلقة for

void main() {
  // حلقة for التقليدية
  for (int i = 0; i < 5; i++) {
    print('العداد: $i');
  }
  
  // حلقة for على مجموعة
  List fruits = ['تفاح', 'موز', 'برتقال'];
  for (String fruit in fruits) {
    print('الفاكهة: $fruit');
  }
  
  // حلقة for مع continue و break
  for (int i = 0; i < 10; i++) {
    if (i == 3) continue;  // تخطي التكرار الحالي
    if (i == 7) break;     // الخروج من الحلقة
    print(i);
  }
}

حلقة while و do-while

void main() {
  // حلقة while: تتحقق من الشرط أولاً، ثم تنفذ الكود
  int count = 0;
  while (count < 5) {
    print('count = $count');
    count++;
  }
  
  // حلقة do-while: تنفذ الكود أولاً، ثم تتحقق من الشرط
  int number = 10;
  do {
    print('number = $number');
    number -= 2;
  } while (number > 0);
  
  // تأكد دائمًا من تحديث متغير الشرط
  // لتجنب الحلقات اللانهائية!
}

التنقل في الحلقات

void main() {
  // استخدام التسميات (labels) للحلقات المتداخلة
  outerLoop: for (int i = 0; i < 3; i++) {
    print('الحلقة الخارجية: $i');
    
    innerLoop: for (int j = 0; j < 3; j++) {
      if (i == 1 && j == 1) {
        break outerLoop;  // الخروج من الحلقة الخارجية
      }
      
      if (j == 1) {
        continue innerLoop;  // تخطي بقية التكرار في الحلقة الداخلية
      }
      
      print('  الحلقة الداخلية: $j');
    }
  }
}

الدوال

الدوال (Functions) هي كتل من التعليمات التي تؤدي مهمة محددة. في دارت، الدوال هي كائنات من الدرجة الأولى، مما يعني أنه يمكن تخزينها في متغيرات وتمريرها كوسيطات.

تعريف الدوال البسيطة

// دالة تقوم بالجمع وترجع قيمة
int add(int a, int b) {
  return a + b;
}

// دالة بدون قيمة إرجاع
void printInfo(String name, int age) {
  print('الاسم: $name، العمر: $age');
}

void main() {
  int sum = add(5, 3);
  print('المجموع: $sum');
  
  printInfo('أحمد', 25);
}

المعاملات الاختيارية

// معاملات اختيارية (باستخدام أقواس مربعة)
String greet(String name, [String? title]) {
  if (title != null) {
    return 'مرحبًا $title $name';
  }
  return 'مرحبًا $name';
}

// معاملات اختيارية مع قيم افتراضية
void printDetails(String name, [int age = 20, String country = 'مصر']) {
  print('$name، $age سنة، من $country');
}

void main() {
  print(greet('محمد'));          // مرحبًا محمد
  print(greet('فاطمة', 'د.'));  // مرحبًا د. فاطمة
  
  printDetails('علي');                 // علي، 20 سنة، من مصر
  printDetails('سارة', 25);            // سارة، 25 سنة، من مصر
  printDetails('خالد', 30, 'السعودية'); // خالد، 30 سنة، من السعودية
}

المعاملات المسماة

// معاملات مسماة (باستخدام أقواس معقوفة)
void createUser({String? name, int? age, String? email}) {
  print('تم إنشاء مستخدم:');
  if (name != null) print('- الاسم: $name');
  if (age != null) print('- العمر: $age');
  if (email != null) print('- البريد: $email');
}

// معاملات مسماة إلزامية (باستخدام required)
void registerUser({required String username, required String password, String? phone}) {
  print('تم تسجيل المستخدم: $username');
  // عمليات التسجيل...
}

void main() {
  // يمكن تقديم المعاملات بأي ترتيب
  createUser(
    email: '[email protected]',
    name: 'أحمد',
    age: 30,
  );
  
  // يمكن تخطي المعاملات الاختيارية
  createUser(
    name: 'محمد',
    age: 25,
  );
  
  // المعاملات المطلوبة إلزامية
  registerUser(
    username: 'user123',
    password: 'password123',
    // phone غير إلزامي
  );
}

دوال الأسهم (Arrow Functions)

// دالة تقليدية
int multiply(int a, int b) {
  return a * b;
}

// نفس الدالة باستخدام صيغة الأسهم المختصرة
int multiplyArrow(int a, int b) => a * b;

// دالة سهم مع جملة شرطية
String getStatus(int score) => score >= 60 ? 'ناجح' : 'راسب';

void main() {
  print('5 × 3 = ${multiply(5, 3)}');
  print('4 × 7 = ${multiplyArrow(4, 7)}');
  print('حالة الطالب: ${getStatus(75)}');
}

الدوال المجهولة

void main() {
  // تعريف دالة مجهولة وتخزينها في متغير
  var sayHello = (String name) {
    return 'مرحبًا $name';
  };
  
  // استدعاء الدالة المجهولة
  print(sayHello('أحمد'));
  
  // دالة مجهولة بصيغة السهم
  var square = (int x) => x * x;
  print('مربع 4 = ${square(4)}');
  
  // استخدام الدالة المجهولة مباشرة دون تخزين
  print(((int x, int y) => x + y)(5, 3));  // 8
  
  // استخدام دوال مجهولة مع دوال أخرى
  List numbers = [1, 2, 3, 4, 5];
  
  // تطبيق دالة على كل عنصر
  var doubled = numbers.map((number) => number * 2);
  print(doubled);  // (2, 4, 6, 8, 10)
  
  // تصفية العناصر
  var evens = numbers.where((number) => number % 2 == 0);
  print(evens);  // (2, 4)
}

الدوال كمعاملات

// تعريف نوع الدالة
typedef MathOperation = int Function(int, int);

// دالة تستقبل دالة أخرى كمعامل
int calculate(int a, int b, MathOperation operation) {
  return operation(a, b);
}

// الدوال التي سنستخدمها كمعاملات
int add(int a, int b) => a + b;
int subtract(int a, int b) => a - b;
int multiply(int a, int b) => a * b;

void main() {
  // استخدام دوال مختلفة مع نفس دالة الحساب
  print('10 + 5 = ${calculate(10, 5, add)}');
  print('10 - 5 = ${calculate(10, 5, subtract)}');
  print('10 × 5 = ${calculate(10, 5, multiply)}');
  
  // استخدام دالة مجهولة
  int result = calculate(10, 5, (a, b) => a ~/ b);
  print('10 ÷ 5 = $result');
}

الفئات والكائنات

دارت هي لغة برمجة كائنية المنحى، حيث تُستخدم الفئات لتعريف مخططات الكائنات وسلوكياتها.

تعريف الفئات

// تعريف فئة بسيطة
class Person {
  // الخصائص (Properties)
  String name;
  int age;
  
  // المُنشئ (Constructor)
  Person(this.name, this.age);
  
  // الدوال (Methods)
  void introduce() {
    print('مرحبًا، اسمي $name وعمري $age سنة.');
  }
  
  // دالة تُرجع نص
  String getInfo() {
    return 'الاسم: $name، العمر: $age';
  }
}

void main() {
  // إنشاء كائن من فئة Person
  var person1 = Person('أحمد', 30);
  
  // الوصول إلى خصائص الكائن
  print(person1.name);
  print(person1.age);
  
  // استدعاء دوال الكائن
  person1.introduce();
  print(person1.getInfo());
  
  // تعديل خصائص الكائن
  person1.age = 31;
  print(person1.getInfo());
}

أنواع المُنشئات

class User {
  String username;
  String email;
  int age;
  
  // المُنشئ الرئيسي
  User(this.username, this.email, this.age);
  
  // منشئ مسمى (Named constructor)
  User.guest() 
      : username = 'زائر',
        email = '[email protected]',
        age = 0;
  
  // منشئ مسمى آخر
  User.fromMap(Map data)
      : username = data['username'],
        email = data['email'],
        age = data['age'];
}

void main() {
  // استخدام المُنشئ الرئيسي
  var user1 = User('ahmed', '[email protected]', 30);
  print('${user1.username}, ${user1.email}, ${user1.age}');
  
  // استخدام المُنشئ المسمى guest
  var guest = User.guest();
  print('${guest.username}, ${guest.email}, ${guest.age}');
  
  // استخدام المُنشئ المسمى fromMap
  var user2 = User.fromMap({
    'username': 'sara',
    'email': '[email protected]',
    'age': 25
  });
  print('${user2.username}, ${user2.email}, ${user2.age}');
}

الخصائص الخاصة والعامة

class BankAccount {
  // خاصية خاصة (تبدأ بعلامة _)
  double _balance = 0;
  
  // المُنشئ
  BankAccount([double initialBalance = 0]) {
    if (initialBalance >= 0) {
      _balance = initialBalance;
    }
  }
  
  // دوال الوصول (Getters)
  double get balance => _balance;
  
  // دوال التعديل (Setters)
  set balance(double newBalance) {
    if (newBalance >= 0) {
      _balance = newBalance;
    } else {
      print('لا يمكن تعيين رصيد سلبي!');
    }
  }
  
  // دوال عامة
  void deposit(double amount) {
    if (amount > 0) {
      _balance += amount;
      print('تم الإيداع: $amount، الرصيد الجديد: $_balance');
    }
  }
  
  bool withdraw(double amount) {
    if (amount > 0 && _balance >= amount) {
      _balance -= amount;
      print('تم السحب: $amount، الرصيد الجديد: $_balance');
      return true;
    }
    print('عملية السحب غير ممكنة!');
    return false;
  }
}

void main() {
  var account = BankAccount(1000);
  print('الرصيد الحالي: ${account.balance}');
  
  account.deposit(500);
  account.withdraw(200);
  
  // استخدام الـ setter
  account.balance = 3000;
  print('الرصيد بعد التعديل: ${account.balance}');
  
  // محاولة وضع قيمة سالبة
  account.balance = -100;  // سيظهر تحذير ولن تتغير القيمة
  
  // لا يمكن الوصول مباشرة للخاصية الخاصة
  // account._balance = 5000;  // خطأ!
}

الخصائص والدوال الثابتة (Static)

class MathUtils {
  // خاصية ثابتة
  static const double pi = 3.14159;
  
  // دالة ثابتة
  static double calculateCircleArea(double radius) {
    return pi * radius * radius;
  }
  
  // دالة ثابتة أخرى
  static int max(int a, int b) {
    return (a > b) ? a : b;
  }
}

class Counter {
  // خاصية ثابتة متغيرة
  static int count = 0;
  
  // دالة عادية تستخدم الخاصية الثابتة
  void increment() {
    Counter.count++;
  }
}

void main() {
  // استخدام الخصائص والدوال الثابتة دون إنشاء كائن
  print('قيمة باي: ${MathUtils.pi}');
  print('مساحة الدائرة: ${MathUtils.calculateCircleArea(5)}');
  print('الأكبر بين 7 و 3: ${MathUtils.max(7, 3)}');
  
  // استخدام الخاصية الثابتة المتغيرة
  print('العداد: ${Counter.count}');
  
  var counter1 = Counter();
  counter1.increment();
  print('العداد بعد زيادة: ${Counter.count}');
  
  var counter2 = Counter();
  counter2.increment();
  print('العداد بعد زيادة أخرى: ${Counter.count}');
}

الوراثة والواجهات

الوراثة (Inheritance) هي إحدى آليات البرمجة الكائنية التي تسمح للفئات بوراثة خصائص وسلوكيات من فئات أخرى.

الوراثة البسيطة

// الفئة الأساسية (الأب)
class Animal {
  String name;
  
  Animal(this.name);
  
  void makeSound() {
    print('صوت حيوان عام');
  }
  
  void eat() {
    print('$name يأكل');
  }
}

// فئة مشتقة (الابن) ترث من Animal
class Dog extends Animal {
  // خاصية إضافية
  String breed;
  
  // المُنشئ يستدعي مُنشئ الفئة الأساسية
  Dog(String name, this.breed) : super(name);
  
  // إعادة تعريف (override) دالة من الفئة الأساسية
  @override
  void makeSound() {
    print('$name ينبح');
  }
  
  // دالة جديدة خاصة بهذه الفئة
  void fetch() {
    print('$name يجلب الكرة');
  }
}

void main() {
  // استخدام الفئة الأساسية
  var animal = Animal('حيوان');
  animal.makeSound();  // صوت حيوان عام
  animal.eat();        // حيوان يأكل
  
  // استخدام الفئة المشتقة
  var dog = Dog('ريكس', 'جيرمن شيبرد');
  dog.makeSound();     // ريكس ينبح (الدالة المعاد تعريفها)
  dog.eat();           // ريكس يأكل (دالة موروثة)
  dog.fetch();         // ريكس يجلب الكرة (دالة جديدة)
  
  // الوصول للخصائص
  print('اسم الكلب: ${dog.name}');     // خاصية موروثة
  print('سلالة الكلب: ${dog.breed}');  // خاصية جديدة
}

الواجهات (Interfaces)

في دارت، لا توجد كلمة مفتاحية خاصة للواجهات، بل تُستخدم الفئات العادية أو الفئات المجردة (abstract) كواجهات.

// واجهة باستخدام فئة مجردة
abstract class Vehicle {
  // دوال مجردة يجب تنفيذها
  void start();
  void stop();
  
  // دالة مع تنفيذ افتراضي
  void honk() {
    print('بيب بيب!');
  }
}

// تنفيذ الواجهة باستخدام كلمة implements
class Car implements Vehicle {
  @override
  void start() {
    print('السيارة تبدأ بتشغيل المحرك');
  }
  
  @override
  void stop() {
    print('السيارة تتوقف بإيقاف المحرك');
  }
  
  @override
  void honk() {
    print('السيارة تنبه: بوووق!');
  }
}

// تنفيذ واجهات متعددة
abstract class ElectricVehicle {
  void charge();
}

class ElectricCar implements Vehicle, ElectricVehicle {
  @override
  void start() {
    print('السيارة الكهربائية تبدأ بتشغيل المحرك الكهربائي');
  }
  
  @override
  void stop() {
    print('السيارة الكهربائية تتوقف');
  }
  
  @override
  void honk() {
    print('السيارة الكهربائية تنبه بصوت هادئ');
  }
  
  @override
  void charge() {
    print('السيارة الكهربائية تشحن البطارية');
  }
}

void main() {
  var car = Car();
  car.start();
  car.honk();
  car.stop();
  
  var tesla = ElectricCar();
  tesla.start();
  tesla.charge();
  tesla.stop();
  
  // تعدد الأشكال: استخدام كائن من نوع الفئة المشتقة كنوع من الفئة الأساسية
  Vehicle vehicle = Car();
  vehicle.start();  // السيارة تبدأ بتشغيل المحرك
}

التعدادات (Enums)

التعدادات (Enums) هي مجموعة من القيم الثابتة المسماة، وتُستخدم لتمثيل مجموعة محدودة من الخيارات.

التعدادات البسيطة

// تعريف تعداد بسيط
enum Color {
  red,
  green,
  blue,
  yellow,
  black,
  white
}

// تعداد لأيام الأسبوع
enum Day {
  monday,
  tuesday,
  wednesday,
  thursday,
  friday,
  saturday,
  sunday
}

void main() {
  // استخدام قيم التعداد
  var favoriteColor = Color.blue;
  
  // المقارنة مع قيم التعداد
  if (favoriteColor == Color.blue) {
    print('اللون المفضل هو الأزرق');
  }
  
  // الحصول على اسم القيمة كنص
  print(favoriteColor.name);  // blue
  
  // الحصول على قائمة بجميع القيم
  print(Color.values);  // [Color.red, Color.green, Color.blue, Color.yellow, Color.black, Color.white]
  
  // التكرار على جميع القيم
  for (var color in Color.values) {
    print('اسم اللون: ${color.name}');
  }
  
  // الحصول على الموقع (الفهرس)
  print(Day.friday.index);  // 4
}

التعدادات المحسنة (منذ Dart 2.17)

// تعداد محسن مع حقول وطرق
enum Status {
  pending(0, 'قيد الانتظار'),
  active(1, 'نشط'),
  inactive(2, 'غير نشط'),
  deleted(3, 'محذوف');
  
  // حقول
  final int code;
  final String arabicName;
  
  // منشئ ثابت
  const Status(this.code, this.arabicName);
  
  // طريقة على التعداد
  bool get isActive => this == Status.active;
  
  // طريقة أخرى
  String getDescription() {
    return 'الحالة: $arabicName (رمز: $code)';
  }
  
  // طريقة ثابتة
  static Status fromCode(int code) {
    return Status.values.firstWhere(
      (status) => status.code == code,
      orElse: () => Status.pending,
    );
  }
}

void main() {
  var userStatus = Status.active;
  
  print('رمز الحالة: ${userStatus.code}');
  print('اسم الحالة: ${userStatus.arabicName}');
  print('هل نشط؟ ${userStatus.isActive}');
  print(userStatus.getDescription());
  
  // استخدام الطريقة الثابتة
  var statusFromCode = Status.fromCode(2);
  print(statusFromCode);  // Status.inactive
  
  // استخدام التعداد في switch
  switch (userStatus) {
    case Status.pending:
      print('المستخدم قيد الانتظار');
      break;
    case Status.active:
      print('المستخدم نشط');
      break;
    case Status.inactive:
      print('المستخدم غير نشط');
      break;
    case Status.deleted:
      print('المستخدم محذوف');
      break;
  }
}

الـ Mixins

الـ Mixins هي آلية لإعادة استخدام الشيفرة في فئات متعددة دون استخدام الوراثة. يمكنك من خلالها مشاركة الدوال والخصائص بين فئات مختلفة.

تعريف واستخدام Mixin

// تعريف Mixin باستخدام كلمة mixin
mixin Logger {
  void log(String message) {
    print('سجل: $message');
  }
}

// Mixin آخر
mixin Validator {
  bool validateEmail(String email) {
    // تحقق بسيط من وجود @ في البريد الإلكتروني
    return email.contains('@');
  }
  
  bool validatePassword(String password) {
    return password.length >= 8;
  }
}

// استخدام Mixin في فئة باستخدام with
class User with Logger, Validator {
  String username;
  String email;
  
  User(this.username, this.email);
  
  void createUser() {
    if (validateEmail(email)) {
      log('تم إنشاء المستخدم: $username');
    } else {
      log('خطأ: البريد الإلكتروني غير صالح');
    }
  }
}

// استخدام Mixin مع الوراثة
class AdminUser extends User with Logger {
  AdminUser(String username, String email) : super(username, email);
  
  void grantAdminPrivileges() {
    log('تم منح صلاحيات المدير لـ $username');
  }
}

void main() {
  var user = User('ahmed', '[email protected]');
  user.createUser();  // سجل: تم إنشاء المستخدم: ahmed
  
  var invalidUser = User('invalid', 'invalid-email');
  invalidUser.createUser();  // سجل: خطأ: البريد الإلكتروني غير صالح
  
  var admin = AdminUser('admin', '[email protected]');
  admin.createUser();
  admin.grantAdminPrivileges();  // سجل: تم منح صلاحيات المدير لـ admin
}

قيود على Mixins

// Mixin محدود على فئات معينة باستخدام on
mixin CanFly on Bird {
  void fly() {
    print('${getName()} يطير في السماء');
  }
}

// الفئة الأساسية
class Animal {
  String name;
  
  Animal(this.name);
  
  String getName() => name;
}

// فئة وسيطة
class Bird extends Animal {
  Bird(String name) : super(name);
  
  void chirp() {
    print('$name يغرد');
  }
}

// الفئة التي تستخدم Mixin محدد
class Sparrow extends Bird with CanFly {
  Sparrow(String name) : super(name);
}

// لا يمكن استخدام CanFly مع Fish لأن Fish لا ترث من Bird
class Fish extends Animal {
  Fish(String name) : super(name);
  
  void swim() {
    print('$name يسبح');
  }
}

// هذا سيسبب خطأ:
// class FlyingFish extends Fish with CanFly { ... }

void main() {
  var sparrow = Sparrow('العصفور');
  sparrow.chirp();
  sparrow.fly();
}

الامتدادات (Extensions)

الامتدادات (Extensions) تسمح بإضافة دوال وخصائص جديدة للفئات الموجودة، حتى للفئات التي لا تملك مصدر كودها.

إضافة دوال لأنواع موجودة

// إضافة دوال للنصوص (String)
extension StringExtension on String {
  // دالة لقلب النص
  String get reversed => split('').reversed.join('');
  
  // دالة لحساب عدد الكلمات
  int get wordCount => trim().split(RegExp(r'\s+')).length;
  
  // دالة لتحويل النص إلى عنوان (أول حرف من كل كلمة كبير)
  String toTitleCase() {
    if (isEmpty) return this;
    
    return split(' ')
        .map((word) => word.isNotEmpty
            ? '${word[0].toUpperCase()}${word.substring(1)}'
            : '')
        .join(' ');
  }
  
  // دالة تتأكد إذا كان النص بريد إلكتروني
  bool get isValidEmail => 
      RegExp(r'^[a-zA-Z0-9.]+@[a-zA-Z0-9]+\.[a-zA-Z]+').hasMatch(this);
}

// إضافة دوال للأعداد (int)
extension IntExtension on int {
  // دالة تحول العدد إلى صيغة رومانية
  String toRoman() {
    if (this <= 0) return 'غير معرف للأعداد السالبة أو الصفر';
    
    List romanNumerals = ['I', 'IV', 'V', 'IX', 'X', 'XL', 'L', 'XC', 'C', 'CD', 'D', 'CM', 'M'];
    List values = [1, 4, 5, 9, 10, 40, 50, 90, 100, 400, 500, 900, 1000];
    
    String result = '';
    int num = this;
    
    for (int i = values.length - 1; i >= 0; i--) {
      while (num >= values[i]) {
        num -= values[i];
        result += romanNumerals[i];
      }
    }
    
    return result;
  }
  
  // دالة تُرجِع قائمة بمضاعفات العدد حتى حد معين
  List multiplesUntil(int limit) {
    List multiples = [];
    for (int i = this; i <= limit; i += this) {
      multiples.add(i);
    }
    return multiples;
  }
}

void main() {
  // استخدام امتدادات String
  String text = 'مرحبا بالعالم';
  print('النص المعكوس: ${text.reversed}');
  print('عدد الكلمات: ${text.wordCount}');
  
  String name = 'محمد علي';
  print('الاسم كعنوان: ${name.toTitleCase()}');
  
  String email = '[email protected]';
  print('البريد صحيح؟ ${email.isValidEmail}');
  
  // استخدام امتدادات int
  int number = 1984;
  print('الرقم $number باللغة الرومانية: ${number.toRoman()}');
  
  int base = 5;
  print('مضاعفات $base حتى 50: ${base.multiplesUntil(50)}');
}

امتدادات للفئات المخصصة

// فئة مخصصة
class Person {
  String firstName;
  String lastName;
  
  Person(this.firstName, this.lastName);
}

// إضافة امتداد للفئة المخصصة
extension PersonExtension on Person {
  // إضافة خاصية للاسم الكامل
  String get fullName => '$firstName $lastName';
  
  // إضافة دالة
  String greet() {
    return 'مرحبًا، أنا $fullName';
  }
  
  // إضافة طرق مقارنة
  bool equals(Person other) {
    return firstName == other.firstName && lastName == other.lastName;
  }
}

void main() {
  var person1 = Person('أحمد', 'محمد');
  
  // استخدام الامتدادات
  print(person1.fullName);  // أحمد محمد
  print(person1.greet());   // مرحبًا، أنا أحمد محمد
  
  var person2 = Person('أحمد', 'محمد');
  var person3 = Person('محمد', 'علي');
  
  print('person1 يساوي person2؟ ${person1.equals(person2)}');  // true
  print('person1 يساوي person3؟ ${person1.equals(person3)}');  // false
}

المجموعات

توفر دارت ثلاثة أنواع رئيسية من المجموعات (Collections): القوائم (Lists)، المجموعات (Sets)، والخرائط (Maps).

القوائم (Lists)

void main() {
  // إنشاء قائمة فارغة
  List emptyList = [];
  
  // إنشاء قائمة مع قيم
  List fruits = ['تفاح', 'موز', 'برتقال', 'فراولة'];
  
  // الوصول للعناصر
  print(fruits[0]);  // تفاح
  
  // تغيير عنصر
  fruits[1] = 'عنب';
  print(fruits);  // [تفاح, عنب, برتقال, فراولة]
  
  // إضافة عناصر
  fruits.add('مانجو');
  
  // إضافة عدة عناصر
  fruits.addAll(['كيوي', 'أناناس']);
  
  // طول القائمة
  print('عدد الفواكه: ${fruits.length}');
  
  // فحص وجود عنصر
  bool hasApple = fruits.contains('تفاح');
  print('هل توجد فاكهة التفاح؟ $hasApple');
  
  // الحصول على الفهرس
  int index = fruits.indexOf('برتقال');
  print('فهرس البرتقال: $index');
  
  // حذف عنصر
  fruits.remove('مانجو');
  
  // حذف عنصر بالفهرس
  fruits.removeAt(0);
  
  // التكرار على العناصر
  for (var fruit in fruits) {
    print('فاكهة: $fruit');
  }
  
  // استخدام forEach
  fruits.forEach((fruit) => print('فاكهة مع forEach: $fruit'));
  
  // استخدام map لإنشاء قائمة جديدة
  List upperFruits = fruits.map((f) => f.toUpperCase()).toList();
  print(upperFruits);
  
  // استخدام where للتصفية
  List longNameFruits = fruits.where((f) => f.length > 4).toList();
  print('الفواكه ذات الأسماء الطويلة: $longNameFruits');
  
  // ترتيب القائمة
  fruits.sort();
  print('بعد الترتيب: $fruits');
  
  // القوائم غير القابلة للتعديل
  List numbers = const [1, 2, 3];
  // numbers.add(4);  // خطأ! القائمة غير قابلة للتعديل
  
  // تحويل قائمة قابلة للتعديل إلى غير قابلة للتعديل
  List fixedFruits = List.unmodifiable(fruits);
  // fixedFruits.add('تين');  // خطأ!
}

المجموعات (Sets)

void main() {
  // إنشاء مجموعة فارغة
  Set emptySet = {};
  
  // إنشاء مجموعة مع قيم
  Set numbers = {1, 2, 3, 4, 5};
  
  // المجموعات لا تسمح بالتكرار
  Set uniqueNames = {'أحمد', 'محمد', 'علي', 'أحمد'};
  print(uniqueNames);  // {أحمد, محمد, علي} - لاحظ أن 'أحمد' يظهر مرة واحدة فقط
  
  // إضافة عنصر
  numbers.add(6);
  
  // إضافة عناصر متعددة
  numbers.addAll({7, 8, 9});
  
  // فحص وجود عنصر
  bool hasThree = numbers.contains(3);
  print('هل المجموعة تحتوي على 3؟ $hasThree');
  
  // حذف عنصر
  numbers.remove(9);
  
  // التكرار على المجموعة
  for (var number in numbers) {
    print('رقم: $number');
  }
  
  // عمليات المجموعات
  Set setA = {1, 2, 3, 4};
  Set setB = {3, 4, 5, 6};
  
  // الاتحاد (union)
  Set union = setA.union(setB);
  print('الاتحاد: $union');  // {1, 2, 3, 4, 5, 6}
  
  // التقاطع (intersection)
  Set intersection = setA.intersection(setB);
  print('التقاطع: $intersection');  // {3, 4}
  
  // الفرق (difference)
  Set difference = setA.difference(setB);
  print('الفرق (A - B): $difference');  // {1, 2}
  
  // تحويل المجموعة إلى قائمة
  List numbersList = numbers.toList();
  
  // تحويل قائمة إلى مجموعة (لإزالة التكرار)
  List repeatedNames = ['سارة', 'محمد', 'سارة', 'أحمد', 'محمد'];
  Set uniqueNamesFromList = repeatedNames.toSet();
  print('الأسماء الفريدة: $uniqueNamesFromList');
}

الخرائط (Maps)

void main() {
  // إنشاء خريطة فارغة
  Map emptyMap = {};
  
  // إنشاء خريطة مع أزواج المفتاح-القيمة
  Map ages = {
    'أحمد': 30,
    'محمد': 25,
    'سارة': 28,
    'فاطمة': 22
  };
  
  // الوصول للقيم باستخدام المفتاح
  print('عمر أحمد: ${ages['أحمد']}');
  
  // التحقق من وجود مفتاح
  bool hasSara = ages.containsKey('سارة');
  print('هل سارة موجودة؟ $hasSara');
  
  // التحقق من وجود قيمة
  bool hasAge30 = ages.containsValue(30);
  print('هل يوجد شخص عمره 30؟ $hasAge30');
  
  // إضافة أو تحديث قيمة
  ages['خالد'] = 35;
  ages['أحمد'] = 31;  // تحديث قيمة موجودة
  
  // الحصول على جميع المفاتيح
  Iterable names = ages.keys;
  print('جميع الأسماء: $names');
  
  // الحصول على جميع القيم
  Iterable allAges = ages.values;
  print('جميع الأعمار: $allAges');
  
  // حذف زوج المفتاح-القيمة
  ages.remove('فاطمة');
  
  // التكرار على أزواج المفتاح-القيمة
  ages.forEach((name, age) {
    print('$name: $age سنة');
  });
  
  // التكرار باستخدام entries
  for (var entry in ages.entries) {
    print('${entry.key}: ${entry.value} سنة');
  }
  
  // تحويل الخريطة (تطبيق دالة على كل قيمة)
  Map ageDescriptions = ages.map(
    (name, age) => MapEntry(name, '$age سنة')
  );
  print(ageDescriptions);
  
  // إنشاء خريطة من القوائم
  List students = ['علي', 'حسن', 'منى'];
  List grades = [85, 92, 78];
  
  Map studentGrades = Map.fromIterables(students, grades);
  print('درجات الطلاب: $studentGrades');
}

الأنواع العامة (Generics)

الأنواع العامة (Generics) تسمح بكتابة كود يمكن إعادة استخدامه مع أنواع مختلفة من البيانات.

استخدام الأنواع العامة مع المجموعات

void main() {
  // قائمة من نوع string
  List names = ['أحمد', 'محمد', 'علي'];
  
  // قائمة من نوع int
  List numbers = [1, 2, 3, 4, 5];
  
  // خريطة من string إلى int
  Map scores = {
    'أحمد': 85,
    'محمد': 92,
    'علي': 78
  };
  
  // خريطة من string إلى List
  Map> studentScores = {
    'أحمد': [85, 90, 78],
    'محمد': [92, 88, 94],
    'علي': [78, 85, 80]
  };
  
  // استخدام دالة تعمل مع أنواع مختلفة
  printItems(names);
  printItems(numbers);
}

// دالة عامة تقبل قائمة من أي نوع
void printItems(List items) {
  for (T item in items) {
    print('العنصر: $item');
  }
}

إنشاء فئات عامة

// فئة عامة لتخزين قيمة من أي نوع
class Box {
  T value;
  
  Box(this.value);
  
  T getValue() {
    return value;
  }
  
  void setValue(T newValue) {
    value = newValue;
  }
  
  String toString() {
    return 'Box<$T>($value)';
  }
}

// فئة عامة تستقبل نوعين
class Pair {
  K first;
  V second;
  
  Pair(this.first, this.second);
  
  @override
  String toString() {
    return 'Pair<$K, $V>($first, $second)';
  }
}

void main() {
  // استخدام Box مع أنواع مختلفة
  var stringBox = Box('مرحبا');
  var intBox = Box(42);
  var doubleBox = Box(3.14);
  
  print(stringBox);  // Box(مرحبا)
  print(intBox);     // Box(42)
  print(doubleBox);  // Box(3.14)
  
  // تغيير القيم
  stringBox.setValue('عالم');
  intBox.setValue(100);
  
  print('قيمة stringBox الجديدة: ${stringBox.getValue()}');
  print('قيمة intBox الجديدة: ${intBox.getValue()}');
  
  // استخدام Pair مع أنواع مختلفة
  var nameAge = Pair('أحمد', 30);
  var pointCoordinates = Pair(10.5, 20.7);
  
  print(nameAge);  // Pair(أحمد, 30)
  print(pointCoordinates);  // Pair(10.5, 20.7)
}

تقييد الأنواع العامة

// فئة أساسية
class Animal {
  String name;
  
  Animal(this.name);
  
  void makeSound() {
    print('صوت حيوان عام');
  }
}

// فئات مشتقة
class Dog extends Animal {
  Dog(String name) : super(name);
  
  @override
  void makeSound() {
    print('$name ينبح');
  }
  
  void fetch() {
    print('$name يجلب الكرة');
  }
}

class Cat extends Animal {
  Cat(String name) : super(name);
  
  @override
  void makeSound() {
    print('$name يموء');
  }
  
  void purr() {
    print('$name يخرخر');
  }
}

// فئة عامة مقيدة بنوع Animal أو مشتقاته
class AnimalShelter {
  List animals = [];
  
  void addAnimal(T animal) {
    animals.add(animal);
  }
  
  void makeAllSounds() {
    for (var animal in animals) {
      animal.makeSound();
    }
  }
}

void main() {
  // إنشاء ملجأ للكلاب
  var dogShelter = AnimalShelter();
  dogShelter.addAnimal(Dog('ريكس'));
  dogShelter.addAnimal(Dog('بادي'));
  
  // إنشاء ملجأ للقطط
  var catShelter = AnimalShelter();
  catShelter.addAnimal(Cat('ميمي'));
  catShelter.addAnimal(Cat('لولو'));
  
  // سماع أصوات جميع الحيوانات
  print('أصوات الكلاب:');
  dogShelter.makeAllSounds();
  
  print('أصوات القطط:');
  catShelter.makeAllSounds();
  
  // لا يمكن إنشاء ملجأ لنوع غير مشتق من Animal
  // var stringShelter = AnimalShelter();  // خطأ!
}

البرمجة غير المتزامنة

البرمجة غير المتزامنة تسمح بتنفيذ العمليات التي تستغرق وقتًا (مثل قراءة الملفات أو طلبات الشبكة) دون تعطيل تنفيذ البرنامج الرئيسي.

المستقبليات (Futures)

import 'dart:async';

// دالة تُرجع Future
Future fetchUserData() {
  // محاكاة عملية تستغرق وقتًا (مثل طلب شبكة)
  return Future.delayed(Duration(seconds: 2), () {
    return 'بيانات المستخدم: {"name": "أحمد", "email": "[email protected]"}';
  });
}

// دالة تقوم بتحميل معلومات المستخدم وقد تفشل
Future fetchUserProfile(String userId) {
  return Future.delayed(Duration(seconds: 1), () {
    if (userId == 'admin') {
      return 'معلومات المدير';
    } else {
      throw Exception('فشل تحميل معلومات المستخدم: $userId');
    }
  });
}

void main() {
  print('بداية البرنامج');
  
  // استخدام Future مع then و catchError
  fetchUserData()
    .then((data) => print('تم استلام البيانات: $data'))
    .catchError((error) => print('حدث خطأ: $error'))
    .whenComplete(() => print('اكتملت العملية'));
  
  print('جاري تحميل البيانات...');
  
  // تجربة العملية التي قد تفشل
  fetchUserProfile('user123')
    .then((profile) => print(profile))
    .catchError((error) => print('خطأ: $error'));
    fetchUserProfile('admin')
    .then((profile) => print(profile))
    .catchError((error) => print('خطأ: $error'));
  
  print('نهاية الكود الرئيسي');
}

async و await

import 'dart:async';

// دالة غير متزامنة تستخدم async و await
Future fetchData() async {
  print('بداية تحميل البيانات');
  
  try {
    // استخدام await لانتظار اكتمال المستقبل
    String userData = await fetchUserData();
    print('تم استلام البيانات: $userData');
    
    // استدعاء غير متزامن آخر
    String adminProfile = await fetchUserProfile('admin');
    print('معلومات المدير: $adminProfile');
    
    // محاولة تحميل معلومات مستخدم غير موجود
    try {
      String invalidProfile = await fetchUserProfile('غير موجود');
      print(invalidProfile);
    } catch (e) {
      print('تعذر تحميل معلومات المستخدم: $e');
    }
  } catch (error) {
    print('حدث خطأ أثناء تحميل البيانات: $error');
  } finally {
    print('اكتملت عملية تحميل البيانات');
  }
}

void main() async {
  print('بداية البرنامج');
  
  // استدعاء الدالة غير المتزامنة باستخدام await
  await fetchData();
  
  print('نهاية البرنامج');
}

العمليات المتوازية

import 'dart:async';

// دوال تُرجع مستقبليات
Future fetchUserData() {
  return Future.delayed(Duration(seconds: 2), () => 'بيانات المستخدم');
}

Future fetchProducts() {
  return Future.delayed(Duration(seconds: 1), () => 'قائمة المنتجات');
}

Future fetchOrders() {
  return Future.delayed(Duration(seconds: 3), () => 'طلبات المستخدم');
}

// تنفيذ مستقبليات بالتتابع
Future fetchSequentially() async {
  final stopwatch = Stopwatch()..start();
  
  print('بدء التحميل المتتابع...');
  
  final userData = await fetchUserData();
  print('تم تحميل: $userData');
  
  final products = await fetchProducts();
  print('تم تحميل: $products');
  
  final orders = await fetchOrders();
  print('تم تحميل: $orders');
  
  print('اكتمل التحميل المتتابع في ${stopwatch.elapsedMilliseconds} مللي ثانية');
}

// تنفيذ مستقبليات بالتوازي
Future fetchParallel() async {
  final stopwatch = Stopwatch()..start();
  
  print('بدء التحميل المتوازي...');
  
  // بدء جميع العمليات في نفس الوقت
  final userDataFuture = fetchUserData();
  final productsFuture = fetchProducts();
  final ordersFuture = fetchOrders();
  
  // انتظار اكتمال الجميع
  final userData = await userDataFuture;
  print('تم تحميل: $userData');
  
  final products = await productsFuture;
  print('تم تحميل: $products');
  
  final orders = await ordersFuture;
  print('تم تحميل: $orders');
  
  print('اكتمل التحميل المتوازي في ${stopwatch.elapsedMilliseconds} مللي ثانية');
}

// تنفيذ مستقبليات بالتوازي باستخدام Future.wait
Future fetchWithFutureWait() async {
  final stopwatch = Stopwatch()..start();
  
  print('بدء التحميل باستخدام Future.wait...');
  
  final results = await Future.wait([
    fetchUserData(),
    fetchProducts(),
    fetchOrders(),
  ]);
  
  print('تم تحميل: ${results[0]}');  // بيانات المستخدم
  print('تم تحميل: ${results[1]}');  // قائمة المنتجات
  print('تم تحميل: ${results[2]}');  // طلبات المستخدم
  
  print('اكتمل التحميل باستخدام Future.wait في ${stopwatch.elapsedMilliseconds} مللي ثانية');
}

void main() async {
  // تنفيذ مستقبليات بالتتابع
  await fetchSequentially();
  print('');
  
  // تنفيذ مستقبليات بالتوازي
  await fetchParallel();
  print('');
  
  // تنفيذ مستقبليات باستخدام Future.wait
  await fetchWithFutureWait();
}

تدفقات البيانات (Streams)

import 'dart:async';

// إنشاء تدفق بيانات بسيط
Stream countStream(int max) async* {
  for (int i = 1; i <= max; i++) {
    await Future.delayed(Duration(seconds: 1));
    yield i;  // إرسال قيمة للتدفق
  }
}

// إنشاء تدفق بيانات من Future
Stream getUserUpdates() {
  final controller = StreamController();
  
  // محاكاة تلقي تحديثات
  Future.delayed(Duration(seconds: 1), () {
    controller.add('المستخدم قام بتسجيل الدخول');
    
    Future.delayed(Duration(seconds: 2), () {
      controller.add('المستخدم يشاهد المنتجات');
      
      Future.delayed(Duration(seconds: 1), () {
        controller.add('المستخدم أضاف منتجًا للسلة');
        
        Future.delayed(Duration(seconds: 3), () {
          controller.add('المستخدم قام بالدفع');
          controller.close();  // إغلاق التدفق
        });
      });
    });
  });
  
  return controller.stream;
}

void main() async {
  // الاستماع لتدفق العداد
  print('بدء الاستماع للعداد...');
  final subscription = countStream(5).listen(
    (data) => print('تلقي بيانات: $data'),
    onError: (error) => print('خطأ: $error'),
    onDone: () => print('اكتمل العداد'),
    cancelOnError: false
  );
  
  // يمكن إلغاء الاشتراك في أي وقت
  // await Future.delayed(Duration(seconds: 2));
  // subscription.cancel();
  // print('تم إلغاء الاشتراك');
  
  // استخدام await for مع Streams
  print('\nبدء تلقي تحديثات المستخدم...');
  await for (final update in getUserUpdates()) {
    print('تحديث: $update');
  }
  print('اكتملت تحديثات المستخدم');
  
  // تحويل Stream
  print('\nتحويل تدفق البيانات:');
  final transformedStream = countStream(3)
    .map((value) => 'العدد: $value')
    .where((event) => event.contains('2') || event.contains('3'));
  
  await for (final value in transformedStream) {
    print(value);
  }
}

التعامل مع الاستثناءات

الاستثناءات (Exceptions) هي أخطاء تحدث أثناء تنفيذ البرنامج. يمكن التقاط ومعالجة هذه الأخطاء باستخدام try-catch.

التقاط الاستثناءات

void main() {
  // try-catch بسيط
  try {
    var result = 10 ~/ 0;  // سيولد خطأ IntegerDivisionByZeroException
    print('النتيجة: $result');  // لن يصل التنفيذ إلى هنا
  } catch (e) {
    print('حدث خطأ: $e');
  }
  
  // التقاط أنواع محددة من الاستثناءات
  try {
    List numbers = [1, 2, 3];
    print(numbers[10]);  // سيولد خطأ RangeError
  } on RangeError {
    print('خطأ: محاولة الوصول لعنصر خارج نطاق القائمة');
  } on FormatException {
    print('خطأ: تنسيق غير صحيح');
  } catch (e) {
    print('خطأ غير متوقع: $e');
  }
  
  // استخدام finally
  try {
    print('محاولة تنفيذ عملية قد تفشل');
    if (DateTime.now().second % 2 == 0) {
      throw Exception('حدث خطأ مخصص');
    }
  } catch (e) {
    print('تم التقاط الخطأ: $e');
  } finally {
    print('سيتم تنفيذ هذا الكود دائمًا، بغض النظر عن نجاح أو فشل العملية');
  }
}

إنشاء استثناءات مخصصة

// استثناء مخصص
class InsufficientFundsException implements Exception {
  final double available;
  final double required;
  
  InsufficientFundsException(this.available, this.required);
  
  @override
  String toString() {
    return 'رصيد غير كاف: متاح $available، مطلوب $required';
  }
}

// فئة تستخدم الاستثناء المخصص
class BankAccount {
  String owner;
  double _balance;
  
  BankAccount(this.owner, [this._balance = 0]);
  
  double get balance => _balance;
  
  void deposit(double amount) {
    if (amount <= 0) {
      throw ArgumentError('يجب أن يكون مبلغ الإيداع أكبر من صفر');
    }
    _balance += amount;
  }
  
  void withdraw(double amount) {
    if (amount <= 0) {
      throw ArgumentError('يجب أن يكون مبلغ السحب أكبر من صفر');
    }
    
    if (_balance < amount) {
      throw InsufficientFundsException(_balance, amount);
    }
    
    _balance -= amount;
  }
}

void main() {
  var account = BankAccount('أحمد', 1000);
  
  // إيداع صحيح
  try {
    account.deposit(500);
    print('الرصيد بعد الإيداع: ${account.balance}');
  } catch (e) {
    print('فشل الإيداع: $e');
  }
  
  // إيداع غير صحيح
  try {
    account.deposit(-100);
  } catch (e) {
    print('فشل الإيداع: $e');
  }
  
  // سحب صحيح
  try {
    account.withdraw(800);
    print('الرصيد بعد السحب: ${account.balance}');
  } catch (e) {
    print('فشل السحب: $e');
  }
  
  // سحب أكبر من الرصيد
  try {
    account.withdraw(1000);
  } on InsufficientFundsException catch (e) {
    print('عملية سحب غير ممكنة: $e');
    print('المبلغ الإضافي المطلوب: ${e.required - e.available}');
  } catch (e) {
    print('فشل السحب لسبب آخر: $e');
  }
}

تتبع ستاك الاستثناءات

void level3() {
  throw Exception('حدث خطأ في المستوى 3');
}

void level2() {
  try {
    level3();
  } catch (e) {
    print('تم التقاط الخطأ في المستوى 2 ولكن سيتم إعادة رميه');
    rethrow;  // إعادة رمي الاستثناء للمستوى الأعلى
  }
}

void level1() {
  try {
    level2();
  } catch (e, stackTrace) {
    print('تم التقاط الخطأ في المستوى 1');
    print('الخطأ: $e');
    print('تتبع الستاك:');
    print(stackTrace);
  }
}

void main() {
  level1();
}

المكتبات والاستيراد

المكتبات (Libraries) تساعد في تنظيم الكود وتقسيمه إلى وحدات قابلة لإعادة الاستخدام.

استيراد المكتبات الأساسية

// استيراد مكتبات دارت الأساسية
import 'dart:math';  // للعمليات الرياضية
import 'dart:io';    // للمدخلات/المخرجات
import 'dart:convert';  // للترميز وفك الترميز (مثل JSON)
import 'dart:async';  // للبرمجة غير المتزامنة

void main() {
  // استخدام دوال من مكتبة dart:math
  print('القيمة التقريبية لـ π: ${pi}');
  print('الجذر التربيعي لـ 16: ${sqrt(16)}');
  print('رقم عشوائي: ${Random().nextInt(100)}');
  
  // استخدام دوال من مكتبة dart:convert
  Map user = {
    'name': 'أحمد',
    'age': 30,
    'email': '[email protected]'
  };
  
  // تحويل Map إلى JSON
  String jsonString = jsonEncode(user);
  print('سلسلة JSON: $jsonString');
  
  // تحويل JSON إلى Map
  Map parsedJson = jsonDecode(jsonString);
  print('Map من JSON: $parsedJson');
  
  // استخدام مكتبة dart:io (قراءة مدخلات المستخدم)
  // ملاحظة: هذا يعمل فقط في برامج المنفذة بواسطة Dart VM وليس في تطبيقات الويب
  // print('أدخل اسمك:');
  // String? name = stdin.readLineSync();
  // print('مرحبًا $name!');
}

إنشاء واستخدام مكتبات مخصصة

عادةً، ستكون هذه الملفات في مشروع Dart الخاص بك.

// ملف: math_utils.dart
// مكتبة للعمليات الرياضية

// دالة لحساب مجموع قائمة من الأرقام
num sum(List numbers) {
  return numbers.fold(0, (a, b) => a + b);
}

// دالة لحساب المتوسط
double average(List numbers) {
  if (numbers.isEmpty) return 0;
  return sum(numbers) / numbers.length;
}

// دالة لإيجاد أكبر رقم
num max(List numbers) {
  if (numbers.isEmpty) throw ArgumentError('القائمة فارغة');
  return numbers.reduce((a, b) => a > b ? a : b);
}

// متغير للاستخدام العام
const double PI = 3.14159265359;

// دالة خاصة (لا يمكن استيرادها من خارج المكتبة)
double _calculateCircumference(double radius) {
  return 2 * PI * radius;
}

// دالة عامة تستخدم الدالة الخاصة
double calculateCircleArea(double radius) {
  return PI * radius * radius;
}
// ملف: string_utils.dart
// مكتبة لمعالجة النصوص

// دالة لعكس النص
String reverse(String text) {
  return text.split('').reversed.join('');
}

// دالة لحساب عدد الكلمات
int countWords(String text) {
  if (text.trim().isEmpty) return 0;
  return text.trim().split(RegExp(r'\s+')).length;
}

// دالة للتحقق من التناظر (Palindrome)
bool isPalindrome(String text) {
  String cleanText = text.toLowerCase().replaceAll(RegExp(r'[^a-z0-9]'), '');
  return cleanText == reverse(cleanText);
}
// ملف: main.dart
// استيراد المكتبات المخصصة
import 'math_utils.dart';
import 'string_utils.dart';

// يمكنك استيراد دوال محددة فقط
// import 'math_utils.dart' show sum, average;

// يمكنك استيراد كل شيء إلا دوال محددة
// import 'string_utils.dart' hide isPalindrome;

// استيراد باسم مستعار لتجنب تضارب الأسماء
// import 'another_library.dart' as alib;

void main() {
  // استخدام دوال من math_utils
  List numbers = [5, 8, 12, 3, 9];
  print('المجموع: ${sum(numbers)}');
  print('المتوسط: ${average(numbers)}');
  print('أكبر رقم: ${max(numbers)}');
  print('قيمة باي: $PI');
  print('مساحة الدائرة: ${calculateCircleArea(5)}');
  
  // لا يمكن استدعاء الدالة الخاصة
  // print(_calculateCircumference(5));  // خطأ!
  
  // استخدام دوال من string_utils
  String text = 'مرحبًا بالعالم العربي';
  print('النص المعكوس: ${reverse(text)}');
  print('عدد الكلمات: ${countWords(text)}');
  
  String palindrome = 'ماهام';
  print('هل "$palindrome" متناظر؟ ${isPalindrome(palindrome)}');
  
  // استخدام المكتبات المستوردة بأسماء مستعارة
  // print(alib.someFunction());
}

أمان القيم الفارغة (Null Safety)

أمان القيم الفارغة (Null Safety) هو ميزة مهمة في دارت تساعد على تجنب الأخطاء الناتجة عن التعامل مع القيم الفارغة (null). منذ الإصدار 2.12، أصبحت هذه الميزة أساسية في اللغة.

الأنواع الآمنة مقابل الأنواع القابلة للقيم الفارغة

void main() {
  // متغيرات لا يمكن أن تكون فارغة (non-nullable)
  String name = 'أحمد';
  int age = 30;
  bool isActive = true;
  
  // لا يمكن تعيين null لهذه المتغيرات
  // name = null;  // خطأ!
  // age = null;   // خطأ!
  
  // متغيرات يمكن أن تكون فارغة (nullable) باستخدام ?
  String? nullableName;
  int? nullableAge;
  bool? nullableFlag;
  
  // يمكن تعيين null
  nullableName = null;  // صحيح
  nullableAge = null;   // صحيح
  
  // يمكن أيضًا تعيين قيم عادية
  nullableName = 'محمد';
  nullableAge = 25;
  
  print('الاسم: $nullableName');
  print('العمر: $nullableAge');
}

عوامل الوصول الآمنة

class User {
  String name;
  int age;
  
  User(this.name, this.age);
  
  String getInfo() => 'الاسم: $name، العمر: $age';
}

void main() {
  // متغير يمكن أن يكون null
  User? user;
  
  // استخدام عامل الوصول الآمن (?)
  // بدون عامل الوصول الآمن، سيحدث خطأ
  print('طول الاسم: ${user?.name.length}');
  
  // استخدام عامل الإرجاع الافتراضي (??)
  String userName = user?.name ?? 'زائر';
  print('اسم المستخدم: $userName');
  
  // استخدام عامل التأكيد (!)
  user = User('سارة', 28);
  String info = user!.getInfo();  // أنا متأكد أن user ليس null
  print(info);
  
  // الاختبار قبل الاستخدام
  if (user != null) {
    print('المستخدم موجود: ${user.name}');
  }
  
  // استخدام المعاملات الشرطية
  user?.age = 29;  // تعيين العمر فقط إذا كان user ليس null
  
  // التحقق من النوع وعدم الفراغ في نفس الوقت
  if (user is User && user.age > 18) {
    print('المستخدم بالغ');
  }
}

التهيئة المتأخرة باستخدام late

class Product {
  // خصائص تُهيأ لاحقًا
  late String name;
  late double price;
  late String description;
  
  // خاصية مع تهيئة كسولة (تُحسب عند الوصول إليها أول مرة)
  late String id = _generateId();
  
  // خاصية غير قابلة للقيمة الفارغة ولكن لن تُهيأ في المُنشئ
  late final String sku;
  
  // المُنشئ
  Product(this.name, this.price) {
    description = 'منتج $name';
  }
  
  // دالة مساعدة
  String _generateId() {
    print('توليد معرّف فريد...');
    return DateTime.now().millisecondsSinceEpoch.toString();
  }
}

void main() {
  var product = Product('هاتف ذكي', 999.99);
  
  // لم يتم توليد معرّف حتى الآن
  print('تم إنشاء المنتج');
  
  // تعيين sku (late final)
  product.sku = 'PHN-1234';
  // product.sku = 'OTHER';  // خطأ: لا يمكن تعيين قيمة final مرة أخرى
  
  // الآن سيتم توليد معرّف عند الوصول إليه
  print('معرّف المنتج: ${product.id}');
  
  // الوصول مرة أخرى لن يعيد توليد المعرّف
  print('معرّف المنتج مرة أخرى: ${product.id}');
  
  // الوصول للخصائص الأخرى
  print('${product.name}: ${product.price} ريال');
  print('الوصف: ${product.description}');
  print('SKU: ${product.sku}');
  
  // إذا حاولنا الوصول لخاصية late غير مُهيأة، سنحصل على استثناء وقت التشغيل
  Product emptyProduct = Product('', 0);
  // print(emptyProduct.sku);  // خطأ وقت التشغيل: LateInitializationError
}

البيانات الوصفية (Metadata)

البيانات الوصفية (Metadata) في دارت توفر معلومات إضافية حول الكود، وتبدأ بعلامة @ وتسمى أيضًا annotations.

البيانات الوصفية المدمجة

class Parent {
  void walk() {
    print('المشي بسرعة عادية');
  }
}

class Child extends Parent {
  // @override تشير إلى أن هذه الدالة تعيد تعريف دالة من الفئة الأساسية
  @override
  void walk() {
    print('المشي بسرعة أقل');
  }
  
  // دالة مهملة (لا يُنصح باستخدامها)
  @deprecated
  void oldMethod() {
    print('هذه الدالة قديمة، استخدم newMethod بدلاً منها');
  }
  
  // الدالة البديلة
  void newMethod() {
    print('هذه هي الدالة الجديدة');
  }
}

// @immutable تشير إلى أن كل الخصائص يجب أن تكون final
// هذه تأتي من حزمة meta
// import 'package:meta/meta.dart';
// @immutable
class Point {
  final int x;
  final int y;
  
  const Point(this.x, this.y);
}

void main() {
  var child = Child();
  child.walk();
  
  // استخدام دالة مهملة سيظهر تحذيرًا في IDE
  child.oldMethod();
  child.newMethod();
  
  var point = Point(10, 20);
  print('النقطة: (${point.x}, ${point.y})');
}

إنشاء بيانات وصفية مخصصة

// تعريف بيانات وصفية مخصصة
class Todo {
  final String description;
  final String assignee;
  final DateTime dueDate;
  
  const Todo(this.description, {this.assignee = '', DateTime? dueDate}) 
    : this.dueDate = dueDate ?? DateTime.now().add(Duration(days: 7));
}

class Author {
  final String name;
  final String email;
  
  const Author(this.name, this.email);
}

// استخدام البيانات الوصفية المخصصة
@Author('أحمد محمد', '[email protected]')
class ProjectManager {
  
  @Todo('إضافة مزيد من الوثائق', assignee: 'سارة')
  void assignTasks() {
    print('توزيع المهام على الفريق');
  }
  
  @Todo(
    'تحسين الأداء عن طريق تقليل الاستدعاءات', 
    assignee: 'محمد',
    dueDate: null
  )
  void generateReports() {
    print('إنشاء تقارير المشروع');
  }
}

void main() {
  var manager = ProjectManager();
  manager.assignTasks();
  manager.generateReports();
  
  // يمكن الوصول للبيانات الوصفية وقت التشغيل باستخدام reflection
  // ملاحظة: هذا يتطلب حزمة 'dart:mirrors' وهي غير متوفرة في Flutter
}

العزلات (Isolates)

العزلات (Isolates) هي وحدات منفصلة للتنفيذ في دارت، تمكّن من إجراء العمليات المتوازية. كل عزلة لها مساحة ذاكرة خاصة بها، بحيث لا تتشارك الحالة مع العزلات الأخرى.

استخدام العزلات للمعالجة المتوازية

import 'dart:isolate';
import 'dart:math';

// الدالة التي ستعمل في العزلة المنفصلة
void calculatePrimes(SendPort sendPort) {
  // البحث عن الأعداد الأولية حتى 200000
  List primes = [];
  
  bool isPrime(int n) {
    if (n <= 1) return false;
    if (n <= 3) return true;
    if (n % 2 == 0 || n % 3 == 0) return false;
    
    int i = 5;
    while (i * i <= n) {
      if (n % i == 0 || n % (i + 2) == 0) return false;
      i += 6;
    }
    return true;
  }
  
  for (int i = 2; i < 50000; i++) {
    if (isPrime(i)) {
      primes.add(i);
    }
  }
  
  // إرسال النتيجة للعزلة الرئيسية
  sendPort.send(primes.length);
}

// دالة تقوم بعملية حسابية مكثفة
int calculateSum(int max) {
  int sum = 0;
  for (int i = 0; i < max; i++) {
    sum += i;
  }
  return sum;
}

void main() async {
  print('بدء البرنامج');
  
  // إنشاء قناة اتصال بين العزلات
  final receivePort = ReceivePort();
  
  // إطلاق عزلة جديدة
  print('بدء حساب الأعداد الأولية في عزلة منفصلة...');
  await Isolate.spawn(calculatePrimes, receivePort.sendPort);
  
  // في نفس الوقت، نقوم بعملية أخرى في العزلة الرئيسية
  print('حساب المجموع في العزلة الرئيسية...');
  final sum = calculateSum(1000000000);
  print('تم حساب المجموع: $sum');
  
  // انتظار النتيجة من العزلة المنفصلة
  final numberOfPrimes = await receivePort.first;
  print('تم العثور على $numberOfPrimes عدد أولي');
  
  print('انتهى البرنامج');
}

التواصل بين العزلات

import 'dart:isolate';

// مهمة سيتم تنفيذها في عزلة منفصلة
void workerIsolate(List args) {
  // استخراج بوابة الإرسال وبيانات المهمة
  SendPort sendPort = args[0];
  String taskName = args[1];
  int delay = args[2];
  
  // إنشاء بوابة استقبال للتواصل ثنائي الاتجاه
  final receivePort = ReceivePort();
  
  // إرسال بوابة الاستقبال للعزلة الرئيسية
  sendPort.send(receivePort.sendPort);
  
  // الاستماع للرسائل من العزلة الرئيسية
  receivePort.listen((message) {
    if (message == 'start') {
      // بدء تنفيذ المهمة
      print('العزلة: بدء تنفيذ المهمة "$taskName"');
      
      // محاكاة عملية تستغرق وقتًا
      Future.delayed(Duration(seconds: delay), () {
        // إعادة النتيجة للعزلة الرئيسية
        sendPort.send('اكتملت المهمة "$taskName" بعد $delay ثوان');
        
        // إغلاق بوابة الاستقبال
        receivePort.close();
      });
    } else if (message == 'abort') {
      print('العزلة: تم إلغاء المهمة "$taskName"');
      sendPort.send('تم إلغاء المهمة "$taskName"');
      receivePort.close();
    }
  });
}

void main() async {
  // بوابة استقبال للعزلة الرئيسية
  final mainReceivePort = ReceivePort();
  
  // إطلاق عزلة عامل مع اسم المهمة والتأخير
  final isolate = await Isolate.spawn(
    workerIsolate, 
    [mainReceivePort.sendPort, 'معالجة البيانات', 3]
  );
  
  // انتظار بوابة الإرسال من العزلة العاملة
  SendPort workerSendPort = await mainReceivePort.first;
  
  // إنشاء بوابة استقبال للرسائل من العزلة العاملة
  final resultReceivePort = ReceivePort();
  
  // الاستماع للرسائل
  resultReceivePort.listen((message) {
    print('العزلة الرئيسية: تم استلام رسالة من العزلة العاملة: $message');
  });
  
  // إرسال رسائل للعزلة العاملة
  workerSendPort.send('start');
  
  // بعد 5 ثوان سنطلق عزلة أخرى
  await Future.delayed(Duration(seconds: 1));
  
  // إطلاق عزلة عامل ثانية
  final secondReceivePort = ReceivePort();
  final secondIsolate = await Isolate.spawn(
    workerIsolate, 
    [secondReceivePort.sendPort, 'تحليل النتائج', 5]
  );
  
  // انتظار بوابة الإرسال من العزلة الثانية
  SendPort secondWorkerSendPort = await secondReceivePort.first;
  
  // إنشاء بوابة استقبال للعزلة الثانية
  final secondResultPort = ReceivePort();
  
  // الاستماع للرسائل من العزلة الثانية
  secondResultPort.listen((message) {
    print('العزلة الرئيسية: رسالة من العزلة الثانية: $message');
  });
  
  // بدء تنفيذ المهمة الثانية
  secondWorkerSendPort.send('start');
  
  // إلغاء العزلة الأولى (اختياري)
  // isolate.kill(priority: Isolate.immediate);
  
  // لمنع انتهاء البرنامج قبل اكتمال المهام
  await Future.delayed(Duration(seconds: 6));
  
  // إغلاق بوابات الاستقبال
  resultReceivePort.close();
  secondResultPort.close();
  mainReceivePort.close();
  secondReceivePort.close();
  
  // إنهاء العزلات
  isolate.kill(priority: Isolate.immediate);
  secondIsolate.kill(priority: Isolate.immediate);
}

الخلاصة والموارد الإضافية

هنيئًا! لقد وصلت إلى نهاية هذا الدليل الشامل للغة دارت. لقد تعلمت:

  • أساسيات لغة دارت وكيفية إعداد بيئة التطوير
  • التعامل مع المتغيرات، والعمليات، وهياكل التحكم
  • الدوال والبرمجة الكائنية (الفئات، الوراثة، الواجهات)
  • المميزات المتقدمة مثل Mixins، الامتدادات، الأنواع العامة
  • البرمجة غير المتزامنة باستخدام Future و async/await و Streams
  • التعامل مع الاستثناءات والأخطاء
  • تنظيم الكود باستخدام المكتبات
  • أمان القيم الفارغة والمزايا الأخرى

الخطوات التالية

إليك بعض الموارد المفيدة لمواصلة تعلم دارت وفلاتر:

نصيحة:

أفضل طريقة لتعلم البرمجة هي الممارسة! ابدأ في بناء مشاريع صغيرة واستكشاف الميزات المختلفة للغة دارت. مع كل مشروع، ستكتسب خبرة أكبر وتصبح مطورًا أفضل.

هل أنت مستعد لبدء رحلتك مع دارت وفلاتر؟

استمر في تعلم المزيد عن دارت وفلاتر من خلال الأمثلة العملية والمشاريع التطبيقية.