Flutter 모바일 앱 개발 시작하기
Flutter 모바일 앱 개발 시작하기
Flutter를 선택하는 이유
모바일 앱 개발을 시작할 때 가장 먼저 부딪히는 문제는 플랫폼 선택입니다. iOS만 지원할지, Android만 지원할지, 아니면 둘 다 지원할지를 결정해야 합니다. 두 플랫폼을 각각 네이티브로 개발하면 코드베이스가 두 배가 되고, 유지보수 비용도 두 배가 됩니다. 독립 개발자에게 이것은 현실적으로 감당하기 어렵습니다.
Flutter는 이 문제를 해결합니다. 하나의 Dart 코드베이스로 iOS, Android, 웹, 데스크톱 앱을 모두 만들 수 있습니다. Google이 개발하고 유지보수하며, 활발한 커뮤니티와 풍부한 패키지 생태계를 갖추고 있습니다.
저도 PDF 뷰어 앱과 플래시카드 앱을 Flutter로 개발하고 있습니다. 하나의 코드베이스로 양 플랫폼을 동시에 지원할 수 있다는 것은 1인 개발자에게 엄청난 이점입니다.
개발 환경 설정
Flutter 개발을 시작하기 위해 필요한 것들을 순서대로 설치합니다.
1. Flutter SDK 설치
공식 사이트(flutter.dev)에서 운영체제에 맞는 SDK를 다운로드합니다. 설치 후 환경 변수 PATH에 Flutter의 bin 디렉토리를 추가합니다.
2. 개발 도구 설치
Android Studio(Android 개발)와 Xcode(iOS 개발, Mac 전용)를 설치합니다. 에디터는 VS Code를 추천합니다. Flutter와 Dart 확장을 설치하면 훌륭한 개발 환경이 됩니다.
3. 환경 진단
터미널에서 flutter doctor를 실행하면 개발 환경에 문제가 없는지 자동으로 진단해줍니다.
flutter doctor
이 명령어가 출력하는 항목들을 하나씩 해결하면 됩니다. 모든 항목에 체크 표시가 나올 때까지 설정을 완료하세요.
Dart 언어 기초
Flutter는 Dart 언어를 사용합니다. Dart는 Java나 JavaScript와 문법이 유사하므로 이 언어들을 경험해본 분이라면 빠르게 적응할 수 있습니다.
// 변수 선언
String name = 'Bitnara';
int age = 30;
var isActive = true; // 타입 추론
final createdAt = DateTime.now(); // 한 번만 할당
const pi = 3.14159; // 컴파일 타임 상수
// 함수
String greet(String name) {
return '안녕하세요, $name님!';
}
// 클래스
class User {
final String name;
final String email;
User({required this.name, required this.email});
String get displayName => '$name ($email)';
}
Dart의 핵심적인 특징 몇 가지를 알아두면 좋습니다. Null Safety가 기본 적용되어 String?처럼 ?를 붙여야 null 값을 허용합니다. 비동기 처리는 async/await를 사용하며 JavaScript와 거의 동일합니다. 컬렉션은 List, Map, Set을 기본으로 제공합니다.
위젯 트리: Flutter의 핵심 개념
Flutter에서 화면에 표시되는 모든 것은 위젯(Widget)입니다. 텍스트, 버튼, 패딩, 정렬, 심지어 앱 전체도 위젯입니다. 위젯은 트리 구조로 조합되며, 이 트리를 위젯 트리라고 합니다.
MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('내 첫 앱')),
body: Center(
child: Text('안녕하세요!'),
),
),
)
이 코드에서 MaterialApp이 최상위 위젯이고, 그 안에 Scaffold, AppBar, Center, Text가 차례로 중첩됩니다. Flutter의 UI 개발은 이런 위젯들을 조합하는 과정입니다.
StatelessWidget vs StatefulWidget
Flutter 위젯은 크게 두 종류로 나뉩니다.
StatelessWidget은 상태가 없는 위젯입니다. 한 번 생성되면 변하지 않습니다. 정적인 텍스트, 아이콘, 이미지 표시 등에 사용합니다.
class GreetingCard extends StatelessWidget {
final String name;
const GreetingCard({super.key, required this.name});
@override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Text('안녕하세요, $name님!'),
),
);
}
}
StatefulWidget은 상태가 변하는 위젯입니다. 버튼 클릭, 텍스트 입력, 타이머 등 사용자 상호작용이나 시간에 따라 UI가 변해야 할 때 사용합니다.
class Counter extends StatefulWidget {
const Counter({super.key});
@override
State<Counter> createState() => _CounterState();
}
class _CounterState extends State<Counter> {
int _count = 0;
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('카운트: $_count', style: const TextStyle(fontSize: 24)),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () => setState(() => _count++),
child: const Text('증가'),
),
],
);
}
}
setState()를 호출하면 Flutter가 build() 메서드를 다시 실행하여 UI를 갱신합니다. 이것이 Flutter의 기본적인 상태 관리 방식입니다.
레이아웃 위젯
화면을 구성할 때 자주 사용하는 레이아웃 위젯들을 알아두면 좋습니다.
- Column: 자식 위젯들을 세로로 배치합니다.
- Row: 자식 위젯들을 가로로 배치합니다.
- Stack: 자식 위젯들을 겹쳐서 배치합니다.
- ListView: 스크롤 가능한 목록을 만듭니다.
- Padding: 내부 여백을 추가합니다.
- SizedBox: 고정 크기 공간이나 위젯 간 간격을 만듭니다.
- Expanded/Flexible: 남은 공간을 비율로 분배합니다.
Column(
children: [
Row(
children: [
Expanded(child: Text('왼쪽')),
Expanded(child: Text('오른쪽')),
],
),
const SizedBox(height: 20),
Expanded(
child: ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) => ListTile(
title: Text(items[index]),
),
),
),
],
)
내비게이션
Flutter에서 화면 간 이동은 Navigator를 사용합니다. 기본 방식은 Navigator.push()와 Navigator.pop()이지만, 실제 프로젝트에서는 go_router 같은 라우팅 패키지를 사용하는 것을 권장합니다.
// 기본 Navigator 방식
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const DetailPage()),
);
// go_router 방식 (선언적 라우팅)
GoRouter(
routes: [
GoRoute(path: '/', builder: (context, state) => const HomePage()),
GoRoute(path: '/detail/:id', builder: (context, state) {
final id = state.pathParameters['id']!;
return DetailPage(id: id);
}),
],
)
상태 관리 소개
앱이 복잡해지면 setState()만으로는 상태 관리가 어려워집니다. 여러 화면에서 같은 데이터를 공유하거나, 전역 설정을 관리해야 하는 경우가 대표적입니다.
Flutter의 상태 관리 솔루션은 다양합니다. Provider, Riverpod, Bloc, GetX 등이 있는데, 입문자에게는 Riverpod을 추천합니다. 타입 안전성이 뛰어나고, 컴파일 타임에 오류를 잡아주며, 테스트 작성이 용이합니다.
// Riverpod 간단 예시
final counterProvider = StateProvider<int>((ref) => 0);
class MyWidget extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final count = ref.watch(counterProvider);
return Text('카운트: $count');
}
}
핫 리로드: Flutter의 킬러 기능
Flutter의 가장 강력한 기능 중 하나는 핫 리로드(Hot Reload) 입니다. 코드를 수정하고 저장하면 약 1초 이내에 앱에 변경 사항이 반영됩니다. 앱을 다시 빌드하고 다시 실행할 필요가 없습니다. 이 기능 덕분에 UI를 실험하고 반복 작업하는 속도가 극적으로 빨라집니다.
핫 리로드는 앱의 상태를 유지하면서 UI만 갱신하므로, 예를 들어 3번째 탭에서 스크롤을 내린 상태에서 코드를 수정해도 그 상태 그대로 변경 사항을 확인할 수 있습니다.
빌드와 실행
개발이 완료되면 각 플랫폼별로 빌드합니다.
# Android APK 빌드
flutter build apk --release
# Android App Bundle (Google Play 배포용)
flutter build appbundle --release
# iOS 빌드 (Mac 필요)
flutter build ios --release
빌드 전에 flutter analyze로 코드 품질을 점검하고, flutter test로 테스트를 실행하는 습관을 들이세요.
Flutter는 러닝 커브가 있지만, 일단 기본 개념을 익히면 빠른 속도로 앱을 만들 수 있습니다. 첫 번째 앱은 할 일 목록이나 메모 앱처럼 간단한 것으로 시작하고, 점점 복잡한 기능을 추가해보세요. 가장 중요한 것은 직접 코드를 작성하고 실행해보는 것입니다.