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


يُعد Compass تطبيقاً غنياً بالمزايا (Feature-rich App)، ويتضمن:
- واجهات متعددة وشاشات متنوعة (Splash, Home, Search, Booking).
- اتصالاً بخادم HTTP لجلب البيانات.
- بيئات متعددة (Development / Staging / Production).
- نظام سمات (Themes) مخصص يعكس هوية العلامة التجارية.
- اختبارات شاملة (High Test Coverage).
بكلمات أخرى، هو نموذج مصغّر لتطبيق Flutter حقيقي في بيئة إنتاجية.
البنية المعمارية في Compass
تم تصميم Compass على نمط MVVM (Model–View–ViewModel)
وهو النمط الذي توصي به Google لبناء تطبيقات Flutter قابلة للصيانة والتوسع.
ما الذي ستتعلمه من هذه الدراسة؟
من خلال تفكيك “ميزة الصفحة الرئيسية (Home)” في Compass، ستتعلّم:
- كيف تطبّق بنية Flutter الموصى بها باستخدام Repositories وServices.
- كيفية استخدام Command Pattern لتحديث الواجهة بأمان عند تغيّر البيانات.
- كيفية إدارة الحالة عبر ChangeNotifier وListenable.
- كيفية تطبيق Dependency Injection باستخدام
package:provider. - كيفية إعداد اختبارات منظمة في بنية Flutter كبيرة.
- وكيفية بناء هيكل مجلدات احترافي لمشاريع ضخمة.
تنظيم هيكل المشروع
تنظيم الكود ليس مجرد ترتيب ملفات — بل هو أساس قابلية التوسع والعمل الجماعي.
في Compass، تم الجمع بين أسلوبين من التنظيم:
- التنظيم حسب المزايا (By Feature)
كل ميزة مثل “Auth” أو “Search” لها مجلدها الخاص الذي يحتوي على:auth_viewmodel.dartlogin_usecase.dartlogout_usecase.dartlogin_screen.dartlogout_button.dart
- التنظيم حسب النوع (By Type)
حيث يتم تجميع الملفات المتشابهة في مجلدات مشتركة مثل:repositories/services/models/viewmodels/
هيكل المجلدات في Compass
lib
├─ ui
│ ├─ core
│ │ ├─ ui (widgets المشتركة)
│ │ └─ themes (نظام السمات)
│ └─ <FEATURE NAME>
│ ├─ view_model
│ │ └── <view_model class>.dart
│ └─ widgets
│ ├── <feature_name>_screen.dart
│ └── <other_widgets>
├─ domain
│ └─ models
│ └── <model_name>.dart
├─ data
│ ├─ repositories
│ │ └── <repository_class>.dart
│ ├─ services
│ │ └── <service_class>.dart
│ └─ model
│ └── <api_model_class>.dart
├── config
├── utils
├── routing
├── main_staging.dart
├── main_development.dart
└── main.dart
أما الاختبارات فتم تنظيمها بنفس المنطق:
test
├── data
├── domain
├── ui
└── utils
ومجلد إضافي باسم testing/ يحتوي على Fakes وMocks لتسهيل تنفيذ الاختبارات.
🎨 الطبقات المعمارية في Compass
كما ذكرنا سابقاً، يعتمد Compass على نمط MVVM —
إليك كيف تتوزع مسؤوليات كل طبقة داخل التطبيق:
1. UI Layer
تتعامل مع عرض البيانات وتفاعل المستخدم.
- تتكوّن من Views وViewModels.
- كل ميزة لها
Viewواحدة وViewModelواحد على الأقل. - مثال:
LoginView↔LoginViewModel
View
تمثل واجهة المستخدم (Widgets).
لا تحتوي على أي منطق عمل، باستثناء منطق بسيط للعرض، الحركة، أو التوجيه.
ViewModel
تحتوي على المنطق الفعلي لعرض البيانات، وتتحكم في الحالة (State).
مثلاً:
- جلب قائمة الرحلات من Repository.
- فرزها أو تصفيتها.
- تمرير النتائج إلى الواجهة.
2. Data Layer
تمثل الطبقة المسؤولة عن إدارة البيانات.
وتنقسم إلى جزئين رئيسيين:
Repositories
هي مصدر الحقيقة (Single Source of Truth) للتطبيق.
مسؤولة عن:
- جلب البيانات من الخدمات.
- الكاش.
- معالجة الأخطاء وإعادة المحاولة (Retry).
- تحويل البيانات إلى Domain Models قابلة للاستخدام في ViewModel.
مثال:
UserProfileRepositoryقد يُرجعStream<UserProfile>يتم تحديثه عند تسجيل الدخول أو الخروج.
Services
الطبقة الأدنى — تتعامل مباشرة مع:
- REST APIs
- قاعدة البيانات المحلية
- مكونات النظام
- الملفات
تُرجع Futures أو Streams، ولا تحتفظ بأي حالة داخلها.
3. Domain Layer (اختيارية)
في الميزات المعقدة، تُستخدم طبقة إضافية تسمى Domain Layer وتحتوي على Use-Cases.
هذه الطبقة تعمل كوسيط ذكي لتبسيط العلاقة بين البيانات والواجهة.
تُستخدم فقط في الحالات التالية:
- عند الحاجة لدمج بيانات من مصادر متعددة.
- عندما يصبح منطق العمل معقداً.
- أو عندما يتكرر نفس المنطق في أكثر من ViewModel.
مثال:FetchUserTripsUseCase يقوم بجلب بيانات الرحلات من عدة مستودعات ودمجها في كائن واحد جاهز للعرض.
⚖️ استخدم Use-Cases فقط عندما تستدعي الحاجة، لأن الإفراط فيها يزيد التعقيد.
إدارة الحالة والاعتمادية
يعتمد التطبيق على:
- ChangeNotifier وListenable لإدارة الحالة.
- Command Pattern لتحديث الواجهة بأمان عند تغيّر البيانات.
- package:provider لتنفيذ Dependency Injection بسهولة.
الاختبار والمرونة
من خلال هذه البنية، يمكن اختبار كل جزء بشكل مستقل:
- اختبار الـ ViewModels بمنطقها فقط باستخدام Mocks.
- اختبار الواجهة Widgets Tests بدون التأثير على البيانات.
- اختبار الـ Repositories وServices كوحدات مستقلة.
وهذا يعني تطبيقاً أكثر مرونة، يسهل تطويره دون الخوف من كسر وظائف أخرى.
خيارات أخرى للبنية
رغم أن Compass استخدم MVVM + Provider، إلا أن نفس الأفكار يمكن تطبيقها باستخدام أدوات أخرى مثل:
- Riverpod
- Flutter_Bloc
- Signals
المهم ليس الأداة، بل الالتزام بالمبادئ الأساسية للهندسة:
فصل المنطق عن الواجهة، وجود مصدر واحد للحقيقة، واعتماد تدفق بيانات واضح أحادي الاتجاه.
وإذا تأملت جيداً، ستجد أن معظم البنى المعمارية في النهاية تدور حول فكرة MVVM — بأسماء وأساليب مختلفة فقط.
الخلاصة
دراسة حالة تطبيق Compass تبرهن أن الهندسة الصحيحة ليست نظرية، بل ممارسة قابلة للتطبيق في مشاريع حقيقية.
ببنية واضحة، وتنظيم دقيق للملفات، يمكنك بناء تطبيق Flutter ضخم يعمل بكفاءة، ويكبر دون أن يتعقّد.
ابدأ اليوم بتنظيم مشروعك وفق هذه المبادئ، وستشكر نفسك لاحقاً عندما يصبح التوسع والاختبار مجرد خطوة بسيطة لا كابوساً تقنياً.
