Flutter: دورة مكثفة في لغة Dart

Dart هي روح Flutter — إتقان Dart كتعلم قراءة النوتات الموسيقية قبل عزف سيمفونية.

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

1. ما ستتعلمه


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

(1) المشكلة: جحيم الاستدعاءات الرجعية والارتباك في الأنواع

بوب مطوّر واجهة خلفية انتقل إلى Flutter. معتادًا على مرونة JavaScript، كان يصطدم باستمرار في Dart: يخلط بين var وfinal، يُشعّب الاستدعاءات غير المتزامنة 5 مستويات، ويواجه أخطاء أنواع عامة مستمرة. في حساب أسعار سلة التسوق، حول خطأ تحويل ضمني من int إلى double مبلغ 99.99 دولارًا إلى 99 دولارًا — مما أدى إلى سيل شكاوى المستخدمين بعد الإطلاق.

(2) حل Dart

Dart لغة ذات أنواع قوية تلتقط أخطاء الأنواع في وقت التجميع؛ async/await يجعل الكود غير المتزامن واضحًا كالكود المتزامن؛ final/const يمنع التعديل غير المقصود.

DART
import 'dart:async';
import 'dart:convert';
import 'package:http/http.dart' as http;

// Simplified class definition (for demonstration)
class Product {
  final int id;
  final String name;
  final double price;
  final String? imageUrl;
  const Product({required this.id, required this.name, required this.price, this.imageUrl});
  factory Product.fromJson(Map<String, dynamic> json) => Product(
    id: json['id'] as int,
    name: json['name'] as String,
    price: (json['price'] as num).toDouble(),
    imageUrl: json['image_url'] as String?,
  );
  Product copyWith({String? name, double? price}) => Product(
    id: id, name: name ?? this.name, price: price ?? this.price, imageUrl: imageUrl,
  );
  static List<Product> fromJsonList(List data) => data.map((j) => Product.fromJson(j as Map<String, dynamic>)).toList();
}

class CartItem {
  final Product product;
  final double price;
  final int quantity;
  CartItem({required this.product, required this.price, this.quantity = 1});
}

// Strong typing prevents price bugs
double calculateTotal(List<CartItem> items) {
  return items.fold(0.0, (sum, item) => sum + item.price * item.quantity);
}

// async/await eliminates callback hell
Future<List<Product>> fetchProducts() async {
  final response = await http.get(Uri.parse('/api/v1/products'));
  return Product.fromJsonList(jsonDecode(response.body));
}
TEXT
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.

(3) النتيجة: أمان الأنواع + كود غير متزامن واضح

بعد الانتقال إلى Dart، تُلتقط أخطاء الأنواع في وقت التجميع، وتُسطّح التعليمات غير المتزامنة، ولم تحدث مشكلة دقة أسعار سلة التسوق مرة أخرى.


3. المتغيرات ونظام الأنواع

Dart لغة ذات أنواع قوية مع استنتاج الأنواع وأمان سليم للقيم الفارغة.

100%
sequenceDiagram
    participant Bob
    participant ShopAPI
    Bob->>ShopAPI: fetchProducts() [async]
    ShopAPI-->>Bob: Future<List<Product>>
    Bob->>Bob: await parses JSON
    Bob->>Bob: Renders product list
TEXT
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.

(1) الأنواع الأساسية

النوع الوصف مثال
int عدد صحيح 64 بت int quantity = 3;
double عدد عشري 64 بت double price = 99.99;
String سلسلة UTF-16 String name = "ShopApp";
bool منطقي bool inStock = true;
num أب لـ int + double num value = 42;

(2) كلمات تعريف المتغيرات

الكلمة القابلية للتغيير وقت الإسناد حالة الاستخدام
var قابل للتغيير وقت التشغيل متغيرات محلية بأنواع قابلة للاستنتاج
final غير قابل للتغيير وقت التشغيل ثوابت وقت التشغيل (مثل استجابات API)
const غير قابل للتغيير وقت التجميع ثوابت وقت التجميع (مثل قيم الألوان)
late تهيئة مؤجلة وقت التشغيل يجب إسنادها قبل الاستخدام

▶ مثال

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

: مقارنة تعريف المتغيرات

DART
// var: type inferred, mutable
var productName = 'Flutter T-Shirt';
productName = 'Dart Hoodie'; // OK

// final: runtime constant, immutable
final double totalPrice = 29.99 * 3;
// totalPrice = 100.0; // Error: final variable

// const: compile-time constant
const double discountRate = 0.15;
const String storeName = 'ShopApp';

// late: deferred initialization
late String userToken;
void login() {
  userToken = 'eyJhbGciOiJIUzI1NiIs...';
}
TEXT
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.

4. عمليات المجموعات

(1) List، Map، Set

المجموعة الخصائص الصيغة الحرفية
List<E> مرتبة، تسمح بالتكرار [1, 2, 3]
Map<K,V> أزواج مفتاح-قيمة {'key': 'value'}
Set<E> غير مرتبة، فريدة {1, 2, 3}

▶ مثال

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

: عمليات المجموعات الأساسية

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

// Product class definition shown above

// List: ordered collection
List<Product> products = [
  Product(id: 1, name: 'Laptop', price: 1299.99),
  Product(id: 2, name: 'Phone', price: 899.99),
];

// Map: key-value pairs
Map<String, double> prices = {
  'Laptop': 1299.99,
  'Phone': 899.99,
  'Tablet': 599.99,
};

// Set: unique items
Set<String> categories = {'Electronics', 'Clothing', 'Books'};

// Access and modify
products.add(Product(id: 3, name: 'Tablet', price: 599.99));
prices['Headphones'] = 199.99;
categories.add('Electronics'); // Ignored, already exists
TEXT
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.

(2) collection-if / collection-for

صيغة Dart الفريدة للشروط والحلقات داخل المجموعات تُبسّط بناء واجهة المستخدم بشكل كبير.

▶ مثال

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

: collection-if وcollection-for

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

// Product class definition shown above

// collection-if: conditional items
bool isAdmin = true;
var menuItems = [
  'Home',
  'Products',
  'Cart',
  if (isAdmin) 'Admin Panel',
];

// collection-for: expand items
var productNames = ['Laptop', 'Phone', 'Tablet'];
var descriptions = [
  for (var name in productNames) '$name - Best Price'
];
// ['Laptop - Best Price', 'Phone - Best Price', 'Tablet - Best Price']

// Combined in Widget list
Widget buildProductList(List<Product> products) {
  return Column(
    children: [
      for (var p in products)
        ListTile(title: Text(p.name), trailing: Text('\$${p.price}')),
    ],
  );
}
TEXT
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.

5. خصائص الدوال

(1) أنواع المعاملات

النوع الصيغة مطلوب القيمة الافتراضية
موضعي (a, b) نعم لا يوجد
موضعي اختياري ([a = 0]) لا مدعوم
مسمّى ({required a}) مطلوب عند وجود required مدعوم
مسمّى اختياري ({a = 0}) لا مدعوم

▶ مثال

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

: أنماط معاملات الدوال

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

// Named parameters (recommended for Flutter)
Widget buildPriceTag({
  required double price,
  String currency = 'USD',
  double fontSize = 16.0,
}) {
  return Text(
    '$currency \$${price.toStringAsFixed(2)}',
    style: TextStyle(fontSize: fontSize),
  );
}

// Call with named params
buildPriceTag(price: 99.99);
buildPriceTag(price: 49.99, currency: 'EUR', fontSize: 20.0);
TEXT
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.

(2) الدوال المجهولة والإغلاقات

▶ مثال

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

: الإغلاقات في Flutter

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

// Product class definition shown above

// Anonymous function (lambda)
var expensiveItems = products.where((p) => p.price > 500.0).toList();

// Closure: captures surrounding variables
double discountThreshold = 100.0;
var discountedItems = products.map((p) {
  // Closure captures discountThreshold
  if (p.price > discountThreshold) {
    return p.copyWith(price: p.price * 0.9);
  }
  return p;
}).toList();

// Callback in Flutter widget
ElevatedButton(
  onPressed: () {
    // Closure captures context variables
    addToCart(product);
  },
  child: const Text('Add to Cart'),
)
TEXT
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.

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

(1) Future وasync/await

المفهوم الوصف التشبيه
Future<T> عنصر نائب لعملية غير متزامنة رقم طلب في مطعم
async يحدّد دالة غير متزامنة يخبر النظام "سأنتظر"
await ينتظر اكتمال Future انتظر الطعام قبل الأكل

▶ مثال

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

: طلب شبكة async/await

DART
import 'dart:convert';
import 'package:http/http.dart' as http;

// Product class definition shown above

Future<List<Product>> fetchProducts({int page = 1}) async {
  try {
    final response = await http.get(
      Uri.parse('https://api.shopapp.com/v1/products?page=$page'),
    );
    if (response.statusCode == 200) {
      final List data = jsonDecode(response.body);
      return data.map((json) => Product.fromJson(json)).toList();
    } else {
      throw Exception('Failed to load products: ${response.statusCode}');
    }
  } catch (e) {
    // Handle network errors
    rethrow;
  }
}
TEXT
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.

(2) Stream: تدفق البيانات غير المتزامن

▶ مثال

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

: Stream لتحديثات سلة التسوق في الوقت الفعلي

DART
import 'dart:async';

// Simplified class definition (for demonstration)
class Product {
  final String name;
  final double price;
  const Product({required this.name, required this.price});
}

class CartItem {
  final Product product;
  int quantity;
  CartItem({required this.product, this.quantity = 1});
}

// StreamController for cart updates
class CartBloc {
  final _cartStream = StreamController<List<CartItem>>.broadcast();

  Stream<List<CartItem>> get cartStream => _cartStream.stream;
  List<CartItem> _items = [];

  void addToCart(Product product) {
    _items.add(CartItem(product: product, quantity: 1));
    _cartStream.sink.add(_items);
  }

  void dispose() {
    _cartStream.close();
  }
}

// Listen to stream
cartBloc.cartStream.listen((items) {
  print('Cart updated: ${items.length} items, total: \$${calculateTotal(items)}');
});
TEXT
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.

7. الأصناف والميكسن

(1) الأصناف والمنشئات

نوع المنشئ الصيغة الغرض
الافتراضي ClassName() إنشاء مثيل قياسي
المسمّى ClassName.named() طرق تهيئة متعددة
المصنع factory ClassName() التحكم في إنشاء المثيل (مثل التخزين المؤقت/ال singleton)
الثابت const ClassName() كائنات ثابتة وقت التجميع

▶ مثال

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

: نموذج بيانات المنتج

DART
class Product {
  final int id;
  final String name;
  final double price;
  final String? imageUrl;
  final double rating;

  // Main constructor with named params
  const Product({
    required this.id,
    required this.name,
    required this.price,
    this.imageUrl,
    this.rating = 0.0,
  });

  // Named constructor from JSON
  factory Product.fromJson(Map<String, dynamic> json) {
    return Product(
      id: json['id'] as int,
      name: json['name'] as String,
      price: (json['price'] as num).toDouble(),
      imageUrl: json['image_url'] as String?,
      rating: (json['rating'] as num?)?.toDouble() ?? 0.0,
    );
  }

  // Copy with pattern for immutable updates
  Product copyWith({String? name, double? price}) {
    return Product(
      id: id,
      name: name ?? this.name,
      price: price ?? this.price,
      imageUrl: imageUrl,
      rating: rating,
    );
  }
}
TEXT
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.

(2) الميكسن: إعادة استخدام الكود

▶ مثال

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

: ميكسن للوظائف المشتركة

DART
// Product class definition shown above

// Mixin: reusable behavior without inheritance
mixin PriceFormatter {
  String formatPrice(double price, {String currency = 'USD'}) {
    return '$currency \$${price.toStringAsFixed(2)}';
  }

  String formatDiscount(double original, double discounted) {
    double percent = ((original - discounted) / original * 100);
    return '${percent.toStringAsFixed(0)}% OFF';
  }
}

// Apply mixin to class
class ProductCard with PriceFormatter {
  final Product product;
  ProductCard(this.product);

  String get priceLabel => formatPrice(product.price);
  String get discountLabel => formatDiscount(100.0, product.price);
}

// Abstract class: cannot be instantiated
abstract class Repository<T> {
  Future<List<T>> getAll();
  Future<T> getById(int id);
  Future<T> create(T item);
}
TEXT
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.

8. مثال كامل: طبقة بيانات ShopApp

⚙️ تثبيت التبعية: flutter pub add http

DART
import 'dart:convert';
import 'package:http/http.dart' as http;

// Model
class Product {
  final int id;
  final String name;
  final double price;
  final String category;

  const Product({
    required this.id,
    required this.name,
    required this.price,
    required this.category,
  });

  factory Product.fromJson(Map<String, dynamic> json) => Product(
        id: json['id'] as int,
        name: json['name'] as String,
        price: (json['price'] as num).toDouble(),
        category: json['category'] as String,
      );
}

// Repository with async/await
class ProductRepository {
  final String baseUrl = 'https://api.shopapp.com/v1';

  Future<List<Product>> fetchProducts({int page = 1, int limit = 20}) async {
    final response = await http.get(
      Uri.parse('$baseUrl/products?page=$page&limit=$limit'),
    );
    if (response.statusCode != 200) {
      throw Exception('API error: ${response.statusCode}');
    }
    final List data = jsonDecode(response.body)['items'] as List;
    return data.map((j) => Product.fromJson(j as Map<String, dynamic>)).toList();
  }

  Future<Product> fetchById(int id) async {
    final response = await http.get(Uri.parse('$baseUrl/products/$id'));
    return Product.fromJson(jsonDecode(response.body) as Map<String, dynamic>);
  }
}

// Usage
void main() async {
  final repo = ProductRepository();
  final products = await repo.fetchProducts(page: 1);
  for (var p in products) {
    print('${p.name}: \$${p.price.toStringAsFixed(2)}');
  }
}
TEXT
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.

❓ أسئلة شائعة

س ما الفرق الحقيقي بين final وconst؟
ج final يُسنَد مرة واحدة في وقت التشغيل ولا يمكن تغييره؛ const يُحدَّد في وقت التجميع ويجب أن يكون قيمة حرفية أو منشئ const.
س هل لدى Dart معامل ثلاثي؟
ج نعم، condition ? value1 : value2، بالإضافة إلى ?? (الدمج المتوقع للفراغ) و?. (الوصول الآمن للفراغ).
س هل يجب أن تُرجع الدالة غير المتزامنة Future؟
ج لا، لكن يُوصى بذلك. الدالة غير المتزامنة تُنفَّذ بشكل غير متزامن حتى لو أرجعت void.
س ما الفرق بين Stream وFuture؟
ج Future نتيجة غير متزامنة لمرة واحدة؛ Stream تسلسل بيانات غير متزامن يُنتج باستمرار، كأسعار الأسهم في الوقت الفعلي.
س كيف أختار بين الميكسن والصنف المجرد؟
ج استخدم الميكسن لإضافة سلوك (مثل التسجيل، التنسيق) بدون علاقة هو-علاقة (is-a)؛ استخدم الصنف المجرد لتعريف عقد نوع يتطلب علاقة هو-علاقة.
س هل يدعم Dart أمان القيم الفارغة؟
ج Dart 2.12+ يفرض أمانًا سليمًا للقيم الفارغة. String لا يمكن أن تكون فارغة، String? يمكن أن تكون فارغة. يضمن المُجمّع عدم وجود أخطاء مرجعية فارغة في وقت التشغيل.

📖 ملخص


📝 تمارين

  1. أساسي (الصعوبة ⭐): أنشئ صنف User بالخصائص id (int)، name (String)، email (String?)، ونفّذ منشئ مصنع fromJson.
  2. متوسط (الصعوبة ⭐⭐): اكتب دالة طلب شبكة محاكاة باستخدام async/await تُرجع Future<List<Product>>، مع استخدام Future.delayed لمحاكاة تأخير ثانيتين.
  3. متقدم (الصعوبة ⭐⭐⭐): نفّذ صنف CartManager يستخدم StreamController لبث تغييرات سلة التسوق، مع دعم إضافة/إزالة العناصر وتفريغ السلة.

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

Web-Tutorial.com

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

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

100%