تخطيط المشروع وإعداد البنية النظيفة
تخطيط المشروع وإعداد البنية النظيفة
قبل كتابة سطر واحد من كود Flutter، أهم استثمار يمكنك تقديمه هو خطة مشروع محددة بوضوح مقرونة بـأساس معماري متين. في مشروع الكابستون هذا سنبني تطبيق مدير المهام الكامل المزايا. يغطي هذا الدرس كيفية تحديد نطاق الميزات، وتعريف هيكل المجلدات وفق مبادئ البنية النظيفة، وضبط جميع التبعيات المطلوبة في pubspec.yaml.
1. تحديد نطاق الميزات
نطاق الميزات الواضح يمنع التمدد غير المقصود في المتطلبات ويبقي قاعدة الكود مركّزة. لتطبيق مدير المهام في الكابستون، مجموعة الميزات المتفق عليها هي:
- المصادقة — تسجيل الدخول والتسجيل بالبريد الإلكتروني وكلمة المرور (محلي + بعيد)
- عمليات المهام — إنشاء وقراءة وتحديث وحذف المهام مع العنوان والوصف وتاريخ الاستحقاق والأولوية
- الفئات — تجميع المهام ضمن فئات يحددها المستخدم
- أولوية العمل دون اتصال — تخزين المهام محلياً عبر SQLite؛ ومزامنتها مع REST API عند الاتصال
- الإشعارات — إشعارات دفع محلية للمهام المستحقة
- تبديل المظهر — الوضع الفاتح والداكن يُحفظ عبر الجلسات
docs/features.md) في بداية المشروع عادة احترافية. يعمل بوصفه عقداً بين التخطيط والتنفيذ ويساعدك على تحديد المكان المعماري الصحيح لكل جزء من الكود.2. البنية النظيفة — ثلاث طبقات
البنية النظيفة (التي شاعها روبرت سي. مارتن) تنظم الكود في طبقات متحدة المركز بقواعد تبعية صارمة: الطبقات الداخلية لا تعرف شيئاً عن الطبقات الخارجية. بالنسبة لتطبيقات Flutter، الطبقات الثلاث العملية هي:
- طبقة النطاق (Domain) — منطق الأعمال الصافي؛ الكيانات، وحالات الاستخدام، وواجهات المستودعات المجردة. لا استيرادات من Flutter.
- طبقة البيانات (Data) — التطبيقات الملموسة للمستودعات؛ مصادر البيانات البعيدة (API)، ومصادر البيانات المحلية (SQLite/SharedPreferences)، ونماذج البيانات (تعيين JSON ↔ كيان).
- طبقة العرض (Presentation) — ودجات Flutter والصفحات وإدارة الحالة (موفرو ومُعلمو Riverpod). تعتمد على حالات استخدام النطاق، ولا تعتمد على البيانات مباشرة.
BuildContext أو تستورد أي حزمة Flutter.3. هيكل المجلدات
التخطيط المقترح للمجلدات في كابستون مشروعنا داخل lib/ هو:
تخطيط مجلدات البنية النظيفة
lib/
├── core/
│ ├── error/
│ │ ├── exceptions.dart // AppException و NetworkException وغيرها
│ │ └── failures.dart // فئة Failure المختومة لنوع النتيجة
│ ├── network/
│ │ └── network_info.dart // واجهة NetworkInfo المجردة
│ ├── theme/
│ │ └── app_theme.dart // ThemeData للوضعين الفاتح والداكن
│ └── utils/
│ └── date_formatter.dart // دوال مساعدة نقية
│
├── features/
│ ├── auth/
│ │ ├── data/
│ │ │ ├── datasources/
│ │ │ │ ├── auth_local_datasource.dart
│ │ │ │ └── auth_remote_datasource.dart
│ │ │ ├── models/
│ │ │ │ └── user_model.dart // toJson / fromJson
│ │ │ └── repositories/
│ │ │ └── auth_repository_impl.dart
│ │ ├── domain/
│ │ │ ├── entities/
│ │ │ │ └── user.dart // فئة Dart نقية — بدون JSON
│ │ │ ├── repositories/
│ │ │ │ └── auth_repository.dart // واجهة مجردة
│ │ │ └── usecases/
│ │ │ ├── sign_in_usecase.dart
│ │ │ └── sign_up_usecase.dart
│ │ └── presentation/
│ │ ├── pages/
│ │ │ ├── login_page.dart
│ │ │ └── register_page.dart
│ │ ├── providers/
│ │ │ └── auth_provider.dart
│ │ └── widgets/
│ │ └── auth_form_field.dart
│ │
│ └── tasks/
│ ├── data/ ...
│ ├── domain/ ...
│ └── presentation/ ...
│
└── main.dart
كل ميزة هي شريحة عمودية مكتفية بذاتها. إضافة ميزة جديدة تعني إنشاء مجلد جديد تحت features/ — دون أي تعديل على الميزات الموجودة.
4. ضبط pubspec.yaml
تُعلن جميع التبعيات مقدماً حتى يثبّت الفريق كاملاً نفس الإصدارات. أدناه ملف pubspec.yaml الكامل لمشروع الكابستون:
pubspec.yaml — قائمة التبعيات الكاملة
name: task_manager
description: تطبيق مدير المهام — تطبيق Flutter يعمل في وضع عدم الاتصال أولاً
publish_to: none
version: 1.0.0+1
environment:
sdk: ">=3.3.0 <4.0.0"
dependencies:
flutter:
sdk: flutter
# إدارة الحالة
flutter_riverpod: ^2.5.1
riverpod_annotation: ^2.3.5
# التنقل
go_router: ^13.2.0
# التخزين المحلي
sqflite: ^2.3.2
shared_preferences: ^2.2.3
path_provider: ^2.1.3
path: ^1.9.0
# الشبكة
dio: ^5.4.3
connectivity_plus: ^6.0.3
# التسلسل
freezed_annotation: ^2.4.1
json_annotation: ^4.9.0
# الإشعارات المحلية
flutter_local_notifications: ^17.2.2
# أدوات مساعدة
dartz: ^0.10.1 # أنواع Either / Option الوظيفية
equatable: ^2.0.5
intl: ^0.19.0
uuid: ^4.4.0
dev_dependencies:
flutter_test:
sdk: flutter
build_runner: ^2.4.9
freezed: ^2.5.2
json_serializable: ^6.8.0
riverpod_generator: ^2.4.3
flutter_lints: ^4.0.0
mockito: ^5.4.4
bloc_test: ^9.1.7
flutter:
uses-material-design: true
assets:
- assets/images/
- assets/icons/
^2.5.1) بدلاً من استخدام any أو حذف القيد. التبعيات غير المثبتة يمكن أن تكسر بنائك عند نشر حزمة تغييراً جذرياً. شغّل flutter pub upgrade --major-versions بشكل متعمد، وليس بالصدفة.5. قاعدة التبعية عملياً
القاعدة الأهم في البنية النظيفة هي قاعدة التبعية: تشير تبعيات الكود المصدري نحو الداخل فقط. ضع في اعتبارك كيان User والفئات المرتبطة به:
كيان النطاق مقابل نموذج البيانات
// domain/entities/user.dart — بدون استيرادات خارجية
class User {
final String id;
final String email;
final String displayName;
const User({
required this.id,
required this.email,
required this.displayName,
});
}
// data/models/user_model.dart — تعتمد على كيان النطاق، ليس العكس
import 'package:task_manager/features/auth/domain/entities/user.dart';
class UserModel extends User {
const UserModel({
required super.id,
required super.email,
required super.displayName,
});
factory UserModel.fromJson(Map<String, dynamic> json) {
return UserModel(
id: json['id'] as String,
email: json['email'] as String,
displayName: json['display_name'] as String,
);
}
Map<String, dynamic> toJson() => {
'id': id,
'email': email,
'display_name': displayName,
};
}
6. الخلاصة
الكابستون الناجح يبدأ بتخطيط منضبط:
- اكتب نطاق الميزات قبل فتح بيئة التطوير
- اعتمد البنية النظيفة: النطاق ← البيانات ← العرض، التبعيات تشير نحو الداخل
- عكس البنية في هيكل المجلدات حتى يكون لكل ملف مكان واضح
- أعلن جميع التبعيات في
pubspec.yamlبقيود إصدار صريحة - كيان
Userيعيش فيdomain/وخالٍ تماماً من استيرادات JSON أو Flutter؛ أماUserModelفيdata/فيمتده ويتعامل مع التسلسل