CI/CD والنشر على متاجر التطبيقات

نكهات البناء وأنواع المنتجات في Flutter

16 دقيقة الدرس 3 من 12

نكهات البناء وأنواع المنتجات في Flutter

تحتاج معظم تطبيقات الإنتاج إلى أكثر من صيغة بناء واحدة. تتيح لك نكهة البناء (تُسمى نوع المنتج على Android أو مخطط/هدف على iOS) تجميع نفس قاعدة الكود إلى تطبيقات متميزة متعددة — مثل dev وstaging وproduction — لكل منها معرّف حزمة خاص ، واسم تطبيق، وأيقونة، ونقطة نهاية API، وهوية توقيع. يربط علم --flavor في Flutter كلتا المنصتين بحيث يقود أمر واحد التهيئة الأصلية الصحيحة.

ملاحظة: النكهات مفهوم أصلي: يتعامل معها Android في Gradle، ويتعامل معها iOS في Xcode. لا يبتكر Flutter آلية جديدة — بل يعين وسيطة --flavor مباشرةً إلى نكهة منتج Gradle المطابقة أو اسم مخطط Xcode. يجب تهيئة كلتا المنصتين بشكل مستقل.

لماذا تستخدم النكهات؟

  • التثبيت المتوازي: com.example.app.dev وcom.example.app معرّفات حزم مختلفة، لذا يمكنها التعايش على نفس الجهاز.
  • الاختبار الآمن: يصل المختبرون إلى API التجربة؛ ويصل المستخدمون النهائيون إلى الإنتاج — بدون أي تغيير في الكود بين البنيات.
  • العلامة التجارية المتميزة: يمكن أن تُظهر بنيات التطوير أيقونة حمراء أو لاحقة "DEV" في اسم التطبيق حتى لا تشحن بنية تصحيح أخطاء عن طريق الخطأ.
  • تكامل CI/CD: تمرر خط الأنابيب الخاص بك --flavor production وينتج الأثر الصحيح تلقائيًا.

تهيئة النكهات على Android (Gradle)

افتح android/app/build.gradle وأضف سطر flavorDimensions بالإضافة إلى كتلة productFlavors داخل إغلاق android {}:

android/app/build.gradle — أنواع المنتجات

android {
    // ...التهيئة الموجودة...

    flavorDimensions "environment"

    productFlavors {
        dev {
            dimension "environment"
            applicationIdSuffix ".dev"
            versionNameSuffix "-dev"
            resValue "string", "app_name", "MyApp DEV"
        }
        staging {
            dimension "environment"
            applicationIdSuffix ".staging"
            versionNameSuffix "-staging"
            resValue "string", "app_name", "MyApp Staging"
        }
        production {
            dimension "environment"
            // لا لاحقة — هذا هو معرّف حزمة الإصدار
            resValue "string", "app_name", "MyApp"
        }
    }
}

يمكن لكل نكهة تجاوز معرّف التطبيق، واسم الإصدار، وقيم الموارد (مثل تسمية التطبيق)، وحتى الإشارة إلى مجموعات مصادر خاصة بالنكهة (مثل src/dev/ لملف google-services.json خاص بالتطوير).

نصيحة: ضع ملفات google-services.json الخاصة بكل نكهة تحت android/app/src/dev/ وandroid/app/src/staging/ وandroid/app/src/production/. يختار Gradle الملف الصحيح تلقائيًا عند بناء النكهة المقابلة.

تهيئة النكهات على iOS (Xcode)

يستخدم iOS المخططات وتهيئات البناء بدلاً من نكهات Gradle. النهج الموصى به هو:

  • تكرار هدف Runner الافتراضي لكل بيئة (أو استخدام هدف واحد مع تهيئات بناء متعددة).
  • إنشاء مخطط Xcode واحد لكل نكهة — تسميتها بنفس اسم نكهة Gradle بالضبط (dev، staging، production). يطابق Flutter بالاسم.
  • في تهيئة البناء لكل مخطط، تعيين PRODUCT_BUNDLE_IDENTIFIER وPRODUCT_NAME فريدين.

ios/Runner/Info.plist — استخدام متغير إعداد البناء للاسم

<key>CFBundleDisplayName</key>
<string>$(APP_DISPLAY_NAME)</string>

<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>

ثم في كل تهيئة بناء في Xcode (Dev، Staging، Production)، عرّف APP_DISPLAY_NAME وPRODUCT_BUNDLE_IDENTIFIER بقيمهما الخاصة بكل بيئة. يبقي هذا Info.plist عاماً بينما تحقن كل تهيئة القيم الصحيحة في وقت البناء.

قراءة النكهة في وقت التشغيل مع Dart

يمكن لـ Flutter تمرير اسم النكهة إلى كود Dart عبر --dart-define. اجمعه مع علم --flavor حتى تعرف دائمًا البيئة الحالية:

lib/config/environment.dart — تهيئة مدركة للنكهة

// يُمرَّر في وقت البناء:
// flutter run --flavor dev --dart-define=FLAVOR=dev
// flutter build apk --flavor production --dart-define=FLAVOR=production

enum AppFlavor { dev, staging, production }

class Environment {
  static const String _flavor =
      String.fromEnvironment('FLAVOR', defaultValue: 'dev');

  static AppFlavor get flavor {
    switch (_flavor) {
      case 'staging':
        return AppFlavor.staging;
      case 'production':
        return AppFlavor.production;
      default:
        return AppFlavor.dev;
    }
  }

  static String get apiBaseUrl {
    switch (flavor) {
      case AppFlavor.dev:
        return 'https://api-dev.example.com';
      case AppFlavor.staging:
        return 'https://api-staging.example.com';
      case AppFlavor.production:
        return 'https://api.example.com';
    }
  }

  static bool get isProduction => flavor == AppFlavor.production;
  static bool get showDebugBanner => !isProduction;
}

أيقونات التطبيق الخاصة بكل نكهة

استخدم حزمة flutter_launcher_icons مع ملفات تهيئة متعددة — واحدة لكل نكهة. سمّ كل ملف تهيئة باسم نكهته:

  • flutter_launcher_icons-dev.yaml
  • flutter_launcher_icons-staging.yaml
  • flutter_launcher_icons-production.yaml

نفّذ dart run flutter_launcher_icons -f flutter_launcher_icons-dev لكل نكهة. تضع الحزمة الأيقونات المولّدة في مجموعة المصادر الصحيحة للنكهة تلقائيًا.

تحذير: يجب أن يطابق اسم مخطط Xcode سلسلة --flavor تمامًا (حساس لحالة الأحرف). إذا كانت نكهة Gradle لديك dev لكن مخطط Xcode لديك Dev (حرف D كبير)، ستفشل بنية iOS. أنشئ اتفاقية تسمية بأحرف صغيرة متسقة عبر كلتا المنصتين من البداية.

التشغيل والبناء بالنكهات

بمجرد تهيئة كلتا المنصتين، استخدم علم --flavor مع كل أمر Flutter:

أوامر النكهات الشائعة

# تشغيل نكهة dev على جهاز متصل
flutter run --flavor dev --dart-define=FLAVOR=dev

# بناء APK للتجربة
flutter build apk --flavor staging --dart-define=FLAVOR=staging

# بناء App Bundle للإنتاج لمتجر Play
flutter build appbundle --flavor production --dart-define=FLAVOR=production --release

# البناء لـ iOS (يجب أن يكون مخطط Xcode موجودًا)
flutter build ios --flavor production --dart-define=FLAVOR=production --release

ملخص

تعد نكهات البناء المعيار الاحترافي لشحن متغيرات بيئات متعددة من قاعدة كود واحدة. يعرّفها Android كـ productFlavors في Gradle؛ ويعرّفها iOS كمخططات مع تهيئات بناء متميزة. يربط علم --flavor في Flutter كليهما معًا. اجمع النكهات مع --dart-define لحقن قيم خاصة بالبيئة في Dart، واستخدم تهيئات الأيقونات لكل نكهة لمنح كل بنية أيقونة تشغيل مرئية متميزة. هذا الإعداد يزيل تبديل الملفات اليدوي، ويمنع حوادث الإنتاج، ويتكامل بسلاسة مع أي خط أنابيب CI/CD.