본문으로 바로가기
기술

Flutter 크로스플랫폼 앱 개발 입문 — 하나의 코드로 모든 플랫폼을

강민수· 모바일 개발 테크 에디터
||12분 읽기
#Flutter#Dart#크로스플랫폼#앱개발#모바일#iOS#Android#위젯#상태관리#Riverpod

Flutter란 무엇인가

크로스플랫폼의 새로운 패러다임

Flutter는 Google이 2018년 정식 출시한 오픈소스 UI 프레임워크입니다. 하나의 Dart 코드베이스로 iOS, Android, Web, Windows, macOS, Linux 총 6개 플랫폼의 앱을 빌드할 수 있습니다.

2025년 기준 주요 지표:

  • GitHub 스타: 165,000+
  • 전 세계 개발자: 300만+ (Google 발표)
  • Google Play 상위 1,000개 앱 중 Flutter 사용: 약 30%
  • 국내 채용 공고에서 Flutter 언급 비율: 2021년 대비 3배 이상 증가

Flutter의 핵심 차별점

기존 크로스플랫폼 프레임워크(React Native, Xamarin 등)와의 결정적 차이는 렌더링 방식입니다.

React Native: JavaScript → Bridge → 네이티브 위젯. 네이티브 UI 컴포넌트를 사용하므로 플랫폼 간 미세한 차이 존재

Flutter: Dart → Skia/Impeller 엔진 → 직접 GPU 렌더링. OS의 네이티브 위젯을 사용하지 않고 자체 렌더링 엔진으로 모든 픽셀을 직접 그림

이로 인한 장점:

  • 완벽한 UI 일관성: iOS와 Android에서 동일한 외관
  • 60/120fps 성능: 자체 렌더링으로 브릿지 오버헤드 없음
  • 디자인 자유도: OS 위젯에 구속받지 않는 커스텀 UI 가능

---

Dart 언어의 특징

왜 Dart인가

Flutter가 JavaScript나 Kotlin이 아닌 Dart를 선택한 이유:

  1. AOT(Ahead-of-Time) 컴파일: 릴리스 빌드에서 네이티브 ARM 코드로 컴파일 → 빠른 시작 속도
  2. JIT(Just-in-Time) 컴파일: 개발 중 Hot Reload 지원 → 코드 변경 즉시 반영 (1초 미만)
  3. Null Safety: Dart 2.12부터 사운드 널 안전성 도입. 컴파일 타임에 NPE 방지
  4. 단일 스레드 + Isolate: 메인 스레드 블로킹 없는 비동기 프로그래밍 모델

Dart 핵심 문법 (5분 속성)

```dart // 변수 선언 final name = '얼웨이즈'; // 불변 (런타임 상수) const pi = 3.14159; // 불변 (컴파일타임 상수) var count = 0; // 가변

// Null Safety String? nullable; // null 가능 String nonNull = 'hello'; // null 불가

// 클래스 class Pet { final String name; final int age;

const Pet({required this.name, required this.age});

String get description => '$name, $age세'; }

// 비동기 Future<String> fetchData() async { final response = await http.get(Uri.parse('https://api.example.com')); return response.body; } ```

---

위젯 시스템 — "Everything is a Widget"

위젯 트리의 이해

Flutter에서 화면의 모든 요소는 위젯(Widget)입니다. 텍스트, 버튼, 패딩, 레이아웃, 심지어 앱 자체도 위젯입니다. 위젯은 트리 구조로 조합되어 UI를 구성합니다.

위젯의 두 가지 유형:

  • StatelessWidget: 불변 상태. 한 번 빌드되면 변경 불가. 정적 UI에 적합
  • StatefulWidget: 가변 상태. `setState()`로 상태 변경 시 UI 재빌드

핵심 레이아웃 위젯

| 위젯 | 역할 | HTML/CSS 대응 | |------|------|-------------| | Container | 박스 모델 (패딩, 마진, 배경) | `<div>` + CSS box | | Row | 수평 배치 | `display: flex; direction: row` | | Column | 수직 배치 | `display: flex; direction: column` | | Stack | 겹치기 배치 | `position: absolute` | | ListView | 스크롤 목록 | 스크롤 컨테이너 | | Expanded | 남은 공간 채우기 | `flex: 1` | | SizedBox | 고정 크기 / 간격 | `width/height` |

Material 3 디자인

Flutter 3.x부터 Material 3 (Material You)가 기본 디자인 시스템입니다. `ColorScheme.fromSeed()`로 씨드 색상 하나만 지정하면 조화로운 색상 팔레트가 자동 생성됩니다.

```dart MaterialApp( theme: ThemeData( colorSchemeSeed: Colors.blue, useMaterial3: true, ), ); ```

Flutter 아키텍처 계층도 — Dart Framework, Engine(Skia/Impeller), Platform별 임베더
Flutter 아키텍처 계층도 — Dart Framework, Engine(Skia/Impeller), Platform별 임베더

---

상태 관리 — 가장 중요한 아키텍처 결정

상태 관리가 왜 중요한가

앱의 규모가 커질수록 "어떤 데이터가, 어디에 저장되고, 어떻게 변경되며, 누가 관찰하는가"가 복잡해집니다. Flutter 커뮤니티에서 가장 활발히 논의되는 주제이며, 여러 접근법이 있습니다.

주요 상태 관리 솔루션 비교

Provider: Flutter 팀이 추천하는 기본 솔루션. InheritedWidget의 래퍼. 단순한 앱에 적합

Riverpod: Provider의 창시자(Remi Rousselet)가 만든 차세대 솔루션. Provider의 한계(BuildContext 의존, 런타임 에러) 해결. 컴파일 타임 안전성, 테스트 용이성이 강점. 2025년 기준 가장 빠르게 성장하는 솔루션

Bloc/Cubit: 이벤트 기반 상태 관리. 대규모 팀 프로젝트에서 일관된 패턴을 강제. 보일러플레이트가 많지만 예측 가능성이 높음

GetX: 최소 보일러플레이트. 빠른 프로토타이핑에 유리하나, 마법 같은 동작이 많아 디버깅과 테스트에 불리. 프로덕션에서는 논란

Riverpod 기본 예시

```dart // Provider 선언 final counterProvider = StateNotifierProvider<CounterNotifier, int>((ref) { return CounterNotifier(); });

class CounterNotifier extends StateNotifier<int> { CounterNotifier() : super(0); void increment() => state++; }

// Widget에서 사용 class CounterPage extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final count = ref.watch(counterProvider); return Text('Count: $count'); } } ```

---

프로젝트 구조 — 확장 가능한 아키텍처

Feature-First 구조 (권장)

``` lib/ ├── app/ # 앱 진입점, 라우팅 ├── core/ # 공통 유틸, 상수, 테마 │ ├── theme/ │ ├── utils/ │ └── constants/ ├── features/ # 기능별 모듈 │ ├── auth/ │ │ ├── data/ # Repository, API │ │ ├── domain/ # Model, Entity │ │ └── presentation/ # Screen, Widget │ ├── home/ │ └── profile/ ├── shared/ # 공유 위젯, 서비스 │ ├── widgets/ │ └── services/ └── main.dart ```

이 구조의 장점:

  • 기능 추가/삭제가 독립적
  • 팀원 간 충돌 최소화
  • 테스트 경계가 명확

---

성능 최적화 핵심 전략

1. const 생성자 적극 활용

`const` 위젯은 빌드 시점에 인스턴스가 재사용되어 불필요한 리빌드를 방지합니다. Flutter 린트 규칙(`prefer_const_constructors`)을 활성화하세요.

2. 리빌드 범위 최소화

`setState()`를 최상위 위젯에서 호출하면 전체 서브트리가 리빌드됩니다. 상태가 변하는 부분만 별도 위젯으로 분리하여 리빌드 범위를 줄이세요.

3. ListView.builder 사용

`ListView(children: [...])`는 모든 아이템을 한 번에 생성하지만, `ListView.builder`는 보이는 아이템만 지연 생성(lazy rendering)합니다. 100개 이상의 목록에서는 성능 차이가 확연합니다.

4. 이미지 최적화

  • `cached_network_image` 패키지로 네트워크 이미지 캐싱
  • 적절한 `width`/`height` 지정으로 불필요한 디코딩 방지
  • WebP 포맷 사용으로 파일 크기 30~50% 절감

5. Impeller 렌더링 엔진

Flutter 3.16+부터 iOS에서 Impeller가 기본 렌더링 엔진. Skia 대비 셰이더 컴파일 지연(jank) 문제를 근본적으로 해결. Android에서도 점진적으로 기본 엔진이 되고 있습니다.

Flutter 성능 최적화 체크리스트 — const 위젯, 리빌드 범위, 지연 로딩, 이미지 캐싱
Flutter 성능 최적화 체크리스트 — const 위젯, 리빌드 범위, 지연 로딩, 이미지 캐싱

---

Flutter vs React Native vs 네이티브 — 선택 가이드

| 기준 | Flutter | React Native | 네이티브(Swift/Kotlin) | |------|---------|-------------|---------------------| | 학습 곡선 | 중간 (Dart 학습 필요) | 낮음 (JS/TS 활용) | 높음 (2개 언어) | | 성능 | 네이티브에 근접 | 약간 낮음 (Bridge) | 최고 | | UI 일관성 | 최고 (자체 렌더링) | 플랫폼 의존 | 플랫폼 최적화 | | 생태계 | 빠르게 성장 | 성숙 | 가장 풍부 | | Hot Reload | 매우 빠름 | 빠름 | Xcode만 일부 지원 | | 팀 규모 | 1개 팀 | 1개 팀 | 최소 2개 팀 | | 적합한 경우 | 커스텀 UI 앱 | 웹 개발자의 모바일 전환 | 하드웨어 밀착 앱 |

Flutter를 선택해야 하는 경우

  • 디자인 일관성이 중요한 브랜드 앱
  • 빠른 MVP 출시 + 이후 멀티 플랫폼 확장
  • 소규모 팀(1~5명)으로 iOS/Android 동시 개발
  • 게임, 애니메이션, 커스텀 UI가 많은 앱

네이티브를 선택해야 하는 경우

  • AR/VR, 카메라, 블루투스 등 하드웨어 깊은 통합 필요
  • 플랫폼별 최적 UX가 핵심인 앱 (예: iOS 감성)
  • 전담 iOS/Android 팀이 이미 있는 대규모 조직
Flutter는 "80/20 법칙"의 앱 개발 버전입니다. 전체 앱의 80%에 해당하는 유스케이스를 하나의 코드베이스로 효율적으로 해결합니다. 나머지 20% — 극단적 성능이나 깊은 네이티브 통합이 필요한 경우 — 에서는 플랫폼 채널(Platform Channel)로 네이티브 코드를 호출할 수 있어, 필요할 때만 네이티브로 내려가는 유연한 접근이 가능합니다.

강민수

모바일 개발 테크 에디터

얼웨이즈 블로그에서 유용한 정보와 인사이트를 공유합니다.