هيكل المشروع والتنظيم Project Structure & Organization
دليل شامل لتنظيم ملفات MRE CashBook وتطبيق مبادئ Clean Architecture. A comprehensive guide to MRE CashBook file organization and Clean Architecture principles.
تطبيق MRE CashBook مش بس كود Dart، ده بيحتوي على ملفات لإعدادات منصات كتير زي أندرويد و iOS و Shorebird.
MRE CashBook isn't just Dart code; it contains configuration files for many platforms like Android, iOS, and Shorebird.
ls -F
بتعرض كل الملفات والمجلدات في الجذر مع علامة مميزة (مثل / للمجلدات) عشان تفرق بينهم بسهولة.
Lists all files and folders in the root with symbols (like / for folders) for quick identification.
| Folder/File | Importance | Details (AR) |
|---|---|---|
lib/ | Critical | قلب التطبيق، هنا كل كود الـ Dart والـ Logic. |
assets/ | High | الصور، الأيقونات، والخطوط اللي التطبيق بيعتمد عليها. |
android/ | High | ملفات إعدادات نظام الأندرويد (Gradle, Manifest). |
ios/ | High | ملفات إعدادات نظام الـ iOS (Xcode Project, Info.plist). |
test/ | Medium | اختبارات الوحدة (Unit Tests) واختبارات الـ UI. |
pubspec.yaml | Critical | "هوية" المشروع وقائمة المكاتب المستخدمة. |
shorebird.yaml | Medium | إعدادات الـ Code Push لتحديث التطبيق بدون رفع نسخة جديدة. |
بنستخدم في المشروع ده Clean Architecture مقرونة بـ Modular Structure. يعني الكود متقسم لجزئين كبار:
We use Clean Architecture combined with Modular Structure in this project. The code is split into two major parts:
ده الأساس اللي كل ميزات التطبيق بتعتمد عليه. لو الـ Core باظ، التطبيق كله بيقف.
The foundation upon which all app features rely. If Core breaks, the entire app fails.
دي ميزات التطبيق المستقلة (زي الكتب، الإحصائيات، الإعدادات). المفروض إن كل ميزة تكون معزولة بقدر الإمكان.
Independent business features (Books, Statistics, Settings). Every feature should be as isolated as possible.
قاعدة ذهبية: الميزة (Feature) مسموح لها تكلم الـ Core، بس الـ Core ممنوع نهائياً يكلم ميزة محددة.
Golden Rule: A Feature is allowed to talk to Core, but Core is strictly forbidden from talking to a specific Feature.
مجلد lib/core/ هو المخ اللي بيحرك الدنيا. تعالو نشوف تقسيمته من جوه:
The lib/core/ folder is the brain that runs everything. Let's look at its internal structure:
هنا بنحط الإعدادات العامة زي app_config.dart اللي فيها أسماء الثوابت (Constants).
class AppConfig {
static const String appName = 'MRE CashBook';
static const String devEmail = 'dev@mrecode.net';
}
الإضافات اللي بتسهل علينا الكود. أهم واحدة هي BuildContextExtension اللي بتخلينا نوصل للألوان والترجمة بسهولة.
extension BuildContextHelper on BuildContext {
AppColors get colors => Theme.of(this).extension()!;
TextTheme get textTheme => Theme.of(this).textTheme;
}
ليه بنستخدمها؟ عشان بدل ما نكتب Theme.of(context).textTheme نكتب بس context.textTheme. كود أنضف وأسرع!
هنا بنحط الـ Widgets اللي بنستخدمها في كل حته، زي AppTextField و AppCustomScrollView. دي اللبنات الأساسية لبناء الـ UI.
ممنوع نستخدم Raw Material Widgets في الـ Screens. كل حاجة لازم تمر من خلال الـ Core Widgets عشان نضمن شكل موحد.
Raw Material Widgets are forbidden in Screens. Everything must go through Core Widgets to ensure a consistent look.
class AppTextField extends StatelessWidget {
// تغليف للـ TextFormField بتصميم المشروع
@override
Widget build(BuildContext context) {
return TextFormField(
decoration: InputDecoration(
borderRadius: AppSizes.radiusSm,
fillColor: context.colors.surface,
// ... إعدادات تانية كتير
),
);
}
}
ls lib/core/widgets
كل ميزة في MRE CashBook هي تطبيق صغير لوحده. تعالو نشوف ميزة "الكتب" (Books) كمثال:
Every feature in MRE CashBook is a mini-app. Let's look at the "Books" feature as an example:
المسؤولة عن التعامل مع الداتا الحقيقية. فيها الـ Models والـ DataSources.
books/data/
├── models/ # BookDTO, BookMapper
└── datasources/ # local_data_source.dart (Drift)
دي "المنطقة المحايدة". فيها شكل الداتا بالنسبة لليوزر والـ Business Rules.
books/domain/
├── entities/ # Pure Book class
└── repositories/ # Abstract interface
الواجهة اللي اليوزر بيشوفها. الـ Screens والـ Logic بتاعها (BLoC).
books/presentation/
├── bloc/ # state, event, bloc files
├── screens/ # books_screen.dart
└── widgets/ # book_card.dart, delete_dialog.dart
عشان نحافظ على نضافة الكود، حطينا قواعد ممنوع كسرها:
- ممنوع ميزة تستورد ملفات من ميزة تانية (Cross-Feature Import).
- ممنوع الـ Screen يكلم الـ Database مباشرة.
- ممنوع الـ Business Logic يكون موجود في الـ UI.
- دائماً افصل الـ BLoC في 3 ملفات (State, Event, Bloc).
تنبيه: أي خرق للقواعد دي هيخلي التطبيق صعب جداً في الـ Debugging بعدين ومستحيل في الـ Testing.
مجلد assets/ هو مخزن المواد الخام للتصميم.
assets/
├── icon/ # App icons (icon.png)
├── images/ # Splash and backgrounds
├── fonts/ # Custom typography (Inter, Cairo)
└── translations/ # en.dart, ar.dart (Easy Localization)
وعشان نستخدم الـ Assets دي، لازم نسجلها في pubspec.yaml:
flutter:
uses-material-design: true
assets:
- assets/icon/
- assets/images/
- assets/translations/
fonts:
- family: Inter
fonts:
- asset: assets/fonts/Inter-Regular.ttf
حقن التبعيات هو اللي بيربط الطبقات ببعض. في MRE CashBook، كل ميزة عندها ملف `_injection.dart` خاص بيها عشان منعملش زحمة في الملف الرئيسي.
Dependency Injection (DI) is what glues the layers together. In MRE CashBook, every feature has its own `_injection.dart` file to avoid cluttering the main entry point.
final dashboardInjection = DashboardInjection();
class DashboardInjection {
void init() {
// تسجيل المستودعات (Repositories)
sl.registerLazySingleton(
() => DashboardRepositoryImpl(sl()),
);
// تسجيل الـ BLoCs
sl.registerFactory(() => DashboardBloc(sl()));
}
}
بيعمل نسخة واحدة بس من الكلاس ده، وبيعملها "لما نحتاجها" (Lazy). دي بتوفر ميموري كتير.
Creates a single instance of the class only when needed (Lazy), saving significant memory.
بيعمل نسخة جديدة كل ما نطلبها. ده ممتاز للـ BLoCs عشان نضمن إن الـ State بتبدأ من الصفر دايماً.
Creates a new instance every time requested. Perfect for BLoCs to ensure state starts from scratch.
رغم إن Flutter هو تطبيق Cross-Platform، بس أحياناً بنحتاج نظبط حاجات في الكود الأصلي (Native).
Even though Flutter is cross-platform, sometimes we need to tweak the native code.
أهم ملف هو build.gradle اللي بنحدد فيه الـ SDK versions والـ App Signing.
app/src/main/AndroidManifest.xml: لتصاريح الإنترنت والبيومتريكس.key.properties: بيانات مفتاح التوقيع لرفع التطبيق للستور.
بنعدل هنا الـ Info.plist عشان نضيف رسايل طلب الإذن (زي الكاميرا أو الجاليري).
Podfile: لإدارة المكتبات الخاصة بـ iOS.Runner.xcworkspace: الملف اللي بنفتحه في Xcode.
cat android/app/src/main/AndroidManifest.xml
جذر المشروع مليان ملفات .yaml و .json. كل واحد ليه وظيفة مهمة جداً.
دي "دستور الكود". بنحدد فيها القواعد اللي Dart لازم يمشي عليها (Linter rules). مثلاً إن لازم نحط const قبل الـ Widgets.
The "Code Constitution". Defines Linter rules that Dart must follow, like requiring const before Widgets.
بتربط مشروعك بـ Shorebird عشان تقدر تبعت تحديثات للمستخدمين (Over-the-air updates) من غير ما يستنوا موافقة الستور.
Links your project to Shorebird for over-the-air updates, bypassing store review wait times.
fvm flutter --version
بنستخدم fvm قبل أمر flutter عشان نتأكد إننا شغالين بنفس النسخة اللي اتبرمج بيها التطبيق (في حالتنا 3.41.2).
We use fvm before the flutter command to ensure we are using the exact version the app was coded with (3.41.2).
مجلد test/ مش للديكور، ده الضمان إننا متبوظش حاجة وإحنا بنطور.
test/
├── core/ # Testing shared utils
└── features/ # Feature-specific unit tests
└── books/
└── books_bloc_test.dart
قاعدة: أي Logic معقد في الـ BLoC لازم يكون ليه Test يقابله في مجلد الـ test/ بنفس المسار.
لو طلبنا منك تضيف ميزة "التقارير" (Reports)، دي الخطوات اللي لازم تمشي عليها بالظبط عشان تحافظ على الهيكل:
If we asked you to add a "Reports" feature, these are the exact steps you must follow to maintain the structure:
mkdir -p lib/features/reports/{data,domain,presentation}/{models,datasources,entities,repositories,bloc,screens,widgets}
الأمر ده بيعملك كل الفولدرات اللي محتاجها في سطر واحد.
تبدأ بالـ Pure Dart Object في الـ domain/entities.
class Report {
final String id;
final DateTime date;
// ...
}
تعمل ملف lib/features/reports_injection.dart وتربط الميزة بالـ injection_container.dart.
تبدأ ترص الـ Widgets في presentation/widgets وتعمل الـ Screen الأساسية.
تطبيق MRE مصمم يشتغل على الموبايل والتابلت والـ iPad Pro. عشان كده بنستخدم تقسيم خاص في الـ widgets:
MRE is designed for mobile, tablet, and iPad Pro. That's why we use a specific split in widgets:
عشان في التابلت ممكن نحتاج نعرض فورم (Form) في Dialog كبير، لكن في الموبايل بتتعرض في BottomSheet. التقسيم ده بيمنع الـ if (isTablet) كتير جوه الكود.
On tablets, we might show a Form in a large Dialog, but on mobile, it's a BottomSheet. This split prevents messy if (isTablet) checks in the UI code.
نصيحة: دايماً استخدم الـ AdaptiveOverlays.showAdaptiveModal() اللي موجودة في الـ core عشان هي اللي بتعرف تختار بين الـ Dialog والـ BottomSheet أوتوماتيك.
لأن الـ BLoC وظيفته إدارة حالة الواجهة (UI State). الـ Domain لازم يفضل "خالي من أي اعتماديات" (Pure Dart) عشان تقدر تنقله لأي مشروع تاني بسهولة.
Because BLoC manages UI State. Domain must remain dependency-free (Pure Dart) so it can be ported to other projects easily.
الـ Entity هو الكائن البسيط (id, name). الـ Model هو الكود اللي بيعرف يتعامل مع الـ JSON أو الـ Database (toJson, fromJson). الـ Data layer بتستخدم الـ Model، والـ Presentation بتستخدم الـ Entity.
في lib/core/utils/. بنحط هناك الـ Formatters، الـ Validators، وأي كود Logic بسيط بيستخدم في أكتر من ميزة.
لما يكون عندك منطق عمل (Business Logic) جديد تماماً ومستقل، زي "نظام النسخ الاحتياطي" (Backup System). لو هي مجرد تعديل في الشاشة، كمل في الميزة الموجودة.
عشان المشروع يفضل "Clean"، إحنا مش بنعتمد بس على شطارة المبرمج، إحنا بنستخدم Linter قوي بيراقب كل سطر بيتكتب في الـ analysis_options.yaml.
To keep the project clean, we don't just rely on dev skill; we use a powerful Linter that monitors every line in analysis_options.yaml.
دي أشهر قاعدة، بتجبرك تحط const لما يكون الكائن ثابت. ده بيوفر ميموري كتير في Flutter.
قاعدة عشان شكل الكود يكون موحد (Consistency)، الـ _ بنستخدمها بس للثوابت أو الكلاسات الخاصة (Private).
لازم تحدد الفانكشن بترجع إيه (void, String, Future) عشان الكود يكون واضح لأي حد بيقرأه.
dart analyze
الـ sl (Service Locator) هو اللي بيشيل كل المفاتيح بتاعة التطبيق. بيتم تشغيله في ملف lib/core/di/injection_container.dart.
final sl = GetIt.instance; // المحرك الأساسي (GetIt)
Future init() async {
// 1. الأساسيات (Core)
sl.registerLazySingleton(() => AppConfig());
// 2. الميزات (Features)
dashboardInjection.init();
booksInjection.init();
settingsInjection.init();
}
دي أول فانكشن بتتنده في الـ main.dart. لازم تستنى لما تخلص (await) عشان التطبيق ميبدأش ومن غير ما تكون الداتا جاهزة.
للمحترفين: لاحظ إننا بنقسم الـ init لـ sub-init لكل ميزة، ده نوع من الـ Separation of Concerns.
ازاي الطلب بيمشي جوه المشروع؟ من الشاشة لحد قاعدة البيانات والرجوع تاني:
[UI Screen]
|--> [BLoC/Cubit] (Presentation)
|--> [Repository Interface] (Domain)
|--> [Repository Implementation] (Data)
|--> [Local DataSource] (Data/Drift)
|--> [SQLite DB]
الرجوع (Response) بيمشي العكس تماماً، بس بشهادة "Entity" مش "Model".
اختبر نفسك! حاول تلاقي مسار الملفات دي من غير ما تبص على الشرح تاني:
الإجابة: assets/translations/ar.dart
الإجابة: lib/core/database/tables/
الإجابة: lib/features/settings/presentation/bloc/settings_bloc.dart
الإجابة: assets/images/
الإجابة: android/app/src/main/res/
الملف ده هو المسؤول عن استيراد كل العضلات اللي التطبيق بيستخدمها. تعالو نشوف أهم المكاتب (Dependencies) اللي فيه:
دي قاعدة البيانات العملاقة اللي بتشيل كل حساباتك. Drift هي اللي بتخلينا نكتب SQL بطريقة Dart السهلة.
المسؤول عن إدارة الحالة (State Management). هو اللي بيعرف الشاشة إن فيه داتا جديدة وصلت ولازم تتحدث.
مكتبة حقن التبعيات (DI). هي اللي بتخلينا نوصل لأي كلاس في التطبيق من أي مكان بسهولة واحترافية.
المسؤولة عن تحويل التطبيق بين العربي والإنجليزي بلمسة واحدة.
معلومة: أي مكتبة بنضيفها هنا بتزود حجم التطبيق، عشان كده بنختار المكاتب "الرشيقة" والمدعومة بقوة بس.
fvm flutter pub get
لو وصلت لحد هنا، فأنت دلوقتي مبرمج فاهم كل "سنتيمتر" في هيكل مشروع MRE CashBook. أنت جاهز تدخل في عمق الكود وتغير أي حاجة وأنت مطمن.
If you reached this far, you now understand every "centimeter" of the MRE CashBook structure. You are ready to dive deep into the code and change anything with confidence.
تهانينا! لقد أتممت أطول فصل في هذه الدورة التعليمية. الهيكل هو الأساس، والقادم هو البناء.
Congratulations! You've completed the longest chapter in this course. Structure is the foundation; the rest is building.
هل كنت تعرف إن الطريقة اللي بننظم بيها الفولدرات بتأثر على سرعة التطبيق؟
Did you know that how we organize folders affects app speed?
لما بنقسم الكود لميزات (Features) مفصولة، الـ Flutter Compiler بيقدر "يهز الشجرة" ويشيل أي كود مش مستخدم في النسخة النهائية. لو الكود كله فوق بعضه، العملية دي بتكون صعبة جداً.
بفضل الـ Dependency Injection اللي شرحناه، إحنا مش بنحمل كل الميزات في الميموري أول ما التطبيق يفتح. إحنا بنحمل الميزة لما اليوزر يفتحها بس.
| Layer / Folder | Main Purpose (AR) | Key Files |
|---|---|---|
core/config |
الثوابت والإعدادات العامة | app_config.dart |
core/extensions |
تسهيل الوصول للثيم والترجمة | context_extension.dart |
core/widgets |
الـ Widgets الموحدة للمشروع ككل | app_text_field.dart |
core/database |
تعريف الجداول وعلاقات الداتا | app_database.dart |
features/*/data |
تحويل الداتا من الداتابيز للغة Dart | *_model.dart |
features/*/domain |
القواعد الصافية للميزة (المنطق) | *_entity.dart |
features/*/presentation |
واجهات المستخدم وإدارة حالتها | *_screen.dart, *_bloc.dart |
assets/translations |
ملفات اللغات (عربي/إنجليزي) | ar.dart, en.dart |
android/app |
إعدادات نظام الأندرويد والنشر | AndroidManifest.xml |
ios/Runner |
إعدادات نظام الـ iOS والنشر | Info.plist |
لقد كانت رحلة طويلة عبر الملفات والمجلدات. لا تنظر إلى هذا الهيكل كقيود، بل انظر إليه كخريطة كنز ترشدك إلى كود مستقر، قابل للاختبار، وسهل التطوير. في الفصل القادم، سنبدأ بالتعامل مع البيانات الحقيقية في "قاعدة البيانات".
It's been a long journey through files and folders. Don't look at this structure as a restriction, but as a treasure map guiding you to stable, testable, and easy-to-develop code. In the next chapter, we'll start dealing with real data in the "Database".
دلوقتي بقيت عارف إزاي MRE CashBook بيحافظ على نظافة وترتيب ملفاته. الفصل ده هو حجر الأساس لفهم باقي الفصول.
Now you know how MRE CashBook maintains its file cleanliness and order. This chapter is the foundation for understanding the rest of the course.