Flutter: تصميم المشروع — تخطيط بنية ShopApp
ارسم المخطط قبل البناء — تصميم البنية يحدد أين توجد "الجدران الحاملة" في كودك.
📋 المتطلبات السابقة: يجب أن تكون قد أتقنت ما يلي أولًا
- الدرس 1: مقدمة Flutter وإعداد البيئة
- الدرس 2: دورة مكثفة في لغة Dart
- الدرس 3: أساسيات الويدجت
- الدرس 4: نظام التخطيط
- الدرس 5: ويدجت ذو حالة والتفاعل
- الدرس 6: تصميم Material والمكونات الشائعة
- الدرس 7: تدريب المرحلة 1 — ShopApp البداية
- الدرس 8: التنقل والتوجيه
- الدرس 9: النماذج والإدخال
- الدرس 10: القوائم والتمرير
- الدرس 11: الشبكات وREST API
- الدرس 12: إدارة الحالة — Riverpod
- الدرس 13: التخزين المحلي
- الدرس 14: تدريب المرحلة 2 — ShopApp الجوهر
- الدرس 15: نظام الحركة
- الدرس 16: التنسيق والأنماط
- الدرس 17: قنوات المنصة والتشابك الأصلي
- الدرس 18: تكامل Firebase
- الدرس 19: التدويل والتوطين
- الدرس 20: الرسم المخصص وCustomPainter
- الدرس 21: الاختبار — وحدة/ويدجت/تكامل
- الدرس 22: تحسين الأداء
- الدرس 23: نشر الويب وسطح المكتب متعدد المنصات
- الدرس 24: أتمتة CI/CD
- الدرس 25: النشر عبر المنصات ومراجعة المتجر
1. ما ستتعلمه
- تحليل المتطلبات: قصص المستخدم (أليس تتصفح وتطلب / بوب إدارة البائعين / تشارلي الدفع عبر الحدود)
- الاختيار التقني: Flutter 3.x + Riverpod + GoRouter + Dio + Firebase + Hive
- تصميم البنية: طبقات البنية النظيفة (النطاق / البيانات / العرض)
- نمذجة البيانات: كيانات Product / Cart / Order / User ومخطط Firestore
- تصميم واجهة/تجربة المستخدم: إطارات سلكية + تخطيط مكتبة المكونات + نظام رموز التصميم
2. قصة مشروع بلا بنية
(1) المشكلة: متطلبات غامضة + بنية فوضوية
عندما استلم بوب مشروع ShopApp، كان المتطلب الوحيد: "ابنِ تطبيق تجارة إلكترونية عبر الحدود." بدأ البرمجة فورًا، وبعد 3 أشهر اكتشف: قصص المستخدم غير واضحة (ميزات إدارة البائعين مفقودة)، نماذج البيانات تتغير باستمرار (أُعيدت كتابة Order ثلاث مرات)، والبنية مقترنة بشدة (تغيير الدفع أثر على قائمة المنتجات). تأخر المشروع شهرين.
(2) صمّم أولًا، برمج بعد ذلك
مرحلة تصميم المشروع توضح المتطلبات والبنية ونماذج البيانات وتخطيط واجهة المستخدم مسبقًا — ثم تصبح البرمجة "بناءً من المخطط."
(3) النتيجة: وقت البرمجة انخفض للنصف + تكلفة التغيير انخفضت بنسبة 80%
بعد قضاء أسبوعين في التصميم، أكمل بوب البرمجة في 4 أسابيع (بدلاً من 3+2 أشهر سابقًا)، وتغييرات المتطلبات تحتاج فقط تعديل الطبقة المقابلة دون التأثير على الوحدات الأخرى.
3. تحليل المتطلبات
(1) قصص المستخدم
graph TD
subgraph User Stories
ALICE[Alice: Browse → Order → Pay $299.99]
BOB[Bob: Manage Products → View Orders]
CHARLIE[Charlie: Cross-border → JPY → International Payment]
end
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.
| الدور | قصة المستخدم | الأولوية |
|---|---|---|
| أليس (مستهلكة) | تصفح قائمة منتجات بمستوى المليون | P0 |
| أليس | بحث/تصفية/تصفح بالفئة | P0 |
| أليس | عرض تفاصيل المنتج + المراجعات | P0 |
| أليس | إضافة للسلة + إدارة الكميات | P0 |
| أليس | تسوية بعملات متعددة USD/CNY/JPY | P1 |
| أليس | عرض حالة الطلب + تتبع الشحن | P1 |
| بوب (بائع) | إدارة عرض/إخفاء المنتجات + المخزون | P1 |
| بوب | عرض تقارير بيانات المبيعات | P2 |
| تشارلي (مستخدم عبر الحدود) | تسوق عبر الحدود + دفع دولي | P2 |
| تشارلي | تبديل متعدد اللغات (EN/ZH/JA) | P1 |
4. الاختيار التقني
(1) قرارات حزمة التقنيات
| المجال | الاختيار | المبرر |
|---|---|---|
| إطار واجهة المستخدم | Flutter 3.x (Material 3) | عبر المنصات + محرك رسم مخصص |
| إدارة الحالة | Riverpod 2.x (@riverpod) | آمن النوع + توليد كود |
| التوجيه | GoRouter | تعريفي + دعم URL للويب |
| الشبكات | Dio + Interceptors | اعتراضات + تحديث Token |
| الخلفية | Firebase (Auth+FS+Storage) | تشغيل صفري + مزامنة فورية |
| التخزين المؤقت | Hive | NoSQL خفيف + وضع عدم اتصال |
| التخزين المشفر | flutter_secure_storage | تخزين آمن للرموز |
| التسلسل | json_serializable + freezed | آمن النوع + غير قابل للتغيير |
| التدويل | flutter_localizations + intl | سير عمل ARB |
| الاختبار | flutter_test + mocktail | وحدة/ويدجت/تكامل |
| CI/CD | GitHub Actions | طبقة مجانية + مصفوفة متعددة |
(2) مقارنة Riverpod مع BLoC
| البُعد | Riverpod | BLoC |
|---|---|---|
| منحنى التعلم | متوسط | مرتفع |
| الكود المعياري | أقل (@riverpod) | أكثر (Event/State/Bloc) |
| أمان النوع | قوي | قوي |
| الاختبار | بسيط | بسيط |
| توليد الكود | ✅ | ❌ |
| الأنسب لـ | مشاريع صغيرة-متوسطة | مشاريع الفرق الكبيرة |
5. طبقات البنية النظيفة
graph TD
subgraph Presentation
PAGE[Pages/Widgets]
NOTI[Notifiers/Providers]
end
subgraph Domain
ENT[Entities]
REPO_I[Repository Interfaces]
USECASE[Use Cases]
end
subgraph Data
REPO_IMPL[Repository Impl]
DS[Data Sources]
DTO2[DTOs / Models]
end
PAGE --> NOTI
NOTI --> USECASE
USECASE --> REPO_I
REPO_I -.-> REPO_IMPL
REPO_IMPL --> DS
DS --> DTO2
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.
(1) مسؤوليات الطبقات
| الطبقة | الدليل | المسؤولية | اتجاه التبعية |
|---|---|---|---|
| العرض | screens/, widgets/ |
واجهة المستخدم + تفاعل المستخدم | ← التطبيق |
| التطبيق | notifiers/, usecases/ |
منطق الأعمال + الحالة | ← النطاق |
| النطاق | entities/, repositories/ |
قواعد الأعمال الأساسية | بدون تبعيات خارجية |
| البيانات | repositories/impl/, datasources/ |
جلب البيانات + الاستمرارية | ← النطاق |
(2) قواعد التبعية
- طبقة النطاق: Dart بحت، بدون تبعيات Flutter/طرف ثالث
- طبقة البيانات: تنفذ واجهات Repository لطبقة النطاق
- طبقة التطبيق: تنسق بين النطاق والعرض
- طبقة العرض: تعتمد فقط على طبقة التطبيق
6. نمذجة البيانات
(1) الكيانات الأساسية
▶ مثال
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.
: تعريفات كيان النطاق
// domain/entities/product.dart
// Pure Dart class, no external dependencies
class Product {
final int id;
final String name;
final double price;
final String imageUrl;
final String category;
final double rating;
final int reviewCount;
final int stock;
final String currency;
const Product({
required this.id,
required this.name,
required this.price,
required this.imageUrl,
this.category = 'General',
this.rating = 0.0,
this.reviewCount = 0,
this.stock = 0,
this.currency = 'USD',
});
bool get inStock => stock > 0;
bool get hasDiscount => false; // Extended in sale scenario
}
// domain/entities/cart_item.dart
class CartItem {
final Product product;
final int quantity;
final String? selectedSize;
final String? selectedColor;
const CartItem({
required this.product,
required this.quantity,
this.selectedSize,
this.selectedColor,
});
double get lineTotal => product.price * quantity;
CartItem copyWith({int? quantity, String? selectedSize, String? selectedColor}) =>
CartItem(product: product, quantity: quantity ?? this.quantity,
selectedSize: selectedSize ?? this.selectedSize,
selectedColor: selectedColor ?? this.selectedColor);
}
// domain/entities/address.dart
class Address {
final String name;
final String street;
final String city;
final String state;
final String zip;
final String country;
const Address({
required this.name,
required this.street,
required this.city,
required this.state,
required this.zip,
this.country = 'US',
});
String get fullAddress => '$street, $city, $state $zip, $country';
Map<String, dynamic> toJson() => {
'name': name, 'street': street, 'city': city,
'state': state, 'zip': zip, 'country': country,
};
}
// domain/entities/order.dart
enum OrderStatus { pending, confirmed, shipped, delivered, cancelled }
class Order {
final String id;
final String userId;
final List<CartItem> items;
final double total;
final String currency;
final OrderStatus status;
final DateTime createdAt;
final Address shippingAddress;
const Order({
required this.id,
required this.userId,
required this.items,
required this.total,
this.currency = 'USD',
this.status = OrderStatus.pending,
required this.createdAt,
required this.shippingAddress,
});
}
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.
(2) تصميم مخطط Firestore
firestore/
├── products/{productId}
│ ├── name: string
│ ├── price: number
│ ├── stock: number
│ ├── category: string
│ ├── rating: number
│ ├── imageUrl: string
│ └── currency: string
├── users/{userId}
│ ├── email: string
│ ├── name: string
│ ├── preferences: map
│ │ ├── theme: string
│ │ ├── currency: string
│ │ └── locale: string
│ └── addresses: array
├── orders/{orderId}
│ ├── userId: string
│ ├── items: array<{productId, quantity, price}>
│ ├── total: number
│ ├── currency: string
│ ├── status: string
│ └── createdAt: timestamp
└── reviews/{reviewId}
├── productId: string
├── userId: string
├── rating: number
└── comment: string
```text
```text
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.
7. تخطيط واجهة/تجربة المستخدم
(1) جرد الصفحات
| الصفحة | المسار | المكونات الأساسية |
|---|---|---|
| الرئيسية | / |
SliverAppBar + GridView + CategoryChips |
| البحث | /search |
SearchBar + FilterSheet + ProductList |
| تفاصيل المنتج | /product/:id |
Hero + SliverAppBar + BottomSheet |
| السلة | /cart |
CartList + QuantitySelector + TotalBar |
| الدفع | /checkout |
AddressForm + PaymentSelector + OrderSummary |
| تأكيد الطلب | /order/:id |
OrderTimeline + TrackingMap |
| الملف الشخصي | /profile |
UserCard + OrderHistory + Settings |
| تسجيل الدخول | /login |
EmailForm + GoogleButton + AppleButton |
(2) رموز التصميم
import 'package:flutter/material.dart';
// Design tokens: unified color/spacing/radius/font constants, carried by ThemeExtension
class ShopDesignTokens {
// Colors
static const primary = Color(0xFF0066CC);
static const secondary = Color(0xFFFF6B35);
static const sale = Color(0xFF4CAF50);
static const discount = Color(0xFFEF5350);
// Spacing
static const xs = 4.0;
static const sm = 8.0;
static const md = 16.0;
static const lg = 24.0;
static const xl = 32.0;
// Radius
static const cardRadius = 12.0;
static const buttonRadius = 8.0;
static const inputRadius = 8.0;
// Typography
static const headlineSize = 24.0;
static const titleSize = 18.0;
static const bodySize = 14.0;
static const captionSize = 12.0;
}
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.
8. مثال كامل: هيكل دليل المشروع
shop_app/
├── lib/
│ ├── main.dart
│ ├── app.dart
│ ├── core/
│ │ ├── router/
│ │ │ └── app_router.dart
│ │ ├── network/
│ │ │ ├── dio_client.dart
│ │ │ └── api_exception.dart
│ │ ├── storage/
│ │ │ ├── secure_storage.dart
│ │ │ ├── hive_cache.dart
│ │ │ └── shared_prefs.dart
│ │ ├── theme/
│ │ │ ├── app_theme.dart
│ │ │ └── brand_tokens.dart
│ │ └── platform/
│ │ └── platform_helper.dart
│ ├── domain/
│ │ ├── entities/
│ │ │ ├── product.dart
│ │ │ ├── cart_item.dart
│ │ │ ├── order.dart
│ │ │ └── user.dart
│ │ └── repositories/
│ │ ├── product_repository.dart
│ │ ├── auth_repository.dart
│ │ └── order_repository.dart
│ ├── data/
│ │ ├── repositories/
│ │ │ ├── product_repository_impl.dart
│ │ │ ├── auth_repository_impl.dart
│ │ │ └── order_repository_impl.dart
│ │ ├── datasources/
│ │ │ ├── firestore_datasource.dart
│ │ │ └── hive_datasource.dart
│ │ └── models/
│ │ ├── product_dto.dart
│ │ └── order_dto.dart
│ ├── application/
│ │ ├── notifiers/
│ │ │ ├── auth_notifier.dart
│ │ │ ├── product_notifier.dart
│ │ │ └── cart_notifier.dart
│ │ └── providers/
│ │ └── infrastructure_providers.dart
│ ├── presentation/
│ │ ├── screens/
│ │ │ ├── home_page.dart
│ │ │ ├── detail_page.dart
│ │ │ ├── cart_page.dart
│ │ │ ├── checkout_page.dart
│ │ │ └── login_page.dart
│ │ └── widgets/
│ │ ├── product_card.dart
│ │ ├── quantity_selector.dart
│ │ ├── cart_badge.dart
│ │ └── price_tag.dart
│ └── l10n/
│ ├── app_en.arb
│ ├── app_zh.arb
│ └── app_ja.arb
├── test/
│ ├── domain/
│ ├── application/
│ └── presentation/
├── integration_test/
├── android/
├── ios/
├── web/
├── windows/
├── macos/
├── pubspec.yaml
├── l10n.yaml
└── .github/workflows/
```text
```text
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.
❓ أسئلة شائعة
📖 ملخص
- قصص المستخدم تقود تحليل المتطلبات، تُنظم حسب الدور والأولوية
- الاختيار التقني يحتاج موازنة بين قدرة الفريق وحجم المشروع ونضج النظام البيئي
- البنية النظيفة أربع طبقات: العرض ← التطبيق ← النطاق ← البيانات
- طبقة النطاق هي Dart بحت بدون تبعيات خارجية؛ واجهات Repository مُعرَّفة في النطاق
- رموز التصميم توحد اللون/المسافة/نصف القطر/الخط، محمولة بـ ThemeExtension
📝 تمارين
- أساسي (الصعوبة ⭐): اكتب 10 قصص مستخدم لـ ShopApp، مصنفة حسب أولوية P0/P1/P2.
- متوسط (الصعوبة ⭐⭐): صمم هيكل دليل من أربع طبقات بالبنية النظيفة، مع تعريف كيانات Product/Cart/Order وواجهات Repository المقابلة.
- متقدم (الصعوبة ⭐⭐⭐): أكمل وثيقة تصميم مشروع كاملة: قصص مستخدم + جدول مقارنة الاختيار التقني + مخطط طبقات البنية النظيفة + مخطط Firestore + جرد الصفحات + رموز التصميم.
← الدرس السابق | الدرس التالي →
(2) مثال: رسم بياني لتبعيات وحدات ShopApp
final Map<String, List<String>> moduleDeps = {
'core': [],
'cart': ['core'],
'checkout': ['core', 'cart'],
'profile': ['core'],
'catalog': ['core'],
};
الإخراج:
core: 0 direct dependencies
cart: depends on core
checkout: depends on core, cart
profile: depends on core
catalog: depends on core
(3) مثال: جدول المسارات
final Map<String, String> routes = {
'/': 'CatalogPage',
'/cart': 'CartPage',
'/checkout': 'CheckoutPage',
'/profile': 'ProfilePage',
};
الإخراج:
/ -> CatalogPage
/cart -> CartPage
/checkout -> CheckoutPage
/profile -> ProfilePage