Flutter: دورة مكثفة في لغة Dart
Dart هي روح Flutter — إتقان Dart كتعلم قراءة النوتات الموسيقية قبل عزف سيمفونية.
📋 المتطلبات السابقة: يجب أن تكون قد أكملت ما يلي أولًا
- الدرس 1: مقدمة في Flutter وإعداد البيئة
1. ما ستتعلمه
- المتغيرات ونظام الأنواع:
int،double،String،bool؛ الفروق بينvar/final/const - عمليات المجموعات:
List<E>،Map<K,V>،Set<E>؛ collection-if / collection-for - خصائص الدوال: المعاملات الاختيارية، المعاملات المسمّاة، الدوال المجهولة والإغلاقات
- البرمجة غير المتزامنة:
Future<T>،async/await،Stream<T> - الأصناف والميكسن: المنشئات، نمط المصنع،
abstract class،mixin
2. قصة حقيقية من مطوّر
(1) المشكلة: جحيم الاستدعاءات الرجعية والارتباك في الأنواع
بوب مطوّر واجهة خلفية انتقل إلى Flutter. معتادًا على مرونة JavaScript، كان يصطدم باستمرار في Dart: يخلط بين var وfinal، يُشعّب الاستدعاءات غير المتزامنة 5 مستويات، ويواجه أخطاء أنواع عامة مستمرة. في حساب أسعار سلة التسوق، حول خطأ تحويل ضمني من int إلى double مبلغ 99.99 دولارًا إلى 99 دولارًا — مما أدى إلى سيل شكاوى المستخدمين بعد الإطلاق.
(2) حل Dart
Dart لغة ذات أنواع قوية تلتقط أخطاء الأنواع في وقت التجميع؛ async/await يجعل الكود غير المتزامن واضحًا كالكود المتزامن؛ final/const يمنع التعديل غير المقصود.
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));
}
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.
(3) النتيجة: أمان الأنواع + كود غير متزامن واضح
بعد الانتقال إلى Dart، تُلتقط أخطاء الأنواع في وقت التجميع، وتُسطّح التعليمات غير المتزامنة، ولم تحدث مشكلة دقة أسعار سلة التسوق مرة أخرى.
3. المتغيرات ونظام الأنواع
Dart لغة ذات أنواع قوية مع استنتاج الأنواع وأمان سليم للقيم الفارغة.
sequenceDiagram
participant Bob
participant ShopAPI
Bob->>ShopAPI: fetchProducts() [async]
ShopAPI-->>Bob: Future<List<Product>>
Bob->>Bob: await parses JSON
Bob->>Bob: Renders product list
> الإخراج: شغّل محليًا باستخدام 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 |
تهيئة مؤجلة | وقت التشغيل | يجب إسنادها قبل الاستخدام |
▶ مثال
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.
: مقارنة تعريف المتغيرات
// 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...';
}
> الإخراج: شغّل محليًا باستخدام 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} |
▶ مثال
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.
: عمليات المجموعات الأساسية
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
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.
(2) collection-if / collection-for
صيغة Dart الفريدة للشروط والحلقات داخل المجموعات تُبسّط بناء واجهة المستخدم بشكل كبير.
▶ مثال
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.
: collection-if وcollection-for
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}')),
],
);
}
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.
5. خصائص الدوال
(1) أنواع المعاملات
| النوع | الصيغة | مطلوب | القيمة الافتراضية |
|---|---|---|---|
| موضعي | (a, b) |
نعم | لا يوجد |
| موضعي اختياري | ([a = 0]) |
لا | مدعوم |
| مسمّى | ({required a}) |
مطلوب عند وجود required |
مدعوم |
| مسمّى اختياري | ({a = 0}) |
لا | مدعوم |
▶ مثال
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.
: أنماط معاملات الدوال
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);
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.
(2) الدوال المجهولة والإغلاقات
▶ مثال
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.
: الإغلاقات في Flutter
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'),
)
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.
6. البرمجة غير المتزامنة
(1) Future وasync/await
| المفهوم | الوصف | التشبيه |
|---|---|---|
Future<T> |
عنصر نائب لعملية غير متزامنة | رقم طلب في مطعم |
async |
يحدّد دالة غير متزامنة | يخبر النظام "سأنتظر" |
await |
ينتظر اكتمال Future | انتظر الطعام قبل الأكل |
▶ مثال
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.
: طلب شبكة async/await
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;
}
}
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.
(2) Stream: تدفق البيانات غير المتزامن
▶ مثال
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.
: Stream لتحديثات سلة التسوق في الوقت الفعلي
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)}');
});
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.
7. الأصناف والميكسن
(1) الأصناف والمنشئات
| نوع المنشئ | الصيغة | الغرض |
|---|---|---|
| الافتراضي | ClassName() |
إنشاء مثيل قياسي |
| المسمّى | ClassName.named() |
طرق تهيئة متعددة |
| المصنع | factory ClassName() |
التحكم في إنشاء المثيل (مثل التخزين المؤقت/ال singleton) |
| الثابت | const ClassName() |
كائنات ثابتة وقت التجميع |
▶ مثال
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.
: نموذج بيانات المنتج
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,
);
}
}
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.
(2) الميكسن: إعادة استخدام الكود
▶ مثال
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.
: ميكسن للوظائف المشتركة
// 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);
}
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.
8. مثال كامل: طبقة بيانات ShopApp
⚙️ تثبيت التبعية:
flutter pub add http
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)}');
}
}
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.
❓ أسئلة شائعة
condition ? value1 : value2، بالإضافة إلى ?? (الدمج المتوقع للفراغ) و?. (الوصول الآمن للفراغ).String لا يمكن أن تكون فارغة، String? يمكن أن تكون فارغة. يضمن المُجمّع عدم وجود أخطاء مرجعية فارغة في وقت التشغيل.📖 ملخص
- Dart لغة ذات أنواع قوية مع أمان القيم الفارغة، تلتقط أخطاء الأنواع في وقت التجميع
var/final/constتميّز بين القابلية للتغيير ووقت الإسناد- collection-if/collection-for يُبسّط بناء قوائم واجهة المستخدم
async/awaitيجعل الكود غير المتزامن مسطحًا وواضحًا- الميكسن يتيح إعادة استخدام الكود الأفقي؛ الصنف المجرد يعرّف عقود الأنواع
- نمط
copyWithيتيح تحديث الكائنات غير القابلة للتغيير
📝 تمارين
- أساسي (الصعوبة ⭐): أنشئ صنف
Userبالخصائص id (int)، name (String)، email (String?)، ونفّذ منشئ مصنعfromJson. - متوسط (الصعوبة ⭐⭐): اكتب دالة طلب شبكة محاكاة باستخدام
async/awaitتُرجعFuture<List<Product>>، مع استخدامFuture.delayedلمحاكاة تأخير ثانيتين. - متقدم (الصعوبة ⭐⭐⭐): نفّذ صنف
CartManagerيستخدمStreamControllerلبث تغييرات سلة التسوق، مع دعم إضافة/إزالة العناصر وتفريغ السلة.