Flutter 학습 로드맵 Phase 1 - 두 번째 글
flutter create로 생성된 프로젝트의 모든 폴더와 파일을 iOS 프로젝트와 비교하며 하나씩 뜯어본다
들어가며
Xcode에서 iOS 프로젝트를 만들면 .xcodeproj, AppDelegate.swift, Info.plist 등이 자동 생성된다.
Flutter도 마찬가지로 flutter create를 실행하면 정해진 구조의 프로젝트가 만들어진다.
이 글에서는 flutter create first_app으로 생성된 실제 프로젝트를 기준으로, 각 폴더와 파일의 역할을 iOS 프로젝트와 비교하며 설명한다.
전체 프로젝트 구조
first_app/ (핵심 구조만 발췌)
├── lib/ ← Dart 코드 (핵심 작업 공간)
│ └── main.dart
├── test/ ← 테스트 코드
│ └── widget_test.dart
├── ios/ ← iOS 네이티브 설정
├── android/ ← Android 네이티브 설정
├── web/ ← 웹 빌드 설정
├── macos/ ← macOS 데스크톱 설정
├── linux/ ← Linux 데스크톱 설정
├── windows/ ← Windows 데스크톱 설정
├── build/ ← 빌드 산출물 (gitignore 대상)
├── pubspec.yaml ← 프로젝트 설정 파일 (핵심)
├── pubspec.lock ← 의존성 잠금 파일
├── analysis_options.yaml ← 린트 규칙 설정
├── README.md
└── (기타: .gitignore, .metadata, .dart_tool/, .idea/ 등 자동 생성 파일)
iOS 프로젝트와 비교하면 이렇다:
| Flutter | iOS (Xcode) | 역할 |
|---|---|---|
lib/ |
Swift 소스 파일들 | 앱 코드 작성 |
test/ |
Tests/ 타겟 |
테스트 코드 |
pubspec.yaml |
Package.swift/Podfile + 앱 메타데이터 |
의존성 + Flutter 프로젝트 설정 |
pubspec.lock |
Package.resolved |
의존성 버전 잠금 |
ios/ |
Xcode 프로젝트 자체 | 네이티브 플랫폼 설정 |
build/ |
DerivedData/ |
빌드 산출물 |
analysis_options.yaml |
SwiftLint 설정 | 코드 스타일 규칙 |
1. lib/ — 핵심 작업 공간
Flutter 개발의 99%는 이 폴더 안에서 이루어진다.
iOS 프로젝트에서 Swift 파일들을 만들어 작업하듯이, Flutter에서는 lib/ 폴더 안에 Dart 파일을 만들어 작업한다.
main.dart — 앱의 진입점
iOS의 @main AppDelegate와 같은 역할이다.
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
main()함수가 앱의 시작점이다 (iOS의@main어트리뷰트와 동일한 역할)runApp()에 루트 위젯을 전달하면 앱이 시작된다- iOS에서
UIApplication.shared→AppDelegate→SceneDelegate→rootViewController순서로 앱이 시작되는 것과 비교하면, Flutter는main()→runApp()→ 루트 위젯으로 훨씬 단순하다
프로젝트가 커지면?
lib/ 안에 하위 폴더를 만들어 구조화한다. 일반적인 패턴:
lib/
├── main.dart ← 진입점
├── app.dart ← MaterialApp 위젯
├── screens/ ← 화면 단위 위젯
│ ├── home_screen.dart
│ └── settings_screen.dart
├── widgets/ ← 재사용 가능한 위젯
│ ├── custom_button.dart
│ └── user_card.dart
├── models/ ← 데이터 모델
│ └── user.dart
├── services/ ← API, DB 등 외부 연동
│ └── api_service.dart
└── utils/ ← 유틸리티 함수, 상수
└── constants.dart
iOS 프로젝트의 폴더 구조와 비교:
| Flutter | iOS 프로젝트 |
|---|---|
screens/ |
Views/ 또는 ViewControllers/ |
widgets/ |
재사용 커스텀 UIView/SwiftUI View |
models/ |
Models/ |
services/ |
Services/ 또는 Networking/ |
utils/ |
Utils/ 또는 Extensions/ |
포인트: Flutter는 폴더 구조에 대한 강제 규칙이 없다. Xcode처럼 빌드 타겟에 파일을 수동으로 추가하는 개념도 없다. lib/ 안에 .dart 파일을 만들면 별도 등록 없이 import로 바로 사용할 수 있다.
프로젝트가 더 커지면? — Feature-First 구조
위의 레이어 기반 구조는 입문용으로 좋지만, 앱이 커지면 기능(Feature) 단위로 나누는 것이 커뮤니티에서 더 권장되는 방식이다:
lib/
├── main.dart
├── app.dart
├── core/ ← 공통 유틸, 테마, 라우팅
│ ├── theme/
│ └── routing/
├── shared/ ← 공유 위젯, 모델
│ ├── widgets/
│ └── models/
└── features/ ← 기능별 모듈
├── auth/
│ ├── screens/
│ ├── widgets/
│ └── services/
└── home/
├── screens/
├── widgets/
└── services/
iOS의 TCA나 MVVM에서 Feature 모듈을 나누는 것과 같은 개념이다.
2. test/ — 테스트 코드
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:first_app/main.dart';
void main() {
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
await tester.pumpWidget(const MyApp());
expect(find.text('0'), findsOneWidget);
expect(find.text('1'), findsNothing);
await tester.tap(find.byIcon(Icons.add));
await tester.pump();
expect(find.text('0'), findsNothing);
expect(find.text('1'), findsOneWidget);
});
}
iOS의 XCTest와 비교:
| 구분 | Flutter | iOS (XCTest) |
|---|---|---|
| 테스트 프레임워크 | flutter_test |
XCTest |
| UI 테스트 도구 | WidgetTester |
XCUITest |
| 테스트 실행 | flutter test |
Cmd+U 또는 xcodebuild test |
| 위치 | test/ 폴더 |
Tests/ 타겟 |
Flutter 테스트는 세 가지 레벨이 있다:
- Unit Test: 단일 함수/클래스 테스트
- Widget Test: 위젯 렌더링 및 상호작용 테스트 (위 예시)
- Integration Test: 전체 앱 흐름 테스트
별도의 테스트 타겟을 만들 필요 없이, test/ 폴더에 파일을 넣으면 바로 테스트가 동작한다.
3. pubspec.yaml — 프로젝트의 심장
Flutter 프로젝트의 의존성 관리, Dart SDK 버전 제약, 에셋/폰트 설정, 프로젝트 메타데이터를 하나의 파일이 담당한다. iOS의 Package.swift(SPM) + Podfile(CocoaPods)의 의존성 관리 역할과 유사하다.
name: first_app
description: "A new Flutter project."
publish_to: 'none'
version: 1.0.0+1
environment:
sdk: ^3.11.5
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.8
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^6.0.0
flutter:
uses-material-design: true
각 섹션 설명
name / description / version
name: first_app # Dart 패키지 이름 (iOS의 Bundle Name과는 다른 개념)
description: "A new Flutter project."
version: 1.0.0+1 # 버전+빌드번호 (iOS의 CFBundleShortVersionString+CFBundleVersion)
environment
environment:
sdk: ^3.11.5 # Dart SDK 버전 범위: ≥3.11.5, <4.0.0 (캐럿은 호환 범위를 의미)
dependencies — 런타임 의존성
dependencies:
flutter:
sdk: flutter # Flutter SDK 자체
cupertino_icons: ^1.0.8 # iOS 스타일 아이콘 (pub.dev에서 가져옴)
iOS에서 SPM이나 CocoaPods로 라이브러리를 추가하는 것과 같다.
패키지는 pub.dev에서 검색하고, 여기에 추가한 뒤 flutter pub get을 실행하면 설치된다.
| 작업 | Flutter | iOS |
|---|---|---|
| 패키지 저장소 | pub.dev | SPM Registry / CocoaPods Specs |
| 패키지 추가 | pubspec.yaml에 추가 + flutter pub get |
Xcode에서 Add Package 또는 Podfile 수정 + pod install |
| 버전 표기 | ^1.0.8 (캐럿 = 호환 범위) |
.upToNextMajor(from: "1.0.8") |
| 잠금 파일 | pubspec.lock |
Package.resolved |
dev_dependencies — 개발 전용 의존성
dev_dependencies:
flutter_test:
sdk: flutter # 테스트 프레임워크
flutter_lints: ^6.0.0 # 린트 규칙 (SwiftLint와 유사)
프로덕션 빌드에는 포함되지 않는 개발 도구들이다.
flutter 섹션 — Flutter 전용 설정
flutter:
uses-material-design: true # Material 아이콘 폰트 포함
# assets: # 이미지, JSON 등 에셋 등록
# fonts: # 커스텀 폰트 등록
iOS에서 이미지를 Assets.xcassets에 추가하듯이, Flutter에서는 여기에 에셋 경로를 등록한다.
4. ios/ — iOS 네이티브 레이어
ios/
├── Runner/
│ ├── AppDelegate.swift ← iOS 앱 진입점
│ ├── SceneDelegate.swift ← Scene 관리
│ ├── Info.plist ← iOS 앱 설정
│ ├── Assets.xcassets/ ← 앱 아이콘 등
│ ├── Base.lproj/ ← LaunchScreen 스토리보드
│ ├── GeneratedPluginRegistrant.h/.m ← 플러그인 자동 등록 (수정 금지)
│ └── Runner-Bridging-Header.h ← Swift-ObjC 브릿지
├── Runner.xcodeproj/ ← Xcode 프로젝트
├── Runner.xcworkspace/ ← Xcode 워크스페이스
├── RunnerTests/ ← iOS 네이티브 테스트
└── Flutter/
├── Debug.xcconfig ← 디버그 빌드 설정
├── Release.xcconfig ← 릴리즈 빌드 설정
└── AppFrameworkInfo.plist ← Flutter 프레임워크 정보
iOS 개발자에게 친숙한 구조다. 실제로 Xcode에서 열어서 작업할 수 있다.
AppDelegate.swift
import Flutter
import UIKit
@main
@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) {
GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry)
}
}
일반적인 iOS AppDelegate와 비슷하지만, FlutterAppDelegate를 상속받고 FlutterImplicitEngineDelegate 프로토콜을 채택한다.FlutterImplicitEngineDelegate는 Flutter 엔진이 암시적으로 초기화될 때 호출되는 콜백을 제공하며, didInitializeImplicitFlutterEngine에서 플러그인 자동 등록을 처리한다.
언제 ios/ 폴더를 직접 수정하나?
대부분의 Flutter 개발에서는 ios/ 폴더를 건드릴 일이 거의 없다. 하지만 다음 경우에는 직접 수정해야 한다:
- 앱 아이콘 변경:
Assets.xcassets/AppIcon.appiconset/ - 권한 설정:
Info.plist에NSCameraUsageDescription등 추가 - 푸시 알림 / Background Modes: Capability 추가 (Xcode에서 직접)
- 딥링크 / Universal Links: Associated Domains 설정, URL Schemes 추가
- 서드파티 SDK 설정:
AppDelegate.swift에 초기화 코드 추가 - 빌드 설정 변경: 코드 서명, 배포 프로파일, minimum deployment target 등
- Platform Channel: Swift로 네이티브 코드를 직접 작성할 때
즉, Apple 플랫폼 고유 기능이나 네이티브 빌드 설정이 필요할 때 ios/ 폴더를 수정한다고 보면 된다.
5. android/ — Android 네이티브 레이어
android/
├── app/
│ ├── build.gradle.kts ← 앱 모듈 빌드 설정 (minSdk, targetSdk 등)
│ └── src/ ← Android 소스 (AndroidManifest.xml 등)
├── build.gradle.kts ← 프로젝트 레벨 빌드 설정
├── gradle/ ← Gradle 래퍼
├── gradle.properties ← Gradle 속성
└── settings.gradle.kts ← 모듈 설정
iOS 개발자 관점에서 핵심만 알면 된다:
build.gradle.kts→ iOS의 Xcode 빌드 설정과 유사AndroidManifest.xml→ iOS의Info.plist와 유사 (권한, 앱 메타데이터)minSdk→ iOS의 minimum deployment target
6. analysis_options.yaml — 코드 품질 관리
include: package:flutter_lints/flutter.yaml
linter:
rules:
# avoid_print: false
# prefer_single_quotes: true
iOS의 SwiftLint .swiftlint.yml과 동일한 역할이다.
flutter_lints패키지가 기본 린트 규칙을 제공한다- 규칙을 활성화/비활성화할 수 있다
flutter analyze명령으로 전체 프로젝트 린트 검사를 실행한다
7. 나머지 플랫폼 폴더
| 폴더 | 대상 플랫폼 | 빌드 시스템 |
|---|---|---|
web/ |
웹 브라우저 | 웹 부트스트랩 + JS/Wasm |
macos/ |
macOS 데스크톱 | Xcode |
linux/ |
Linux 데스크톱 | CMake |
windows/ |
Windows 데스크톱 | CMake |
Flutter의 강점은 하나의 lib/ 코드로 모든 플랫폼에 빌드할 수 있다는 것이다.
각 플랫폼 폴더는 해당 OS의 네이티브 설정만 담당한다.
필요 없는 플랫폼은 flutter create 시 --platforms 옵션으로 제외할 수 있다:
# iOS와 Android만 생성
flutter create --platforms=ios,android my_app
8. 빌드와 실행 명령어
iOS 개발자에게 익숙한 작업과 대응하는 Flutter 명령어:
| 작업 | iOS (Xcode) | Flutter CLI |
|---|---|---|
| 빌드 + 실행 | Cmd+R |
flutter run |
| 테스트 | Cmd+U |
flutter test |
| 정적 분석 | Build Warnings | flutter analyze |
| 패키지 설치 | Resolve Packages | flutter pub get |
| 클린 빌드 | Cmd+Shift+K |
flutter clean |
| 릴리즈 빌드 | Archive | flutter build ios --release |
| 디바이스 목록 | Window > Devices | flutter devices |
Hot Reload vs Hot Restart
Flutter의 가장 큰 장점 중 하나다:
- Hot Reload (
r): 코드 변경사항을 즉시 반영. 앱 상태(state)가 유지된다. Xcode의 Previews보다 빠르고, 실제 시뮬레이터에서 동작한다. - Hot Restart (
R): 앱을 완전히 재시작. 상태가 초기화된다. Xcode의Cmd+R과 유사하지만 훨씬 빠르다.
9. iOS 프로젝트 vs Flutter 프로젝트 구조 최종 비교
[iOS 프로젝트] [Flutter 프로젝트]
MyApp.xcodeproj pubspec.yaml
├── Sources/ ├── lib/
│ ├── AppDelegate.swift │ ├── main.dart
│ ├── Views/ │ ├── screens/
│ ├── Models/ │ ├── models/
│ └── Services/ │ └── services/
├── Tests/ ├── test/
├── Resources/ ├── assets/ (pubspec.yaml에 등록)
│ └── Assets.xcassets │
├── Info.plist ├── ios/Runner/Info.plist
├── Podfile (또는 Package.swift) ├── pubspec.yaml (dependencies)
├── .swiftlint.yml ├── analysis_options.yaml
└── DerivedData/ └── build/
마무리
Flutter 프로젝트 구조의 핵심을 정리하면:
lib/폴더가 전부다 — 개발 시간의 99%를 여기서 보낸다. 나머지 플랫폼 폴더는 특별한 네이티브 설정이 필요할 때만 건드린다.pubspec.yaml이 프로젝트의 심장이다 — 의존성, 에셋, 폰트, 메타데이터가 모두 이 파일 하나에 들어간다.- 플랫폼별 폴더는 "포장"이다 — 동일한 Dart 코드를 각 OS에 맞게 포장하는 역할이다.
- Dart 코드 관리는 Xcode보다 단순하다 — 네이티브 빌드 설정(타겟, 스키마, 워크스페이스)은
ios/,android/등에 여전히 존재하지만, 일상적인 Flutter 개발에서는 그 위에서 작업하므로 직접 다룰 일이 적다.
iOS 개발자가 가장 적응이 필요한 부분은, Xcode GUI 대신 pubspec.yaml 텍스트 파일로 프로젝트를 관리한다는 점이다. 하지만 익숙해지면 오히려 더 직관적이고 빠르다.
참고 자료
'Flutter' 카테고리의 다른 글
| [ Flutter ]Swift 개발자를 위한 Dart 핵심 문법 비교 (0) | 2026.04.22 |
|---|