이전 글 개발환경 분리는, 사실 이글 보여주려고..
모바일 앱 개발 할 때, 개발환경과 운영환경을 사람이 하나하나 조작한다면, 매번, 해당 환경 변경되는 부분이 아는 사람이 있어야 합니다. 게다가, 실수로 잘못 배포라도 하게 되면... 쉽지 않은 경험을 할 수도 있습니다.
모바일 앱 개발을 하면서, 개발환경 / 운영환경 을 다르게 설정하여 앱을 다르게 제공해줘야할 때가 있습니다.
이런 경우, 매번 해당 서비스에 맞춰서 새롭게 빌드해서 앱을 건내주게 되면, 실수할 우려가 있을 뿐만 아니라, 어느 부분이 환경변경되어야 하는지 매번 암기하고 있어야 합니다.
이런 불편한 작업들을, 편리하게 해주는 도구가 Flutter 에서는 Flavor 라는 도구를 사용하여 해결합니다.
예를 들어 기존에는 아래와 같이 관리하고 있었다고 가정합시다.
const BAES_URL = "api.prod.com"
// const BAES_URL = "api.dev.com"
// const BAES_URL = "api.test.com"
main() {
final repository = Repository(baseURL: BASE_URL);
runApp(MyApp(repository));
...
}
BASE_URL
이란 값을 주입해서 사용하곤 했습니다.BASE_URL
을 안바꾸면, AppStore 에 잘못 올리게되고, 참사가 일어날 수도 있습니다...그래서 애초에 빌드 환경을 나눠버리면, 주석을 잘못해서 실수하는 일은 없겠죠.
iOS 의 경우, Scheme 이라는 단위로 해당 설정을 컨트롤하고, 각각 다른 Config 파일을 지정하곤 했습니다. Flutter 에서는 그 Config 를 기반으로 하되,
개발환경을 분리하는 방법은 각 프레임워크 혹은 IDE 별로 제공하는 방법이 있습니다.
Flutter & Android 의 경우, "Flvaor" 를 사용하곤 합니다.
정리하면, Xcode 의 분리 방식을 Flavor 에 추가하여, iOS & Android 의 개발 환경을 동시에 관리한다고 보면 되겠습니다.
Flavors helps us to create builds for different instances of our app. For example, we can create a flavor for development, a flavor for production and another flavor for a demo of the app. In this way we can create different flavors, and thus have different instances of our apps before publishing it on the App Store and Google Play.
Flavors 를 통해서 다른 환경의 앱 인스턴스를 편리하게 빌드할 수 있습니다.
만약 개발환경을 "prodction / test / dev" 3 단계로 구분을 했다면,
커멘드 라인 입력을 통해 손쉽게 구분해서 바로 빌드 할 수 있습니다.
flutter run --flavor {셋팅한 개발환경 이름}
ex)
flutter build --flavor development
추가로 관련된 상태값들도 일괄적으로 주입해주면, 정적 상태값들도 관리가 쉬워집니다.
현재FlutterProject폴더/android/app/src/build.gradle
android {
...(기타 설정 내용들)...
// 모든 flvaor 는 반드시 이름이 정해진 아래와 같은 "Dimension" 이 있어야합니다
// 이 이름이 각각의 flavor 를 구분하는 ID 가 됩니다.
flavorDimensions "app"
// dev 와 product 로 환경을 구분했습니다.
productFlavors {
dev {
dimensions "app"
applicationId "com.example.flavor_example"
resValue "string", "app_name", "DEV falvor example"
}
product {
dimensions "app"
applicationId "com.example.flavor_example"
resValue "string", "app_name", "falvor example"
}
}
}
flavorDimensions
는 "빌드" 의 구분의 기준을 정의한다고 생각하시면 됩니다.applicationId
는 각각의 flavor 의 ID 입니다. 이 설정을 해주면, 동일 디바이스에 다른 앱 ID 로 함께 설치될 수 있습니다.경로는 다음과 같습니다.
android/app/src/main/AndroidManifest.xml
resValue "string", "app_name", "flavor example"
을 이곳에서 사용합니다.<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.flavor_example">
<application
android:label="@string/app_name"
...
</application>
</manifest>
경로
android/app/src/main/kotlin/프로젝트명/MainActivity.kt
package com.example.flavor_example
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)
}
}
}
build.gradle
에서 설정한 값을 사용하기 위함입니다.Flutter 폴더에서, iOS 를 우측클릭하여, Flutter > "open xcode" 를 통해 Xcode 를 켭니다.
(그냥 폴더에 가셔서 직접 .proj 파일을 실행해도 됩니다.)
최상단에 있는 "Runner" 파일을 누르고 프로젝트 클릭 후, "Configurations" 를 세팅합니다.
저는 Debug, Release, Profile 을 각각 Duplicate 해서 구성했습니다.
위 이미지와 같이, "Edit Scheme" 을 클릭해서 설정합니다.
이후 "Duplicate Scheme" 을 눌러서, 현재 있는 Scheme 을 복제합니다.
우측에 "Build, "Run", "Test", "Profile", "Analyze", "Archive" 가 있는데, 해당 Scheme 의 이름에 맞게 dev 인지 product 인지 설정합니다.
그러면 Xcode 의 탭바영역의 Scheme 설정 화면에 다음과 같이 표시됩니다.
저는 dev 환경에서는 뒤에 .dev
를 추가했습니다.
Bundle display name
은 앱에 보이는 이름입니다. 환경에 따라서 .dev
가 붙을지 말지가 결정됩니다. 해당 값은 변수 값이므로, 변수로 설정해줘야 합니다.
위 Info.plist
에서 사용한 값을 정의해주어야 하는데, 다시 이전에 갔던 곳으로 이동합니다.
경로
Runner/Target/Build Settings
위 경로로 이동한 이후, User-Defind 을 2 개 추가합니다.
각각의 이름은 "APP_FLAVOR" 와 "APP_NAME" 입니다.
추가한 모습입니다.
flavor 는 Android 에서 사용하는 개념입니다. iOS 의 경우 scheme 을 통해서 환경을 분리하고 관리합니다. 그러므로, Flutter 에서 iOS 를 지정하기 위해서는 XCode 에서 설정한 값을 flavor 에서 알 수 있도록 해야합니다. 그래야 Flutter Project 에서 설정한 값이 iOS 도 컨트롤이 가능하니까요.
이렇게 iOS 에서 설정한 값을 Dart 로 가져오는 방법이 "Platform Channel" 입니다. 그 중 "MethodChannel" 을 사용할 예정입니다.
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)
}
}
FlutterMethodChannel
이란 부분이 Swift 코드와 dart 코드가 커뮤니케이션 하도록 도와주는 객체 입니다. MainActivity.kt
에서 처리해준 것도 동일한 로직입니다.위 설명과 위 코드의 예시 및 자세한 설명이 궁금하시면 아래 공식문서를 참고하시면 됩니다.
(참고로, MethodChannel 은 비동기이지만, MainThread 에서만 동작합니다.)
공식문서 링크