[태그:] 프로젝트셋업

  • 2. [Flutter]앱 개발, 첫 삽 뜨기 전 필수 확인 사항!

    Flutter 프로젝트 시작 전 필수 체크리스트 📋

    실무에서 바로 쓰는 Flutter 프로젝트 초기 설정 가이드
    더 이상 프로젝트 중간에 “아, 이걸 처음에 설정했어야 했는데…” 하지 마세요!


    안녕하세요! 모바일 개발의 새로운 패러다임인 Flutter로 첫 프로젝트를 시작하려고 하시나요?

    Android/iOS 네이티브 개발을 해보신 분들이나 웹 프론트엔드에서 모바일로 확장을 고려하고 계신 분들에게 “프로젝트 시작 전에 이것만은 꼭 확인하자!”는 실전 가이드를 준비했습니다.

    제가 여러 Flutter 프로젝트를 진행하면서 겪었던 시행착오를 바탕으로, 프로젝트 초기에 놓치기 쉬운 핵심 설정들을 정리해봤습니다. 이 글을 따라하시면 나중에 “아, 처음에 이걸 설정했어야 했는데…”라는 후회를 하지 않으실 거예요! 😊

    🎯 왜 초기 설정이 중요할까?

    Flutter 프로젝트에서 초기 설정을 제대로 하지 않으면:

    • 🔄 중간에 패키지 의존성 충돌로 며칠을 삽질하게 됩니다
    • 📱 플랫폼별 설정 누락으로 배포 직전에 당황하게 됩니다
    • 🔐 권한 설정 실수로 앱스토어 리젝을 당하게 됩니다
    • ⚡ 성능 최적화를 나중에 하려고 하면 리팩토링 지옥에 빠집니다

    특히 안드로이드나 iOS에서 넘어오신 분들은 “Flutter는 크로스 플랫폼이니까 설정이 간단하겠지?”라고 생각하시는데, 오히려 두 플랫폼을 모두 고려해야 하기 때문에 더 신경 써야 할 부분이 많습니다.


    📋 체크리스트 개요

    이 글에서 다룰 핵심 체크 포인트들입니다:

    1. Android 설정 – Manifest와 Gradle 설정
    2. iOS 설정 – info.plist와 Xcode 프로젝트 설정
    3. pubspec.yaml 완벽 설정 – 의존성부터 리소스까지
    4. 필수 패키지 미리 세팅 – 나중에 후회하지 않을 패키지들
    5. 권한 및 보안 설정 – 앱스토어 승인을 위한 필수 사항들

    각 단계별로 왜 필요한지, 어떻게 설정하는지, 주의사항은 무엇인지 상세히 알아보겠습니다.


    1️⃣ Android 설정: 탄탄한 기반 다지기

    📱 Android Manifest 설정

    Android Manifest는 앱의 신분증 같은 역할을 합니다. android/app/src/main/AndroidManifest.xml 파일에서 핵심 설정들을 확인해봅시다.

    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.example.your_app">
    
        <!-- 인터넷 권한 (API 통신 필수) -->
        <uses-permission android:name="android.permission.INTERNET" />
    
        <!-- 카메라 권한 (카메라 기능 사용 시) -->
        <uses-permission android:name="android.permission.CAMERA" />
    
        <!-- 저장소 권한 (파일 업로드/다운로드 시) -->
        <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
        <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    
        <!-- 위치 권한 (지도 기능 사용 시) -->
        <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
        <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    
        <application
            android:label="Your App Name"  <!-- 앱 이름 -->
            android:name="${applicationName}"
            android:icon="@mipmap/ic_launcher"  <!-- 앱 아이콘 -->
            android:usesCleartextTraffic="true"> <!-- HTTP 통신 허용 (개발 시에만) -->
    
            <activity
                android:name=".MainActivity"
                android:exported="true"
                android:launchMode="singleTop"
                android:theme="@style/LaunchTheme"
                android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
                android:hardwareAccelerated="true"
                android:windowSoftInputMode="adjustResize">
    
                <!-- 딥링크 설정 -->
                <intent-filter android:autoVerify="true">
                    <action android:name="android.intent.action.VIEW" />
                    <category android:name="android.intent.category.DEFAULT" />
                    <category android:name="android.intent.category.BROWSABLE" />
                    <data android:scheme="https"
                          android:host="yourapp.com" />
                </intent-filter>
    
                <meta-data
                  android:name="io.flutter.embedding.android.NormalTheme"
                  android:resource="@style/NormalTheme" />
            </activity>
    
            <!-- Firebase 설정 (푸시 알림 사용 시) -->
            <service
                android:name=".java.MyFirebaseMessagingService"
                android:exported="false">
                <intent-filter>
                    <action android:name="com.google.firebase.MESSAGING_EVENT" />
                </intent-filter>
            </service>
        </application>
    </manifest>

    💡 Pro Tip: 권한은 처음부터 다 추가하지 마세요! 필요한 기능이 생길 때마다 추가하는 것이 좋습니다. Google Play Store에서는 불필요한 권한을 요청하는 앱을 좋지 않게 봅니다.

    🔧 build.gradle 설정

    android/app/build.gradle 파일은 앱의 빌드 설정을 담당합니다.

    def localProperties = new Properties()
    def localPropertiesFile = rootProject.file('local.properties')
    if (localPropertiesFile.exists()) {
        localPropertiesFile.withReader('UTF-8') { reader ->
            localProperties.load(reader)
        }
    }
    
    def flutterRoot = localProperties.getProperty('flutter.sdk')
    if (flutterRoot == null) {
        throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
    }
    
    def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
    if (flutterVersionCode == null) {
        flutterVersionCode = '1'
    }
    
    def flutterVersionName = localProperties.getProperty('flutter.versionName')
    if (flutterVersionName == null) {
        flutterVersionName = '1.0'
    }
    
    apply plugin: 'com.android.application'
    apply plugin: 'kotlin-android'
    apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
    
    // Firebase 사용 시 추가
    apply plugin: 'com.google.gms.google-services'
    
    android {
        compileSdkVersion 34  // 최신 SDK 버전 사용
        ndkVersion flutter.ndkVersion
    
        compileOptions {
            sourceCompatibility JavaVersion.VERSION_1_8
            targetCompatibility JavaVersion.VERSION_1_8
        }
    
        kotlinOptions {
            jvmTarget = '1.8'
        }
    
        sourceSets {
            main.java.srcDirs += 'src/main/kotlin'
        }
    
        defaultConfig {
            applicationId "com.example.your_app"  // 고유한 패키지명
            minSdkVersion 21  // Android 5.0 이상 지원
            targetSdkVersion 34  // 최신 타겟 SDK
            versionCode flutterVersionCode.toInteger()
            versionName flutterVersionName
    
            // MultiDex 활성화 (패키지가 많을 때 필요)
            multiDexEnabled true
    
            // 프로가드 설정
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    
        signingConfigs {
            release {
                // 릴리즈 빌드를 위한 키스토어 설정
                keyAlias localProperties.getProperty('keyAlias')
                keyPassword localProperties.getProperty('keyPassword')
                storeFile localProperties.getProperty('storeFile') ? file(localProperties.getProperty('storeFile')) : null
                storePassword localProperties.getProperty('storePassword')
            }
        }
    
        buildTypes {
            release {
                signingConfig signingConfigs.release
                minifyEnabled true  // 코드 난독화 활성화
                shrinkResources true  // 불필요한 리소스 제거
                proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
            }
            debug {
                applicationIdSuffix ".debug"  // 디버그 버전 구분
                debuggable true
            }
        }
    }
    
    flutter {
        source '../..'
    }
    
    dependencies {
        implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    
        // Firebase 사용 시
        implementation platform('com.google.firebase:firebase-bom:32.7.0')
        implementation 'com.google.firebase:firebase-analytics'
        implementation 'com.google.firebase:firebase-crashlytics'
    
        // MultiDex 지원
        implementation 'com.android.support:multidex:1.0.3'
    }

    ⚠️ 주의사항:

    • minSdkVersion은 21 이상으로 설정하세요 (Android 5.0). 더 낮으면 최신 Flutter 기능들을 사용할 수 없습니다.
    • compileSdkVersiontargetSdkVersion은 최신 버전을 사용하세요.
    • 키스토어 정보는 절대 코드에 하드코딩하지 마세요!

    🔑 키스토어 설정 (배포 준비)

    배포를 위해서는 앱 서명용 키스토어가 필요합니다.

    # 키스토어 생성
    keytool -genkey -v -keystore ~/upload-keystore.jks -keyalg RSA -keysize 2048 -validity 10000 -alias upload
    
    # android/key.properties 파일 생성
    storePassword=your_store_password
    keyPassword=your_key_password
    keyAlias=upload
    storeFile=/Users/your_username/upload-keystore.jks

    2️⃣ iOS 설정: 애플 생태계 정복하기

    🍎 info.plist 설정

    iOS 설정의 핵심인 ios/Runner/Info.plist 파일을 살펴봅시다.

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
    <plist version="1.0">
    <dict>
        <!-- 앱 기본 정보 -->
        <key>CFBundleDevelopmentRegion</key>
        <string>$(DEVELOPMENT_LANGUAGE)</string>
    
        <key>CFBundleDisplayName</key>
        <string>Your App Name</string>  <!-- 홈 화면에 표시될 앱 이름 -->
    
        <key>CFBundleExecutable</key>
        <string>$(EXECUTABLE_NAME)</string>
    
        <key>CFBundleIdentifier</key>
        <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
    
        <key>CFBundleInfoDictionaryVersion</key>
        <string>6.0</string>
    
        <key>CFBundleName</key>
        <string>your_app</string>
    
        <key>CFBundlePackageType</key>
        <string>APPL</string>
    
        <key>CFBundleShortVersionString</key>
        <string>$(FLUTTER_BUILD_NAME)</string>
    
        <key>CFBundleSignature</key>
        <string>????</string>
    
        <key>CFBundleVersion</key>
        <string>$(FLUTTER_BUILD_NUMBER)</string>
    
        <!-- 최소 iOS 버전 -->
        <key>LSMinimumSystemVersion</key>
        <string>12.0</string>
    
        <!-- UI 설정 -->
        <key>LSRequiresIPhoneOS</key>
        <true/>
    
        <key>UILaunchStoryboardName</key>
        <string>LaunchScreen</string>
    
        <key>UIMainStoryboardFile</key>
        <string>Main</string>
    
        <!-- 지원 기기 방향 -->
        <key>UISupportedInterfaceOrientations</key>
        <array>
            <string>UIInterfaceOrientationPortrait</string>
            <string>UIInterfaceOrientationLandscapeLeft</string>
            <string>UIInterfaceOrientationLandscapeRight</string>
        </array>
    
        <key>UISupportedInterfaceOrientations~ipad</key>
        <array>
            <string>UIInterfaceOrientationPortrait</string>
            <string>UIInterfaceOrientationPortraitUpsideDown</string>
            <string>UIInterfaceOrientationLandscapeLeft</string>
            <string>UIInterfaceOrientationLandscapeRight</string>
        </array>
    
        <!-- 권한 설명 메시지 (필수!) -->
        <key>NSCameraUsageDescription</key>
        <string>이 앱은 사진 촬영을 위해 카메라에 접근합니다.</string>
    
        <key>NSPhotoLibraryUsageDescription</key>
        <string>이 앱은 사진 저장을 위해 사진 라이브러리에 접근합니다.</string>
    
        <key>NSLocationWhenInUseUsageDescription</key>
        <string>이 앱은 위치 기반 서비스 제공을 위해 위치 정보를 사용합니다.</string>
    
        <key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
        <string>이 앱은 백그라운드에서도 위치 기반 서비스를 제공하기 위해 위치 정보를 사용합니다.</string>
    
        <key>NSMicrophoneUsageDescription</key>
        <string>이 앱은 음성 녹음을 위해 마이크에 접근합니다.</string>
    
        <!-- App Transport Security -->
        <key>NSAppTransportSecurity</key>
        <dict>
            <key>NSAllowsArbitraryLoads</key>
            <false/>  <!-- 프로덕션에서는 false로 설정 -->
            <key>NSExceptionDomains</key>
            <dict>
                <key>your-api-domain.com</key>
                <dict>
                    <key>NSExceptionAllowsInsecureHTTPLoads</key>
                    <true/>
                    <key>NSExceptionMinimumTLSVersion</key>
                    <string>TLSv1.0</string>
                </dict>
            </dict>
        </dict>
    
        <!-- Deep Link 설정 -->
        <key>CFBundleURLTypes</key>
        <array>
            <dict>
                <key>CFBundleURLName</key>
                <string>your-app-scheme</string>
                <key>CFBundleURLSchemes</key>
                <array>
                    <string>yourapp</string>
                </array>
            </dict>
        </array>
    
        <!-- App Tracking Transparency (iOS 14.5+) -->
        <key>NSUserTrackingUsageDescription</key>
        <string>이 앱은 더 나은 광고 경험을 제공하기 위해 추적 권한을 요청합니다.</string>
    </dict>
    </plist>

    🚨 중요: iOS에서는 권한 사용 이유를 반드시 명시해야 합니다. 애매한 설명은 앱스토어 리뷰에서 리젝당할 수 있습니다!

    🔨 Xcode 프로젝트 설정

    Xcode에서 직접 설정해야 할 항목들:

    1. Bundle Identifier: 고유한 식별자 설정
    2. Deployment Target: 최소 iOS 버전 (12.0 이상 권장)
    3. Signing & Capabilities: 개발자 계정 연결
    4. App Transport Security: HTTPS 보안 설정
    5. Background Modes: 백그라운드 실행 권한
    # Xcode에서 프로젝트 열기
    open ios/Runner.xcworkspace

    💡 Pro Tip: Xcode에서 설정하는 내용들은 자동으로 관련 파일들에 반영됩니다. 가능하면 Xcode GUI를 통해 설정하세요.


    3️⃣ pubspec.yaml: Flutter 프로젝트의 심장

    📦 기본 설정

    pubspec.yaml은 Flutter 프로젝트의 모든 의존성과 설정을 관리하는 핵심 파일입니다.

    name: your_app
    description: A new Flutter project.
    publish_to: 'none' # 패키지로 배포하지 않음
    
    # 버전 정보
    version: 1.0.0+1
    
    # 환경 설정
    environment:
      sdk: '>=3.0.0 <4.0.0'
      flutter: ">=3.10.0"
    
    # 의존성
    dependencies:
      flutter:
        sdk: flutter
    
      # UI 컴포넌트
      cupertino_icons: ^1.0.6  # iOS 스타일 아이콘
    
      # 상태 관리 (선택: riverpod OR bloc OR provider)
      flutter_riverpod: ^2.4.9
      # flutter_bloc: ^8.1.3
      # provider: ^6.1.1
    
      # 네트워킹
      dio: ^5.4.0  # HTTP 클라이언트 (http보다 기능이 풍부)
      retrofit: ^4.0.3  # REST API 클라이언트 생성
    
      # 로컬 저장소
      shared_preferences: ^2.2.2  # 간단한 키-값 저장
      hive: ^2.2.3  # NoSQL 로컬 데이터베이스
      hive_flutter: ^1.1.0
    
      # 유틸리티
      get_it: ^7.6.7  # 의존성 주입
      injectable: ^2.3.2  # 코드 생성 기반 DI
      auto_route: ^7.9.2  # 자동 라우팅 생성
    
      # JSON 직렬화
      json_annotation: ^4.8.1
      freezed_annotation: ^2.4.1
    
      # 환경 변수
      flutter_dotenv: ^5.1.0
    
      # 네이티브 기능
      permission_handler: ^11.2.0  # 권한 관리
      device_info_plus: ^9.1.1  # 디바이스 정보
      package_info_plus: ^4.2.0  # 앱 정보
      url_launcher: ^6.2.2  # 외부 링크 열기
    
      # 이미지 & 미디어
      cached_network_image: ^3.3.0  # 이미지 캐싱
      image_picker: ^1.0.5  # 이미지 선택
      photo_view: ^0.14.0  # 이미지 확대/축소
      video_player: ^2.8.1  # 비디오 재생
    
      # Firebase (필요한 것만 선택)
      firebase_core: ^2.24.2
      firebase_auth: ^4.15.3
      cloud_firestore: ^4.13.6
      firebase_storage: ^11.5.6
      firebase_messaging: ^14.7.10
      firebase_analytics: ^10.7.4
      firebase_crashlytics: ^3.4.8
    
      # 소셜 로그인
      google_sign_in: ^6.1.6
      sign_in_with_apple: ^5.0.0
      # flutter_facebook_auth: ^6.0.3
    
      # UI 라이브러리
      flutter_svg: ^2.0.9  # SVG 이미지
      lottie: ^2.7.0  # Lottie 애니메이션
      shimmer: ^3.0.0  # 로딩 효과
      flutter_staggered_grid_view: ^0.7.0  # 그리드 뷰
    
      # 유틸리티 위젯
      gap: ^3.0.1  # 간격 위젯
      flutter_screenutil: ^5.9.0  # 반응형 UI
    
      # 기타
      intl: ^0.19.0  # 국제화
      equatable: ^2.0.5  # 값 비교 유틸리티
    
    # 개발 의존성
    dev_dependencies:
      flutter_test:
        sdk: flutter
    
      # 린팅
      flutter_lints: ^3.0.1
      very_good_analysis: ^5.1.0  # 더 엄격한 린트 규칙
    
      # 코드 생성
      build_runner: ^2.4.7
      freezed: ^2.4.6
      json_serializable: ^6.7.1
      retrofit_generator: ^8.0.4
      injectable_generator: ^2.4.1
      auto_route_generator: ^7.3.2
      hive_generator: ^2.0.1
    
      # 테스팅
      mockito: ^5.4.4
      integration_test:
        sdk: flutter
    
    # Flutter 설정
    flutter:
      uses-material-design: true
    
      # 리소스 파일
      assets:
        - assets/images/
        - assets/icons/
        - assets/animations/
        - .env
        - .env.dev
        - .env.prod
    
      # 폰트
      fonts:
        - family: Pretendard
          fonts:
            - asset: assets/fonts/Pretendard-Regular.ttf
              weight: 400
            - asset: assets/fonts/Pretendard-Medium.ttf
              weight: 500
            - asset: assets/fonts/Pretendard-SemiBold.ttf
              weight: 600
            - asset: assets/fonts/Pretendard-Bold.ttf
              weight: 700

    🎨 리소스 관리 전략

    프로젝트에서 사용할 리소스들을 체계적으로 관리하세요:

    assets/
    ├── images/
    │   ├── logos/
    │   ├── icons/
    │   ├── illustrations/
    │   └── backgrounds/
    ├── animations/
    │   └── loading.json
    ├── fonts/
    │   └── Pretendard/
    └── configs/
        ├── .env
        ├── .env.dev
        └── .env.prod

    환경 변수 관리 (.env 파일):

    # .env.dev
    API_BASE_URL=https://dev-api.yourapp.com
    GOOGLE_MAPS_API_KEY=your_dev_api_key
    FIREBASE_PROJECT_ID=your-dev-project
    
    # .env.prod
    API_BASE_URL=https://api.yourapp.com
    GOOGLE_MAPS_API_KEY=your_prod_api_key
    FIREBASE_PROJECT_ID=your-prod-project

    4️⃣ 필수 패키지 미리 세팅: 미래의 나를 위한 투자

    🎯 패키지 선택 가이드

    프로젝트 유형별로 필요한 패키지들을 정리해봤습니다:

    📱 모든 프로젝트 공통 (필수)

    # 상태 관리
    flutter_riverpod: ^2.4.9  # 또는 bloc, provider 중 선택
    
    # 네트워킹
    dio: ^5.4.0
    
    # 로컬 저장소
    shared_preferences: ^2.2.2
    
    # 권한 관리
    permission_handler: ^11.2.0
    
    # 환경 변수
    flutter_dotenv: ^5.1.0
    
    # JSON 처리
    json_annotation: ^4.8.1
    freezed_annotation: ^2.4.1

    🛒 E-커머스/소셜 앱

    # 인증
    firebase_auth: ^4.15.3
    google_sign_in: ^6.1.6
    sign_in_with_apple: ^5.0.0
    
    # 이미지 처리
    cached_network_image: ^3.3.0
    image_picker: ^1.0.5
    
    # 푸시 알림
    firebase_messaging: ^14.7.10
    
    # 결제
    # in_app_purchase: ^3.1.11

    📍 위치 기반 서비스

    # 위치
    geolocator: ^10.1.0
    geocoding: ^2.1.1
    
    # 지도
    google_maps_flutter: ^2.5.0

    🎵 미디어 앱

    # 미디어
    video_player: ^2.8.1
    audio_players: ^5.2.1
    camera: ^0.10.5+5
    
    # 파일 처리
    file_picker: ^6.1.1
    path_provider: ^2.1.2

    📚 패키지 관리 팁

    1. 버전 호환성 확인: flutter pub deps 명령어로 의존성 트리 확인
    2. 정기적인 업데이트: flutter pub upgrade 하되 메이저 업데이트는 신중히
    3. 불필요한 패키지 제거: 사용하지 않는 패키지는 과감히 제거
    4. 라이선스 확인: 상업적 사용 가능한 라이선스인지 확인
    # 패키지 추가
    flutter pub add package_name
    
    # 개발 의존성 추가  
    flutter pub add --dev package_name
    
    # 특정 버전 지정
    flutter pub add package_name:^1.0.0
    
    # 의존성 확인
    flutter pub deps
    
    # 오래된 패키지 확인
    flutter pub outdated

    5️⃣ 권한 및 보안 설정: 앱스토어 승인의 지름길

    🔐 iOS App Tracking Transparency (iOS 14.5+)

    iOS 14.5부터는 사용자 추적을 위한 명시적 권한 요청이 필요합니다:

    import 'package:app_tracking_transparency/app_tracking_transparency.dart';
    import 'package:permission_handler/permission_handler.dart';
    
    class AppInitializer {
      static Future<void> initialize() async {
        WidgetsFlutterBinding.ensureInitialized();
    
        // App Tracking Transparency 권한 요청
        if (Platform.isIOS) {
          await _requestTrackingPermission();
        }
    
        // 알림 권한 요청
        await _requestNotificationPermission();
    
        // 기타 초기화 작업...
      }
    
      static Future<void> _requestTrackingPermission() async {
        final status = await AppTrackingTransparency.trackingAuthorizationStatus;
    
        if (status == TrackingStatus.notDetermined) {
          // 커스텀 다이얼로그 표시 (선택사항)
          await _showTrackingDialog();
    
          // 시스템 권한 요청
          await AppTrackingTransparency.requestTrackingAuthorization();
        }
      }
    
      static Future<void> _showTrackingDialog() async {
        // 사용자에게 추적 권한의 필요성을 설명하는 커스텀 다이얼로그
        return showDialog(
          context: navigatorKey.currentContext!,
          builder: (context) => AlertDialog(
            title: Text('개인화된 광고'),
            content: Text(
              '더 나은 앱 경험을 위해 다른 회사의 앱과 웹사이트에서 회원님의 활동을 추적하도록 허용하시겠습니까?'
            ),
            actions: [
              TextButton(
                onPressed: () => Navigator.pop(context),
                child: Text('계속'),
              ),
            ],
          ),
        );
      }
    
      static Future<void> _requestNotificationPermission() async {
        final status = await Permission.notification.status;
    
        if (status.isDenied) {
          await Permission.notification.request();
        }
      }
    }
    
    // main.dart
    void main() async {
      await AppInitializer.initialize();
      runApp(MyApp());
    }

    🛡️ 네트워크 보안 설정

    Android Network Security Config

    android/app/src/main/res/xml/network_security_config.xml:

    <?xml version="1.0" encoding="utf-8"?>
    <network-security-config>
        <domain-config cleartextTrafficPermitted="false">
            <domain includeSubdomains="true">your-api-domain.com</domain>
            <pin-set expiration="2025-12-31">
                <pin digest="SHA-256">AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</pin>
                <pin digest="SHA-256">BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=</pin>
            </pin-set>
        </domain-config>
    
        <!-- 개발 환경에서만 허용 -->
        <domain-config cleartextTrafficPermitted="true">
            <domain includeSubdomains="true">localhost</domain>
            <domain includeSubdomains="true">10.0.2.2</domain>
        </domain-config>
    </network-security-config>

    iOS App Transport Security

    개발 단계에서는 임시로 HTTP를 허용하되, 프로덕션에서는 반드시 HTTPS만 사용:

    <!-- 개발 시 임시 설정 -->
    <key>NSAppTransportSecurity</key>
    <dict>
        <key>NSAllowsArbitraryLoads</key>
        <true/>
    </dict>
    
    <!-- 프로덕션 권장 설정 -->
    <key>NSAppTransportSecurity</key>
    <dict>
        <key>NSAllowsArbitraryLoads</key>
        <false/>
        <key>NSExceptionDomains</key>
        <dict>
            <key>your-api-domain.com</key>
            <dict>
                <key>NSExceptionRequiresForwardSecrecy</key>
                <false/>
                <key>NSExceptionMinimumTLSVersion</key>
                <string>TLSv1.2</string>
            </dict>
        </dict>
    </dict>

    🔑 API 키 보안 관리

    절대 API 키를 코드에 하드코딩하지 마세요!

    // ❌ 잘못된 방법
    const String apiKey = "AIzaSyB123456789";
    
    // ✅ 올바른 방법
    class ApiConfig {
      static String get googleMapsApiKey {
        return dotenv.env['GOOGLE_MAPS_API_KEY'] ?? '';
      }
    
      static String get firebaseApiKey {
        return dotenv.env['FIREBASE_API_KEY'] ?? '';
      }
    }
    
    // 환경별 설정 로드
    Future<void> loadEnvConfig() async {
      const flavor = String.fromEnvironment('FLAVOR', defaultValue: 'dev');
    
      switch (flavor) {
        case 'prod':
          await dotenv.load(fileName: '.env.prod');
          break;
        case 'staging':
          await dotenv.load(fileName: '.env.staging');
          break;
        default:
          await dotenv.load(fileName: '.env.dev');
      }
    }

    6️⃣ 프로젝트 구조 & 아키텍처 설정

    📁 추천 폴더 구조

    확장 가능하고 유지보수하기 쉬운 폴더 구조를 설정하세요:

    lib/
    ├── main.dart
    ├── app/
    │   ├── app.dart                 # 앱 진입점
    │   ├── router/                  # 라우팅 설정
    │   ├── theme/                   # 테마 설정
    │   └── constants/               # 앱 상수
    ├── core/
    │   ├── error/                   # 에러 처리
    │   ├── network/                 # 네트워크 설정
    │   ├── utils/                   # 유틸리티 함수
    │   └── di/                      # 의존성 주입
    ├── features/
    │   ├── auth/
    │   │   ├── data/               # 데이터 레이어
    │   │   ├── domain/             # 비즈니스 로직
    │   │   └── presentation/       # UI 레이어
    │   ├── home/
    │   └── profile/
    ├── shared/
    │   ├── widgets/                # 공통 위젯
    │   ├── models/                 # 공통 모델
    │   └── services/               # 공통 서비스
    └── generated/                  # 자동 생성 파일

    🏗️ Clean Architecture 기본 설정

    // core/di/injection.dart
    @InjectableInit()
    Future<void> configureDependencies() async => getIt.init();
    
    final getIt = GetIt.instance;
    
    // main.dart
    void main() async {
      WidgetsFlutterBinding.ensureInitialized();
    
      // 환경 설정 로드
      await loadEnvConfig();
    
      // 의존성 주입 설정
      await configureDependencies();
    
      // Hive 초기화
      await Hive.initFlutter();
    
      // Firebase 초기화
      if (Firebase.apps.isEmpty) {
        await Firebase.initializeApp(
          options: DefaultFirebaseOptions.currentPlatform,
        );
      }
    
      runApp(
        ProviderScope(
          child: MyApp(),
        ),
      );
    }

    7️⃣ 디버깅 & 개발 도구 설정

    🔧 개발 환경 최적화

    // core/utils/logger.dart
    import 'package:logger/logger.dart';
    
    class AppLogger {
      static final _logger = Logger(
        printer: PrettyPrinter(
          methodCount: 2,
          errorMethodCount: 8,
          lineLength: 120,
          colors: true,
          printEmojis: true,
          printTime: true,
        ),
      );
    
      static void d(String message) => _logger.d(message);
      static void i(String message) => _logger.i(message);
      static void w(String message) => _logger.w(message);
      static void e(String message, [dynamic error, StackTrace? stackTrace]) {
        _logger.e(message, error, stackTrace);
      }
    }
    
    // Firebase Crashlytics 연동
    class CrashReporter {
      static Future<void> recordError(
        dynamic exception,
        StackTrace? stack, {
        bool fatal = false,
      }) async {
        await FirebaseCrashlytics.instance.recordError(
          exception,
          stack,
          fatal: fatal,
        );
      }
    
      static Future<void> log(String message) async {
        await FirebaseCrashlytics.instance.log(message);
      }
    }

    📊 앱 성능 모니터링

    // core/services/performance_service.dart
    class PerformanceService {
      static Future<void> startTrace(String traceName) async {
        final trace = FirebasePerformance.instance.newTrace(traceName);
        await trace.start();
      }
    
      static Future<void> stopTrace(String traceName) async {
        final trace = FirebasePerformance.instance.newTrace(traceName);
        await trace.stop();
      }
    
      static Future<void> recordNetworkRequest(
        String url,
        String method,
        int responseCode,
        int responseSize,
      ) async {
        final metric = FirebasePerformance.instance.newHttpMetric(url, HttpMethod.Get);
        await metric.start();
    
        metric.responseCode = responseCode;
        metric.responsePayloadSize = responseSize;
    
        await metric.stop();
      }
    }

    8️⃣ 배포 준비: 한 번에 성공하는 배포

    🚀 배포 전 체크리스트

    ## Android 배포 체크리스트
    
    - [ ] 키스토어 생성 및 설정 완료
    - [ ] 프로가드 규칙 설정
    - [ ] 앱 서명 확인
    - [ ] 권한 최소화
    - [ ] APK/AAB 크기 최적화
    - [ ] 64비트 아키텍처 지원 확인
    
    ## iOS 배포 체크리스트
    
    - [ ] Apple Developer 계정 등록
    - [ ] Bundle ID 등록
    - [ ] 인증서 및 프로비저닝 프로파일 생성
    - [ ] App Store Connect 앱 등록
    - [ ] 권한 사용 이유 명시
    - [ ] ATS (App Transport Security) 설정
    
    ## 공통 체크리스트
    
    - [ ] 앱 아이콘 설정 (모든 사이즈)
    - [ ] 스플래시 스크린 설정
    - [ ] 앱 이름 및 설명 다국어 지원
    - [ ] 개인정보 보호정책 URL 설정
    - [ ] 테스트 디바이스에서 최종 확인
    - [ ] 성능 최적화 확인
    - [ ] 크래시 없는지 확인

    🔧 빌드 최적화 설정

    // 릴리즈 빌드 최적화
    flutter build apk --release --shrink --obfuscate --split-debug-info=build/debug-info
    
    flutter build ios --release --obfuscate --split-debug-info=build/debug-info

    🎉 마무리: 성공적인 Flutter 프로젝트의 시작

    축하합니다! 🎊 이제 여러분은 Flutter 프로젝트를 시작하기 전에 확인해야 할 모든 핵심 사항들을 숙지하셨습니다.

    📋 최종 점검 요약

    1. Android 설정: Manifest, Gradle, 키스토어
    2. iOS 설정: info.plist, Xcode 프로젝트, 인증서
    3. pubspec.yaml: 의존성, 리소스, 환경 설정
    4. 필수 패키지: 프로젝트 유형별 패키지 선택
    5. 권한 및 보안: 플랫폼별 권한, API 키 관리
    6. 프로젝트 구조: Clean Architecture 기반 폴더 구조
    7. 개발 도구: 디버깅, 로깅, 성능 모니터링
    8. 배포 준비: 플랫폼별 배포 체크리스트

    🚀 다음 단계

    이제 본격적인 개발에 집중할 수 있습니다! 다음 글에서는:

    • Dart 언어 핵심 개념 마스터하기
    • Flutter 위젯 시스템 완전 정복
    • 상태 관리 패턴 비교 및 선택 가이드
    • 실전 API 연동 및 에러 처리

    등의 주제로 더 깊이 있는 내용을 다룰 예정입니다.

    💬 커뮤니티와 함께 성장하기

    Flutter 개발 여정에서 막히는 부분이 있다면:

    혼자서는 어려운 길이지만, 함께라면 더 즐겁고 빠르게 성장할 수 있습니다!


    즐거운 Flutter 개발 되세요! 🚀

    이 글이 도움이 되셨다면 좋아요 👍와 공유 📤 부탁드립니다!


    📚 참고 자료