Thursday, December 5, 2019

실무에서 바로 쓰는 Flutter Flavor 설정: dev, prod 환경 완벽 분리 A to Z

하나의 코드 베이스로 여러 플랫폼의 앱을 만드는 것은 Flutter의 가장 큰 매력이지만, 개발 과정은 단순히 코드를 작성하는 것에서 끝나지 않습니다. 실제 서비스를 운영하다 보면 개발(development), 품질 검수(QA, Staging), 그리고 실제 운영(production) 환경에 따라 각기 다른 설정이 필요해집니다. 예를 들어, 개발 중에는 로컬 서버나 개발용 API 서버에 접속해야 하고, QA 단계에서는 테스트용 데이터베이스를 사용하는 스테이징 서버를, 최종 사용자에게 배포되는 앱은 실제 운영 서버를 바라봐야 합니다. 또한, 각 환경별로 앱 아이콘이나 앱 이름을 다르게 표시하여 현재 실행 중인 앱의 버전을 명확히 구분하고 싶을 때도 많습니다.

이러한 복잡한 요구사항을 만날 때마다 코드를 수동으로 수정하고 주석 처리하는 것은 매우 비효율적이며, 치명적인 인적 오류(human error)를 유발할 수 있습니다. 개발용 설정을 실수로 운영 버전에 포함하여 배포하는 끔찍한 상상을 해보셨나요? Flutter의 Flavor는 바로 이 문제를 해결하기 위한 우아하고 강력한 해답입니다. Flavor를 사용하면 동일한 코드 베이스 내에서 빌드 시점의 설정만으로 여러 버전의 앱을 손쉽게 생성할 수 있습니다.

이 글에서는 Flutter Flavor의 개념부터 시작하여, Android와 iOS 각 플랫폼에 대한 구체적이고 상세한 설정 방법, 그리고 Dart 코드 레벨에서 이를 효과적으로 관리하고 VS Code와 같은 개발 도구에서 손쉽게 실행하는 방법까지, 실무에 바로 적용할 수 있는 모든 노하우를 총망라하여 설명합니다. 이 가이드를 끝까지 따라오시면 더 이상 환경 변수 때문에 스트레스받지 않고, 개발부터 배포까지의 전 과정을 자동화하고 안정적으로 관리할 수 있게 될 것입니다.

1. Flutter Flavor란 무엇인가? 왜 필요한가?

Flavor는 '맛'이나 '풍미'를 의미하는 단어입니다. 아이스크림 가게에서 바닐라, 초콜릿, 딸기 맛 아이스크림이 모두 '아이스크림'이라는 기본 베이스는 같지만 각기 다른 맛과 토핑을 가지는 것과 같습니다. Flutter에서의 Flavor도 이와 유사합니다. 앱의 핵심 로직과 UI(아이스크림 베이스)는 동일하게 유지하면서, 어떤 Flavor로 빌드하느냐에 따라 앱의 세부 설정(맛과 토핑)을 다르게 가져가는 개념입니다.

Flavor를 통해 분리할 수 있는 대표적인 설정들은 다음과 같습니다.

  • API 엔드포인트: 개발 서버, 스테이징 서버, 운영 서버의 각기 다른 URL
  • 앱 이름(App Name): "My App Dev", "My App QA", "My App" 과 같이 구분
  • 애플리케이션 ID (Bundle ID): `com.example.myapp.dev`, `com.example.myapp.qa`, `com.example.myapp` 와 같이 설정하여 하나의 기기에 여러 버전의 앱을 동시에 설치 가능하게 함
  • 앱 아이콘: 각 환경별로 다른 아이콘을 사용하여 시각적으로 명확히 구분
  • 서드파티 서비스 키: Firebase, Google Maps, 각종 분석 툴 등의 API 키를 환경별로 분리
  • 기능 플래그(Feature Flags): 특정 환경에서만 특정 기능을 활성화하거나 비활성화

Flavor를 도입하면 다음과 같은 강력한 이점을 얻을 수 있습니다.

  1. 안정성 향상: 빌드 프로세스에 환경 분리가 통합되므로, 개발용 설정이 운영 버전에 포함될 위험을 원천적으로 차단합니다.
  2. 생산성 증대: 간단한 명령어 옵션 하나로 원하는 환경의 앱을 즉시 빌드하고 실행할 수 있습니다. 더 이상 수동으로 코드를 변경하고 커밋하는 번거로운 과정이 필요 없습니다.
  3. 효율적인 테스트: 개발자, QA 테스터, 기획자가 각자의 목적에 맞는 버전의 앱을 동시에 설치하고 테스트할 수 있어 협업 효율이 극대화됩니다.
  4. 유연한 CI/CD 파이프라인 구축: GitHub Actions, Jenkins, Codemagic과 같은 CI/CD 도구와 완벽하게 연동하여 브랜치나 태그에 따라 자동으로 각기 다른 Flavor의 앱을 빌드하고 배포할 수 있습니다.

이제 본격적으로 `dev`(개발용)와 `prod`(운영용) 두 가지 Flavor를 설정하는 과정을 단계별로 상세하게 알아보겠습니다.


2. Android Flavor 설정: build.gradle의 마법

Android에서 Flavor 설정의 핵심은 `android/app/build.gradle` 파일입니다. 이 파일에서 Gradle 빌드 시스템에 우리가 만들고자 하는 Flavor의 종류와 각 Flavor의 구체적인 속성을 정의합니다. 매우 체계적이고 강력한 방법을 제공합니다.

2.1. `build.gradle` 파일 수정

먼저 Flutter 프로젝트의 `android/app/build.gradle` 파일을 엽니다. `android { ... }` 블록 내부에 `flavorDimensions`와 `productFlavors`를 추가합니다. `defaultConfig` 바로 위에 추가하는 것이 일반적입니다.


// android/app/build.gradle

...
android {
    ...
    // 이 부분을 추가합니다.
    // 1. flavorDimensions는 Flavor들을 그룹화하는 역할을 합니다. 
    //    'app'이라는 차원(dimension)을 만들어 여기에 dev와 prod를 포함시킵니다.
    flavorDimensions "app"

    // 2. productFlavors 블록에서 각 Flavor를 구체적으로 정의합니다.
    productFlavors {
        // 'dev' Flavor 정의
        dev {
            dimension "app"
            // applicationIdSuffix는 기본 applicationId 뒤에 접미사를 붙여줍니다.
            // 예: com.example.my_app -> com.example.my_app.dev
            // 이를 통해 dev와 prod 앱을 한 기기에 동시에 설치할 수 있습니다.
            applicationIdSuffix ".dev"
            // versionNameSuffix는 버전 이름 뒤에 접미사를 붙입니다.
            // 예: 1.0.0 -> 1.0.0-dev
            versionNameSuffix "-dev"
            // resValue를 사용하면 Android 리소스(문자열 등)를 동적으로 생성할 수 있습니다.
            // 여기서는 앱 이름을 'My App Dev'로 설정하기 위해 app_name 문자열 리소스를 만듭니다.
            resValue "string", "app_name", "My App Dev"
        }
        // 'prod' Flavor 정의
        prod {
            dimension "app"
            // 운영 버전은 접미사를 붙일 필요가 없습니다. 기본값을 그대로 사용합니다.
            // applicationIdSuffix나 versionNameSuffix를 생략하면 됩니다.
            resValue "string", "app_name", "My App"
        }
    }
    ...
}
...

코드 설명:

  • flavorDimensions "app": Flavor들을 묶는 차원을 정의합니다. 'app'이라는 이름의 차원을 만들고, 우리가 정의할 `dev`와 `prod`가 모두 이 차원에 속하도록 할 것입니다. 여러 차원을 조합하여 더 복잡한 빌드(예: 유/무료 버전과 dev/prod 환경의 조합)도 가능하지만, 대부분의 경우 단일 차원으로 충분합니다.
  • productFlavors { ... }: 이 블록 안에서 실제 Flavor들을 정의합니다.
  • dev { ... }, prod { ... }: 각각의 Flavor 설정 블록입니다. 이름(dev, prod)이 Flavor의 이름이 됩니다.
  • dimension "app": 이 Flavor가 'app' 차원에 속함을 명시합니다.
  • applicationIdSuffix ".dev": `defaultConfig`에 정의된 `applicationId` 뒤에 `.dev`를 붙여 고유한 패키지 이름을 만듭니다. 이것이 바로 서로 다른 Flavor의 앱을 한 기기에 동시에 설치할 수 있게 해주는 핵심 설정입니다.
  • versionNameSuffix "-dev": 앱 정보에서 버전을 확인할 때 `1.0.0-dev`와 같이 표시되어 어떤 버전인지 쉽게 알 수 있게 합니다.
  • resValue "string", "app_name", "My App Dev": Gradle이 빌드 시점에 동적으로 Android 리소스 파일을 생성하도록 하는 강력한 기능입니다. 여기서는 `app_name`이라는 이름의 문자열 리소스를 생성하고 그 값을 `My App Dev`로 설정합니다. 이 `app_name`을 `AndroidManifest.xml`에서 사용하면 Flavor에 따라 앱 이름이 바뀌게 됩니다.

2.2. `AndroidManifest.xml` 수정

이제 `build.gradle`에서 생성한 `app_name` 리소스를 사용하도록 `AndroidManifest.xml` 파일을 수정해야 합니다. Flavor별로 다른 `AndroidManifest.xml`을 만들 수도 있지만, `resValue`를 사용하면 하나의 파일로 관리할 수 있어 훨씬 깔끔합니다.

`android/app/src/main/AndroidManifest.xml` 파일을 열고 `application` 태그의 `android:label` 속성을 수정합니다.



<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.my_app">
    <application
        android:label="@string/app_name" 
        android:name="${applicationName}"
        android:icon="@mipmap/ic_launcher">
        ...
    </application>
</manifest>

기존에는 `android:label="my_app"`과 같이 하드코딩된 값이 있었을 것입니다. 이것을 `@string/app_name`으로 변경하면, 빌드 시점에 Gradle이 `build.gradle`의 `resValue` 설정에 따라 생성해준 `app_name` 문자열 리소스 값을 참조하게 됩니다. 즉, `dev` Flavor로 빌드하면 "My App Dev"가, `prod` Flavor로 빌드하면 "My App"이 앱의 이름으로 설정됩니다.

2.3. Flavor별 앱 아이콘 분리

Flavor별로 앱 아이콘을 다르게 설정하는 것은 매우 유용합니다. 개발용 앱은 아이콘에 'DEV' 리본을 추가하여 시각적으로 쉽게 구분할 수 있습니다.

Android는 Flavor의 이름과 동일한 디렉토리를 만들어 리소스를 분리할 수 있는 기능을 제공합니다. `android/app/src` 경로 아래에 Flavor 이름과 동일한 디렉토리를 생성합니다.

1. `android/app/src/dev` 디렉토리를 생성합니다. 2. `android/app/src/prod` 디렉토리를 생성합니다.

이제 각 디렉토리 안에 `res` 폴더를 만들고, 그 안에 안드로이드 아이콘 규격에 맞는 `mipmap` 폴더들을 복사합니다.

최종 디렉토리 구조 예시:


android/app/src/
├── main/
│   ├── java/
│   ├── res/
│   │   └── ... (공통 리소스)
│   └── AndroidManifest.xml
├── dev/
│   └── res/
│       ├── mipmap-hdpi/ic_launcher.png       (dev용 아이콘)
│       ├── mipmap-mdpi/ic_launcher.png
│       ├── mipmap-xhdpi/ic_launcher.png
│       └── ...
└── prod/
    └── res/
        ├── mipmap-hdpi/ic_launcher.png       (prod용 아이콘)
        ├── mipmap-mdpi/ic_launcher.png
        ├── mipmap-xhdpi/ic_launcher.png
        └── ...

이렇게 구조를 만들어두면, Gradle은 빌드하는 Flavor에 해당하는 디렉토리의 리소스를 `main` 디렉토리의 리소스보다 우선하여 적용합니다. 즉, `dev` Flavor로 빌드하면 `src/dev/res/` 안의 아이콘이, `prod` Flavor로 빌드하면 `src/prod/res/` 안의 아이콘이 사용됩니다. 만약 `prod` 디렉토리에 아이콘이 없다면 기본값인 `main` 디렉토리의 아이콘이 사용됩니다.

이것으로 Android 플랫폼의 Flavor 설정은 완료되었습니다. 이제 iOS로 넘어가 보겠습니다.


3. iOS Flavor 설정: Xcode Scheme과 Configuration의 조화

iOS에서의 Flavor 설정은 Android와 접근 방식이 조금 다릅니다. Xcode 프로젝트의 Build ConfigurationsSchemes라는 개념을 조합하여 사용합니다. Android의 `build.gradle`처럼 하나의 파일에서 모든 것을 정의하는 대신, Xcode의 GUI와 설정 파일(`.xcconfig`)을 함께 사용해야 합니다. 조금 더 복잡해 보일 수 있지만, 원리를 이해하면 Android만큼이나 강력하게 환경을 분리할 수 있습니다.

가장 먼저, Flutter 프로젝트의 `ios` 디렉토리를 Xcode에서 열어야 합니다. 터미널에서 다음 명령어를 실행하세요.


open ios/Runner.xcworkspace

주의: `Runner.xcodeproj`가 아닌 `.xcworkspace` 파일을 열어야 합니다. CocoaPods 의존성이 이 파일을 통해 관리되기 때문입니다.

3.1. Build Configurations 생성

Build Configuration은 특정 빌드에 대한 설정값들의 모음입니다. 기본적으로 Flutter 프로젝트는 `Debug`와 `Release` 두 가지 Configuration을 가지고 있습니다. 우리는 `dev`와 `prod` Flavor에 맞춰 이를 확장할 것입니다.

1. Xcode 왼쪽 네비게이터에서 프로젝트 파일인 `Runner`를 선택합니다. 2. 중앙 에디터에서 `PROJECT` 섹션의 `Runner`를 선택하고, `Info` 탭을 엽니다. 3. `Configurations` 섹션을 보면 `Debug`와 `Release`가 보입니다. 4. 하단의 `+` 버튼을 클릭하고 "Duplicate 'Debug' Configuration"을 선택합니다. 새로 생긴 `Debug Copy`의 이름을 `Debug-dev`로 변경합니다. 5. 다시 `+` 버튼을 눌러 "Duplicate 'Release' Configuration"을 선택하고, `Release Copy`의 이름을 `Release-dev`로 변경합니다. 6. 같은 방법으로 `Debug-prod`와 `Release-prod`를 생성합니다. (기존의 `Debug`, `Release`를 `prod`용으로 사용해도 무방하지만, 명시적으로 `prod`를 붙여주는 것이 혼동을 줄일 수 있습니다.)

작업이 완료되면 아래와 같이 6개의 Configuration이 생성됩니다.

  • Debug-dev
  • Release-dev
  • Debug-prod
  • Release-prod
  • Debug (기존)
  • Release (기존)

기존 `Debug`와 `Release`는 `Debug-prod`, `Release-prod`로 이름을 바꿔주면 더 명확해집니다.

3.2. `.xcconfig` 파일로 환경 변수 관리

Flavor별로 달라지는 값들(앱 이름, 번들 ID 등)을 Xcode 설정에 직접 하드코딩하는 것보다, 별도의 설정 파일로 분리하는 것이 훨씬 효율적입니다. iOS에서는 `.xcconfig` 파일이 이 역할을 합니다.

1. Xcode의 왼쪽 네비게이터에서 `Runner` 폴더를 우클릭하고 `New Group`을 선택하여 `Config`라는 이름의 그룹(폴더)을 만듭니다. 2. 새로 만든 `Config` 그룹을 우클릭하고 `New File...`을 선택합니다. 3. `Configuration Settings File` 템플릿을 검색하여 선택하고 `Next`를 클릭합니다. 4. 파일 이름을 `Dev.xcconfig`로 지정하고 생성합니다. 5. 같은 방법으로 `Prod.xcconfig` 파일도 생성합니다.

이제 각 `.xcconfig` 파일에 Flavor별 변수를 정의합니다.

`Config/Dev.xcconfig` 파일 내용:


// 앱 이름
APP_NAME = My App Dev

// 번들 ID 접미사
BUNDLE_ID_SUFFIX = .dev

// 앱 아이콘 에셋 카탈로그 이름
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon-Dev

// 이 부분은 CocoaPods를 사용할 때 필수입니다.
// Flutter가 생성한 기본 설정 파일을 포함합니다.
#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig"

`Config/Prod.xcconfig` 파일 내용:


// 앱 이름
APP_NAME = My App

// 번들 ID 접미사 (운영 버전은 없음)
BUNDLE_ID_SUFFIX =

// 앱 아이콘 에셋 카탈로그 이름
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon

// 이 부분은 CocoaPods를 사용할 때 필수입니다.
#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig"

이제 이 `.xcconfig` 파일들을 우리가 앞에서 만든 Build Configuration에 연결해야 합니다.

1. 다시 `PROJECT` -> `Runner` -> `Info` 탭의 `Configurations` 섹션으로 돌아갑니다. 2. 각 Configuration 옆의 `None`이라고 표시된 부분을 클릭하여 방금 만든 `.xcconfig` 파일을 지정해줍니다. * `Debug-dev`: `Dev.xcconfig` * `Release-dev`: `Dev.xcconfig` * `Debug-prod`: `Prod.xcconfig` * `Release-prod`: `Prod.xcconfig`

3.3. Xcode Scheme 생성 및 설정

Scheme은 어떤 타겟을, 어떤 Build Configuration으로 빌드, 실행, 테스트, 아카이브할지를 정의하는 하나의 '실행 계획'입니다. 우리는 `dev`와 `prod` Flavor를 위한 별도의 Scheme을 만들 것입니다.

1. Xcode 상단 메뉴에서 `Product` > `Scheme` > `Manage Schemes...`를 선택합니다. 2. 기존의 `Runner` Scheme을 선택하고, 하단의 톱니바퀴 아이콘을 클릭한 뒤 `Duplicate`를 선택합니다. 3. 새로 복제된 `Runner copy`의 이름을 `dev`로 변경합니다. 4. 같은 방법으로 `Runner`를 한번 더 복제하여 `prod` Scheme을 만듭니다. (기존 `Runner`의 이름을 `prod`로 변경해도 됩니다.) 5. 이제 각 Scheme을 편집하여 올바른 Build Configuration을 사용하도록 설정합니다. * `dev` Scheme을 선택하고 `Edit...` 버튼을 클릭합니다. * 왼쪽 목록에서 각 액션을 선택하고, 오른쪽의 `Build Configuration`을 우리가 만든 dev용 Configuration으로 변경합니다. * `Run`: `Debug-dev` * `Test`: `Debug-dev` * `Profile`: `Release-dev` * `Analyze`: `Debug-dev` * `Archive`: `Release-dev` * `Close`를 눌러 저장합니다. * `prod` Scheme에 대해서도 동일한 과정을 반복하여, 모든 액션에 `prod`용 Configuration(`Debug-prod`, `Release-prod`)을 할당합니다.

3.4. Build Settings에서 값 연결하기

이제 `.xcconfig`에 정의한 변수들을 실제 프로젝트 설정에 연결할 차례입니다.

1. `TARGETS` 섹션의 `Runner`를 선택하고 `Build Settings` 탭으로 이동합니다. 2. 검색창에 `Product Bundle Identifier`를 검색합니다. 3. 값을 `$(PRODUCT_BUNDLE_IDENTIFIER)$(BUNDLE_ID_SUFFIX)`로 수정합니다. `$(...)` 구문은 다른 설정이나 변수 값을 참조하는 문법입니다. 이렇게 하면 기본 번들 ID 뒤에 `.xcconfig`에 정의된 `BUNDLE_ID_SUFFIX` 값이 붙게 됩니다. 4. 검색창에 `Product Name`을 검색하고, 값을 `$(APP_NAME)`으로 수정합니다. 5. 홈 화면에 표시되는 앱 이름은 `Info.plist` 파일의 `Bundle display name` 값을 따릅니다. 네비게이터에서 `Runner/Info.plist` 파일을 열고, `Bundle display name` 키의 값을 `$(APP_NAME)`으로 변경합니다.

3.5. Flavor별 앱 아이콘 설정 (iOS)

iOS에서도 Flavor별로 다른 아이콘을 설정할 수 있습니다.

1. 네비게이터에서 `Runner/Assets.xcassets`를 선택합니다. 2. `AppIcon`을 우클릭하고 `Duplicate`를 선택하여 복사본을 만듭니다. 이름을 `AppIcon-Dev`로 변경합니다. (이 이름은 `Dev.xcconfig`의 `ASSETCATALOG_COMPILER_APPICON_NAME` 값과 일치해야 합니다.) 3. `AppIcon-Dev`에 개발용 아이콘 이미지들을 채워 넣습니다. 4. 다시 `TARGETS` > `Runner` > `Build Settings` 탭으로 돌아갑니다. 5. `Asset Catalog App Icon Set Name`을 검색합니다. 6. 값을 `$(ASSETCATALOG_COMPILER_APPICON_NAME)`으로 변경합니다.

이제 빌드 시점에 활성화된 Scheme에 따라 `.xcconfig` 파일이 선택되고, 그 파일에 정의된 `ASSETCATALOG_COMPILER_APPICON_NAME` 변수 값에 따라 `AppIcon` 또는 `AppIcon-Dev` 에셋이 앱의 아이콘으로 사용됩니다.

여기까지 따라오셨다면, Android와 iOS 양쪽 플랫폼에서 Flavor를 위한 네이티브 설정이 모두 완료되었습니다. 하지만 아직 가장 중요한 단계가 남았습니다. 이 Flavor 정보를 Flutter의 Dart 코드에서 어떻게 인지하고 활용할 수 있을까요?


4. Dart 코드에서 Flavor 분기 처리하기

플랫폼 레벨에서 앱 이름이나 아이콘을 바꾸는 것도 중요하지만, Flavor의 진정한 힘은 Dart 코드 내에서 API 주소나 기능 플래그와 같은 로직을 분기 처리하는 데에서 나옵니다. 이를 위해 앱의 진입점(entrypoint)을 Flavor별로 분리하고, 환경 설정을 담은 객체를 앱 전체에 주입하는 전략을 사용할 것입니다.

4.1. 환경 설정 클래스 정의

`lib` 디렉토리 아래에 환경 설정을 관리할 파일을 만듭니다. 예를 들어, `lib/config/` 디렉토리를 만들고 그 안에 `app_config.dart` 파일을 생성합니다.


// lib/config/app_config.dart

// 모든 환경 설정 클래스가 구현해야 할 추상 클래스
abstract class AppConfig {
  String get baseUrl;
  String get appTitle;
  bool get featureXEnabled;
}

// 개발 환경용 설정
class DevConfig implements AppConfig {
  @override
  String get baseUrl => 'https://dev-api.myapp.com/v1/';

  @override
  String get appTitle => 'My App (Dev)';

  @override
  bool get featureXEnabled => true; // 개발 환경에서는 X 기능 활성화
}

// 운영 환경용 설정
class ProdConfig implements AppConfig {
  @override
  String get baseUrl => 'https://api.myapp.com/v1/';

  @override
  String get appTitle => 'My App';

  @override
  bool get featureXEnabled => false; // 운영 환경에서는 X 기능 비활성화
}

4.2. Flavor별 main 파일 생성

Flutter 앱은 `lib/main.dart` 파일의 `main()` 함수에서 시작합니다. 우리는 이 진입점을 Flavor별로 여러 개 만들 것입니다.

1. 기존 `lib/main.dart` 파일의 이름을 `lib/main_common.dart`로 변경합니다. 2. `main_common.dart`의 `main()` 함수가 `AppConfig` 객체를 인자로 받도록 수정합니다.


// lib/main_common.dart

import 'package:flutter/material.dart';
import 'package:my_app/config/app_config.dart';

// AppConfig를 인자로 받는 공통 main 함수
void mainCommon(AppConfig config) {
  // 여기서 config 객체를 사용하여 앱의 초기 설정을 할 수 있습니다.
  // 예를 들어, DI 컨테이너(GetIt, Provider 등)에 등록하여 앱 전역에서 사용할 수 있게 합니다.
  print('App is running with base URL: ${config.baseUrl}');
  
  runApp(MyApp(config: config));
}

class MyApp extends StatelessWidget {
  final AppConfig config;

  const MyApp({Key? key, required this.config}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: config.appTitle, // 설정 객체에서 앱 제목을 가져옴
      home: MyHomePage(title: config.appTitle),
    );
  }
}

// ... 이하 앱 코드

3. 이제 Flavor별 진입점 파일을 생성합니다.

`lib/main_dev.dart` 파일 생성:


// lib/main_dev.dart

import 'package:my_app/config/app_config.dart';
import 'package:my_app/main_common.dart';

void main() {
  // DevConfig 인스턴스를 생성하여 공통 main 함수에 전달
  mainCommon(DevConfig());
}

`lib/main_prod.dart` 파일 생성:


// lib/main_prod.dart

import 'package:my_app/config/app_config.dart';
import 'package:my_app/main_common.dart';

void main() {
  // ProdConfig 인스턴스를 생성하여 공통 main 함수에 전달
  mainCommon(ProdConfig());
}

이 구조를 통해 어떤 `main_*.dart` 파일을 실행하느냐에 따라 앱의 모든 동작을 제어할 수 있게 되었습니다. `mainCommon` 함수 내에서 `AppConfig` 객체를 Provider나 GetIt과 같은 상태 관리/DI 도구를 통해 등록하면, 앱 내 어느 위젯에서나 현재 Flavor에 맞는 설정값(`baseUrl` 등)에 안전하게 접근할 수 있습니다.


5. Flavor 실행 및 빌드하기: 터미널과 VS Code

이제 모든 설정이 끝났습니다. 마지막으로, 특정 Flavor를 지정하여 앱을 실행하고 빌드하는 방법을 알아봅니다.

5.1. 터미널(CLI)에서 실행/빌드

Flutter CLI는 `--flavor` 옵션과 `-t` (또는 `--target`) 옵션을 제공합니다.

  • --flavor [flavor_name]: 사용할 네이티브 Flavor를 지정합니다. (Android의 `productFlavor` 이름, iOS의 `Scheme` 이름)
  • -t [dart_file_path]: 앱의 진입점으로 사용할 Dart 파일 경로를 지정합니다.

개발(dev) Flavor로 앱 실행:


flutter run --flavor dev -t lib/main_dev.dart

운영(prod) Flavor로 앱 실행:


flutter run --flavor prod -t lib/main_prod.dart

운영(prod) Flavor로 Android 앱(APK) 빌드:


flutter build apk --flavor prod -t lib/main_prod.dart

운영(prod) Flavor로 iOS 앱(IPA) 빌드:


flutter build ipa --flavor prod -t lib/main_prod.dart

5.2. VS Code에서 실행 설정 (`launch.json`)

매번 터미널에 긴 명령어를 입력하는 것은 번거롭습니다. VS Code의 `launch.json` 파일을 설정하면 클릭 한 번으로 원하는 Flavor를 실행하고 디버깅할 수 있습니다.

1. VS Code에서 `Run and Debug` 탭(Ctrl+Shift+D)으로 이동합니다. 2. `create a launch.json file` 링크를 클릭하여 `.vscode/launch.json` 파일을 생성합니다. 3. 아래와 같이 `configurations` 배열에 각 Flavor에 대한 실행 설정을 추가합니다.

// .vscode/launch.json
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Flutter (dev)",
            "request": "launch",
            "type": "dart",
            "program": "lib/main_dev.dart",
            "args": [
                "--flavor",
                "dev"
            ]
        },
        {
            "name": "Flutter (prod)",
            "request": "launch",
            "type": "dart",
            "program": "lib/main_prod.dart",
            "args": [
                "--flavor",
                "prod"
            ]
        }
    ]
}

이제 `Run and Debug` 탭의 드롭다운 메뉴에서 `Flutter (dev)` 또는 `Flutter (prod)`를 선택하고 F5 키를 누르면, 해당 Flavor와 연결된 설정으로 앱이 실행되고 디버깅 세션이 시작됩니다. 이로써 개발 생산성이 비약적으로 향상됩니다.

결론: 안정적이고 확장 가능한 앱 개발의 초석

지금까지 Flutter Flavor를 사용하여 개발, 운영 환경을 완벽하게 분리하는 전 과정을 상세히 살펴보았습니다. Android의 `build.gradle`부터 iOS의 `Build Configurations`와 `Schemes`, 그리고 Dart 코드 레벨에서의 분기 처리와 IDE 연동까지, 다소 복잡해 보일 수 있는 과정이었지만 한 번 제대로 구축해두면 그 가치는 실로 엄청납니다.

Flavor는 단순히 편의를 위한 기능이 아닙니다. 이것은 수동 설정 변경으로 인한 실수를 방지하고, 여러 환경을 동시에 관리해야 하는 복잡성을 체계적으로 해결하며, 자동화된 빌드 및 배포 파이프라인의 기반을 마련하는, 프로페셔널한 앱 개발의 핵심적인 실천 방법(practice)입니다. 오늘 배운 내용을 여러분의 프로젝트에 바로 적용하여, 더욱 안정적이고 효율적인 개발 워크플로우를 경험해 보시기를 바랍니다.


0 개의 댓글:

Post a Comment