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 개발 되세요! 🚀

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


📚 참고 자료

코멘트

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다