[Flutter] flavor 를 이용한 개발, 운영 환경 설정

동동·2022년 5월 31일
5

모바일 앱 개발시 개발 환경과 운영 환경의 세팅을 일일이 수동으로 변경한다면 매우 번거로울것이다. 또한 실수로 운영 빌드시 개발 환경 세팅으로 앱이 배포된다면 정말 아찔한 경험을 할 수 있다.

Flutter 의 flavor 를 이용한다면 개발/운영 환경 설정을 깔끔하게 분리할 수 있다. 덤으로 한 디바이스에서 개발/운영 앱을 동시에 설치 할 수 있어 테스트 시에 앱을 지우고 재설치하는 번거로움을 줄일 수 있다.

Flavor 란?

사실 flavor 는 Android 에서 동일한 소스로 다른 버전의 앱을 빌드 할 수 있도록 해주는 개념이다.

  • 개발/운영 에 따른 접속 서버 url 구분 및 다양한 key, token 값 구분
  • 유료/무료 앱 설정
  • 광고 유무 설정

위와 같은 값들을 하드코딩 하는게 아닌 build 시 설정에 따라 적용하게 된다.

iOS 의 경우 build scheme 을 이용해서 구현할 수 있다. Flutter 의 경우 구글의 플랫폼이다 보니 좀 더 Android 의 향기가 짙게 나는게 사실이다.

Android Native 설정

build.gradle 설정

android/app/src/build.gradle 에서 android.productFlavorsandroid.flavorDimensions 에서 설정할 수 있다.

  • flavorDimensions : 빌드의 구분. 기본적으로 하나 이상의 dimension 이 존재해야 함.
  • applicationId : 각 flavor 를 유일하게 만들어주는 id 값. 이 값을 설정 해주면 동일한 디바이스에 동시에 앱을 설치 할 수 있다.
android {
    // ... all existing things like `sourceSets`, ...

    flavorDimensions "app"

    productFlavors {
        dev {
            dimension "app"
            applicationId "com.example.flavor_test.dev"
            resValue "string", "app_name", "DEV flavor test"
        }
        product {
            dimension "app"
            applicationId "com.example.flavor_test"
            resValue "string", "app_name", "flavor test"
        }
    }
}

Android Manifest 설정

flavor 에 맞게 app 이름을 변경 될 수 있도록 label 값을 변경하자

android:label="@string/app_name"

MainActivity 변경

build.gradle 에 의해 설정된 값을 method channels 를 통해 Flutter 에서 사용할 수 있도록 전달 하자.

package com.example.flavor_test

import androidx.annotation.NonNull;
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugins.GeneratedPluginRegistrant

class MainActivity: FlutterActivity() {
    override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
        GeneratedPluginRegistrant.registerWith(flutterEngine);

        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "flavor").setMethodCallHandler {
            call, result -> result.success(BuildConfig.FLAVOR)
        }
    }
}

iOS Native 설정

custom build Configurations 생성

iOS 의 설정은 xCode 에서 하는것이 편하다.
iOS workspace 를 xCode 에서 오픈하고 아래와 같은 설정을 해주자

  1. 좌측 Navigation tab 에서 Runner Project 선택
  2. 메인 창에서 Runner PRJECT 선택 (TARGETS 아님)
  3. info 탭 선택
  4. Configurations 영역에서 Debug, Release, Profie 을 복사 후 이름 변경.
    ex) Debug-dev, Release-dev, Profile-dev, Debug-product, Release-product, Profile-product

custom build schemes 설정

이제 위에서 설정한 configuration 을 build scheme 에 연결 시켜 보자

  1. main toolbar 에서 Product > Scheme > Manage Schemes 를 선택
  2. Manage Schemes 창에서 Runner 를 선택후 복사하자. 하단에 + 버튼으로 할 수 있고, + 버튼 옆에 더보기 버튼 클릭시 Duplicate 가 존재한다.
  3. 복사한 scheme 은 dev 라는 이름으로 변경하고 Edit 버튼을 눌러 Edit 창에서 각 세션 (Run, Text, Profile, Analyze, Archive) 에 선택된 build configuration 이 dev 임을 확인하자.
  4. 똑같은 방법으로 scheme 을 하나 더 만들어서 product 라고 이름을 변경하고, Edit 창의 각 세션이 이번엔 -product 임을 확인 하자

이제 scheme 설정이 끝났다. 설정이 잘 마무리 되었다면, xCode 상단에 아래와 같이 선택이 가능할 것이다. 사실 여기서 run 버튼 (플레이 화살표) 을 통해서도 해당 scheme 으로 실행이 가능하다. 하지만 CLI 형태로 설정한다면 CI/CD 설정을 간편히 할 수 있어 CLI 를 사용하는것을 추천한다.

Build Setting 에 설정 추가

이제 각 빌드 scheme 별로 다른 설정 값을 추가 해주자

  1. Identifier 수정
    Android 에 applicationId 을 변경해주었다면, iOS 에서는 Identifier 값을 수정해주면 된다. build Setting 창에서 identifier 라고 검색하면 Product Bundle identifier 내용이 나올 것이다. 이 내용을 dev, product 환경에 맞게 변경 해주자
    (dev 빌드시 identifier 뒤에 .dev 를 붙여 줄 것이다.)

  2. app name 추가
    이제 각 빌드마다 앱 이름을 다르게 설정하기 위해 App_Name 값을 추가 해 준다.
    좌측 상단에 + 버튼을 눌러 Add User-defined setting 을 선택하면 사용자가 custom 하게 설정을 추가 할 수 있다.
    APP_NAME 이라고 추가하고 각 빌드 scheme 별 이름을 설정하자.
    이제 info.plist 에 Bundle display name 을 $(APP_NAME) 로 변경 시켜주면 된다.

  1. app flavor 추가
    flavor 의 경우 Android 에서의 개념이고 iOS 는 build scheme 이라고 했다. 그런데 왜 flavor 값을 추가 해 주어야 할까?
    아래에서 설명하겠지만 Flutter 소스에서 Method Channel 을 이용해서 native 의 build 값을 flavor 로 가지고 올 수 있도록 할 계획이다. 이 때를 위해 각 빌드 scheme 에 맞는 flavor 값을 추가 설정 해주어야 한다.
    APP_NAME 을 추가했던거 처럼 User-defined setting 을 추가해서 APP_FLAVOR 을 추가하자. 그리고 각 scheme 에 맞게 flavor 값을 설정 해 주자

AppDelegate.swift 수정

Android 마찬가지로 build 설정을 flutter 에 전달하기 위해서 AppDelefate.swift 에서 Method channel 을 이용하여 flavor 를 전달하자

import UIKit
import Flutter

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
      GeneratedPluginRegistrant.register(with: self)

          let controller = window.rootViewController as! FlutterViewController

          let flavorChannel = FlutterMethodChannel(
              name: "flavor",
              binaryMessenger: controller.binaryMessenger)

          flavorChannel.setMethodCallHandler({(call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
              // Note: this method is invoked on the UI thread
              let flavor = Bundle.main.infoDictionary?["App-Flavor"]
              result(flavor)
          })

          return super.application(application, didFinishLaunchingWithOptions: launchOptions)
      
  }
}

Flutter

flavor 에 따른 설정 세팅

flutter code 에서는 flavor 에 따른 설정을 가지고 오는 부분이 구현 되어야 한다.

main.dart 에서 실행시 아래와 같이 flavor 값을 전달 받은 다음 설정을 해주자

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  // App flavor 값 조회
  // flavor = 'dev' | 'product'
  String? flavor = await const MethodChannel('flavor')
      .invokeMethod<String>('getFlavor');
  Config(flavor);
  
  runApp(MyApp());
}
class Config{
  final String baseUrl;
  final String token;

  Config._dev():
      baseUrl = '',		// dev url
      token = '';		// dev token

  Config._product():
        baseUrl = '',	// product url
        token = '';		// product token

  factory Config (String? _flavor) {
    if (_flavor == 'dev'){
      instance = Config._dev();
     (_flavor == 'product'){
      instance = Config._product();
    }else {
      throw Exception("Unknown flaver : $_flavor}");
    }

    return instance;
  }

  static late final Config instance;
}

실행

위에서 잠깐 말했듯이 Android Studio 나 xCode 에서 flavor 를 선택후 실행을 할 수 있다. CLI 를 이용한다면 CI/CD 를 활용할 수 있고, 무엇보다 터미널 창에서 command 로 실행하는것이 개발자로서 간지(?) 가 난다. 쉽다

flutter run --flavor [flavor 명]

위 명령어를 실행하면 아래와 같이 다른 이름의 앱이 설치 되는것을 볼 수 있다. flavor 에 따라 앱 아이콘도 변경 가능하지만 이는 이번 글에서 소개하지 않겠다

참고

0개의 댓글