مقدمة
مشروع Flutter الجديد يبدأ بملف main.dart واحد، وسرعان ما يصبح آلاف الأسطر إن لم تنظمه. التنظيم الجيد لا يعني طبقات معقدة؛ يعني أن تعرف أين تضع كل شيء، وأن تستطيع حذف ميزة أو تعديلها دون لمس الباقي.
أهداف التعلم
- تنظيم المشروع حسب الميزات (Feature-first).
- الفصل بين الواجهة والمنطق والبيانات داخل كل ميزة.
- إدارة الإعدادات والمتغيرات حسب البيئة.
- تجهيز الترجمة والاتجاه العربي مبكراً.
1. هيكل مقترح
lib/
main.dart
app/
app.dart # MaterialApp، السمات، التوجيه
router.dart
theme.dart
core/
network/api_client.dart
errors/failure.dart
utils/
features/
topics/
data/
topics_api.dart
topics_repository.dart
models/topic.dart
presentation/
topics_page.dart
topics_controller.dart
widgets/topic_tile.dart
auth/
data/ …
presentation/ …
l10n/
app_ar.arb
app_en.arb
test/
features/topics/…
كل ما يخص «المواضيع» داخل مجلد واحد. core للأشياء المشتركة فعلاً فقط؛ لا تجعله سلة مهملات.
2. النموذج والتحويل من JSON
class Topic {
const Topic({required this.id, required this.title, required this.replyCount});
final String id;
final String title;
final int replyCount;
factory Topic.fromJson(Map<String, dynamic> json) => Topic(
id: json['id'] as String,
title: json['title'] as String,
replyCount: (json['replyCount'] as num?)?.toInt() ?? 0,
);
}
التحويل في مكان واحد يعني أن تغيير شكل الـ API يتطلب تعديل ملف واحد.
3. المستودع يخفي مصدر البيانات
class TopicsRepository {
TopicsRepository(this._api);
final TopicsApi _api;
Future<List<Topic>> latest() async {
final rows = await _api.fetchLatest();
return rows.map(Topic.fromJson).toList(growable: false);
}
}
الواجهة لا تعرف أن البيانات من HTTP؛ يمكنك لاحقاً إضافة تخزين مؤقت محلي دون تغيير الصفحات.
4. الإعدادات حسب البيئة
لا تضع عنوان الخادم ثابتاً في الكود. مرره وقت البناء:
flutter run --dart-define=API_BASE=https://api.example.com
const apiBase = String.fromEnvironment('API_BASE', defaultValue: 'http://10.0.2.2:3000');
ولا تضع أسراراً في التطبيق أبداً: أي مفتاح داخل ملف APK أو IPA يمكن استخراجه. الأسرار مكانها الخادم.
5. العربية والاتجاه من اليوم الأول
# pubspec.yaml
dependencies:
flutter_localizations:
sdk: flutter
flutter:
generate: true
MaterialApp(
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
locale: const Locale('ar'),
);
مع اللغة العربية يقلب Flutter الاتجاه تلقائياً. استخدم EdgeInsetsDirectional وAlignmentDirectional بدل left/right حتى يعمل التخطيط في الاتجاهين.
تمرين عملي
- أنشئ ميزة
topicsبالهيكل أعلاه، مع مستودع يعيد بيانات وهمية. - أضف ملفي ترجمة
app_ar.arbوapp_en.arbلعنوان الصفحة وجرّب تبديل اللغة. - اكتب اختبار وحدة لـ
Topic.fromJsonمع حقلreplyCountمفقود.
أخطاء شائعة
- منطق الشبكة داخل
build()فيتكرر الطلب مع كل إعادة رسم. - ملف
utils.dartضخم يحوي كل شيء. - مفاتيح API داخل التطبيق.
EdgeInsets.only(left: …)في تطبيق عربي.
الخلاصة
نظّم حسب الميزات، وافصل الواجهة عن البيانات داخل كل ميزة، ومرر الإعدادات وقت البناء، وجهّز الترجمة والاتجاه مبكراً. ستشكر نفسك عندما يصل التطبيق إلى عشرين شاشة.
مصادر مقترحة
- توثيق Flutter: «Guide to app architecture».
- توثيق Flutter: «Internationalizing Flutter apps».
- حزمة
very_good_analysisلقواعد تحليل صارمة.