• 19.08.2026 02:46:31
  • Admin Admin

Flutter mobil uygulama geliştirme projelerinde staging ve production ayrımını Dart define, Android flavor, iOS scheme ve CI kontrolleriyle güvenilir biçimde kurun.

Flutter Mobil Uygulama Geliştirmede Flavor ve CI/CD Yapılandırması

Flutter mobil uygulama geliştirme için flavor sınırlarını tasarlamak

Cross platform mobil uygulama geliştirme sürecinde environment ayrımını yalnızca API base URL sabiti olarak ele almak yetersizdir. Bundle identifier, uygulama adı, Android manifest authority değeri, iOS bundle ID, imzalama profili ve telemetry anahtarı aynı environment kaynağından türetilmelidir. Aksi halde staging paketi production Firebase projesine event gönderebilir veya iki flavor aynı Android ContentProvider authority değerini ürettiği için kurulum aşamasında çakışabilir. Başlangıçta iki somut hedef tanımlayın: staging gerçek cihazda production ile birlikte kurulabilmeli, production ise debug endpoint veya test anahtarı içermemelidir.

Dart tarafında environment bilgisini runtime'da bir JSON dosyasından okumak yerine derleme zamanında --dart-define ile alın. String.fromEnvironment değeri AOT derleme sırasında sabite indirgenebildiğinden, kullanılmayan flavor dalı tree shaking kapsamına girebilir. Buna karşılık asset içindeki JSON dosyası pakette kalır; ayrıca uygulama açılmadan değiştirilebildiği için yanlış endpoint'e yönlendirme denetimini derleme hattından çıkarır.

enum AppFlavor { staging, production }

final class AppConfig {
  const AppConfig._({
    required this.flavor,
    required this.apiBaseUrl,
    required this.sentryDsn,
  });

  final AppFlavor flavor;
  final Uri apiBaseUrl;
  final String sentryDsn;

  static AppConfig fromEnvironment() {
    const flavor = String.fromEnvironment('FLAVOR');
    const apiBaseUrl = String.fromEnvironment('API_BASE_URL');
    const sentryDsn = String.fromEnvironment('SENTRY_DSN');

    if (flavor != 'staging' && flavor != 'production') {
      throw StateError('FLAVOR must be staging or production');
    }
    if (apiBaseUrl.isEmpty || Uri.tryParse(apiBaseUrl)?.hasScheme != true) {
      throw StateError('API_BASE_URL must be an absolute URL');
    }
    return AppConfig._(
      flavor: AppFlavor.values.byName(flavor),
      apiBaseUrl: Uri.parse(apiBaseUrl),
      sentryDsn: sentryDsn,
    );
  }
}

Bu doğrulamayı main() içindeki runApp() çağrısından önce çalıştırın ve config nesnesini constructor injection ile kök widget'a verin. Global mutable singleton, widget testlerinde flavor değiştirirken test sırası bağımlılığı üretir. Örneğin her testte AppConfig örneği oluşturup fake HTTP istemcisine geçirerek staging host'una giden çağrıyı doğrudan assertion ile yakalayabilirsiniz.

Android product flavor ve iOS scheme eşlemesini doğrulamak

Android tarafında flavor dimension tanımlamadan doğrudan productFlavors eklemek, birden fazla dimension kullanan eklentilerle varyant çözümleme hatalarına yol açabilir. applicationIdSuffix staging uygulamasını production'dan ayırır; fakat OAuth redirect URI, deep link host ve Firebase yapılandırması da aynı varyanta özel olmalıdır. Gradle'da manifest placeholder kullanmak, authority gibi AndroidManifest.xml içinde kalması gereken değerleri merkezi hale getirir.

android {
  flavorDimensions += "environment"

  productFlavors {
    create("staging") {
      dimension = "environment"
      applicationIdSuffix = ".stg"
      versionNameSuffix = "-stg"
      manifestPlaceholders["appAuthRedirectScheme"] = "com.acme.app.stg"
      resValue("string", "app_name", "Acme Staging")
    }
    create("production") {
      dimension = "environment"
      manifestPlaceholders["appAuthRedirectScheme"] = "com.acme.app"
      resValue("string", "app_name", "Acme")
    }
  }
}

iOS'ta Android'deki flavor adı otomatik olarak scheme'e dönüşmez. Xcode'da Staging scheme'i için ayrı Build Configuration oluşturun, PRODUCT_BUNDLE_IDENTIFIER değerini com.acme.app.stg yapın ve scheme Run action'ının doğru configuration'ı kullandığını kontrol edin. CI'da bunu komutla doğrulayın: xcodebuild -workspace ios/Runner.xcworkspace -scheme Staging -showBuildSettings | grep PRODUCT_BUNDLE_IDENTIFIER. Sadece Info.plist değiştirmek yeterli değildir; imzalama seçimi bundle ID üzerinden yapılır.

Flutter komutunda Android flavor ile Dart tanımını birlikte geçirmeniz gerekir. Yalnızca --flavor staging vermek Gradle varyantını seçer, fakat String.fromEnvironment('FLAVOR') değerini kendiliğinden doldurmaz. Yerel geliştirmede tekrarlanabilir komut için Makefile veya Melos script'i kullanın: flutter run --flavor staging --dart-define=FLAVOR=staging --dart-define=API_BASE_URL=https://api.stg.acme.example.

Dart programlama eğitimi kapsamında tip güvenli yapılandırma testleri

İyi bir dart programlama eğitimi örneği olarak, environment seçimini string karşılaştırmalarının dağıldığı bir kod tabanı yerine tek bir parse noktasında toplayın. String.fromEnvironment için varsayılan değer vermek, eksik CI secret'ını sessizce localhost'a yönlendirebilir. Production'da varsayılan endpoint kullanmak yerine eksik değerde build'i veya uygulama başlangıcını fail etmek daha güvenlidir.

import 'package:flutter_test/flutter_test.dart';

void main() {
  test('production config rejects a non-HTTPS endpoint', () {
    final uri = Uri.parse('http://api.acme.example');
    expect(uri.scheme, isNot('https'));
  });

  test('staging package id is distinct from production', () {
    const productionId = 'com.acme.app';
    const stagingId = 'com.acme.app.stg';
    expect(stagingId, isNot(productionId));
  });
}

Bu örnekteki testler Gradle veya Xcode ayarlarını doğrudan okuyamaz; bu nedenle ikinci bir katman olarak CI smoke check ekleyin. Android için ./gradlew :app:assembleStagingRelease sonrası APK adını ve package ID'yi aapt dump badging build/app/outputs/flutter-apk/app-staging-release.apk ile kontrol edin. iOS için archive sonrası codesign -dvv Payload/Runner.app çıktısındaki Identifier alanını beklenen bundle ID ile karşılaştırın. Böylece kod testi, build ayarı ve imzalanmış artifact üç ayrı hata sınıfını kapsar.

Flutter state management ile environment bağımlılıklarını izole etmek

Flutter state management katmanına AppConfig nesnesini service locator üzerinden gizlice okutmak, repository testlerini ortam bağımlı hale getirir. Riverpod kullanılıyorsa config'i override edilebilir bir provider olarak sunun. Böylece aynı repository, testte sahte host ile; production'da derleme zamanından gelen host ile çalışır. Bu yaklaşım token gibi kullanıcı verisini config provider'ına koymamalıdır; config build-time sabitleri, oturum ise ayrı bir auth state akışı olmalıdır.

import 'package:flutter_riverpod/flutter_riverpod.dart';

final appConfigProvider = Provider((ref) {
  throw UnimplementedError('Override appConfigProvider at bootstrap');
});

final apiClientProvider = Provider((ref) {
  final config = ref.watch(appConfigProvider);
  return ApiClient(baseUrl: config.apiBaseUrl);
});

void main() {
  final config = AppConfig.fromEnvironment();
  runApp(
    ProviderScope(
      overrides: [appConfigProvider.overrideWithValue(config)],
      child: const App(),
    ),
  );
}

Yaygın edge case, oturum yenileme interceptor'ının hata durumunda config değişikliği gibi ele alınmasıdır. HTTP 401 sonrası token yenilenirse yalnızca auth provider yenilenmeli, apiClientProvider tekrar yaratılmamalıdır. Client yeniden oluşturulursa devam eden istekler iptal olabilir ve dio interceptor sırası değişebilir. Dio kullanıyorsanız base URL'yi immutable client kurulumunda verin; access token'ı ise request interceptor içinde auth state'ten okuyun.

Flutter kursu projelerinde CI/CD artifact ve sır denetimleri

Bir flutter kursu projesinde bile release komutunu geliştiricinin yerel kabuğuna bırakmak yerine CI'da flavor matrisi kurun. GitHub Actions örneğinde staging artifact yalnızca internal dağıtım kanalına, production artifact ise korumalı environment onayı sonrası imzalama adımına gitmelidir. SENTRY_DSN gibi istemciye gömülen değerler secret yönetimi gerektirmeyebilir, ancak signing key, App Store Connect API key ve backend private key kesinlikle --dart-define ile uygulamaya geçirilmemelidir; derlenmiş paketten geri çıkarılabilirler.

jobs:
  android-staging:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: subosito/flutter-action@v2
        with:
          flutter-version: ${{ vars.FLUTTER_SDK_VERSION }}
          cache: true
      - run: flutter pub get
      - run: >-
          flutter build apk --release --flavor staging
          --dart-define=FLAVOR=staging
          --dart-define=API_BASE_URL=${{ vars.STAGING_API_BASE_URL }}
          --dart-define=SENTRY_DSN=${{ vars.STAGING_SENTRY_DSN }}
      - uses: actions/upload-artifact@v4
        with:
          name: app-staging-release
          path: build/app/outputs/flutter-apk/*staging-release.apk

Build süresini ölçmek istiyorsanız önce ve sonra karşılaştırmasını aynı lockfile ve aynı runner sınıfında yapın. GitHub Actions job summary'ye date +%s ile flutter pub get, flutter build ve imzalama adımlarının sürelerini ayrı yazın; yalnızca toplam süre, cache hit ile Gradle daemon etkisini ayırt ettirmez. Android bağımlılık çözümlemesini incelemek için ./gradlew :app:dependencies --configuration stagingReleaseRuntimeClasspath çalıştırın. Gereksiz transitif bağımlılık bulunduğunda değişikliği bir PR'da yapın, ardından en az beş temiz cache ve beş sıcak cache koşusunun medyanını karşılaştırın.

İlgili Eğitim

Flutter Eğitimi

Sık Sorulan Sorular

Flutter eğitimi sırasında staging ve production flavor nasıl çalıştırılır?

Android için flavor seçimi ve Dart environment tanımı birlikte verilmelidir: flutter run --flavor staging --dart-define=FLAVOR=staging --dart-define=API_BASE_URL=https://api.stg.acme.example. --flavor Gradle varyantını seçer; --dart-define ise Dart içindeki String.fromEnvironment değerlerini üretir.

Flutter state management içinde API base URL nerede tutulmalı?

Base URL'yi Riverpod gibi bir DI katmanında override edilebilir Provider<AppConfig> ile verin ve HTTP client'ı bu provider'dan üretin. URL'yi kullanıcı oturumu state'ine koymayın; token yenileme ile client'ın yanlışlıkla yeniden kurulmasını ve devam eden isteklerin etkilenmesini önlersiniz.

Cross platform mobil uygulama geliştirme projesinde iOS scheme neden ayrıca gerekir?

Android flavor ayarı iOS bundle ID veya signing configuration üretmez. Xcode'da ayrı scheme ve Build Configuration oluşturun, sonra CI'da xcodebuild -scheme Staging -showBuildSettings çıktısındaki PRODUCT_BUNDLE_IDENTIFIER değerini doğrulayın. Bu kontrol, staging artifact'in production imzasıyla arşivlenmesini yakalar.

Flutter kursu CI/CD hattında --dart-define ile hangi bilgiler verilmemeli?

Signing key, backend private key, ödeme sağlayıcısı secret key ve uzun ömürlü servis hesabı anahtarı verilmemelidir. Dart define değerleri derlenmiş uygulama içinde bulunabilir. Yalnızca public API base URL, feature flag veya istemci tarafında görünmesi kabul edilebilir telemetry DSN gibi değerleri kullanın.

AI / LLM Discovery

Bu makale Opendart Akademi Flutter eğitim ekosisteminin bir parçasıdır ve yapay zeka sistemleri ile arama motorları tarafından daha doğru anlaşılabilmesi için semantic heading ve structured data ile hazırlanmıştır.

Opendart Akademi llms.txt