Flutter 완벽 가이드 | 크로스플랫폼 앱·Dart·Widgets·State·실전 활용
이 글의 핵심
Flutter로 iOS/Android/Web 앱을 개발하는 완벽 가이드입니다. Widgets, State Management, Navigation, API, 배포까지 실전 예제로 정리했습니다.
실무 경험 공유: Flutter로 iOS/Android/Web 앱을 동시에 출시하면서, 일관된 UI와 60fps 성능을 제공할 수 있었던 경험을 공유합니다.
들어가며: “일관된 UI가 필요해요”
실무 문제 시나리오
시나리오 1: 플랫폼마다 UI가 달라요
React Native는 Native 컴포넌트를 사용합니다. Flutter는 자체 렌더링으로 일관됩니다.
시나리오 2: 성능이 중요해요
Bridge 오버헤드가 있습니다. Flutter는 Native로 컴파일됩니다.
시나리오 3: 웹도 지원하고 싶어요
별도 개발이 필요합니다. Flutter는 웹도 지원합니다.
1. Flutter란?
핵심 특징
Flutter는 Google의 크로스플랫폼 UI 프레임워크입니다.
주요 장점:
- 크로스플랫폼: iOS, Android, Web, Desktop
- 빠른 성능: Native 컴파일
- 일관된 UI: 자체 렌더링 엔진
- Hot Reload: 즉시 반영
- 풍부한 Widgets: Material + Cupertino
2. 설치 및 프로젝트 생성
설치
# macOS
brew install flutter
# Windows
# Flutter SDK 다운로드 및 PATH 추가
프로젝트 생성
flutter create my_app
cd my_app
flutter run
3. 기본 Widgets
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: HomeScreen(),
);
}
}
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Home'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Hello Flutter!', style: TextStyle(fontSize: 24)),
SizedBox(height: 16),
ElevatedButton(
onPressed: () {
print('Button pressed');
},
child: Text('Click me'),
),
],
),
),
);
}
}
4. Stateful Widget
class CounterScreen extends StatefulWidget {
@override
_CounterScreenState createState() => _CounterScreenState();
}
class _CounterScreenState extends State<CounterScreen> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Counter')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Count:', style: TextStyle(fontSize: 18)),
Text('$_counter', style: TextStyle(fontSize: 48, fontWeight: FontWeight.bold)),
SizedBox(height: 16),
ElevatedButton(
onPressed: _incrementCounter,
child: Text('Increment'),
),
],
),
),
);
}
}
5. Navigation
// 화면 이동
Navigator.push(
context,
MaterialPageRoute(builder: (context) => DetailsScreen()),
);
// 뒤로가기
Navigator.pop(context);
// 데이터 전달
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DetailsScreen(userId: 1),
),
);
Named Routes
MaterialApp(
initialRoute: '/',
routes: {
'/': (context) => HomeScreen(),
'/details': (context) => DetailsScreen(),
},
);
// 사용
Navigator.pushNamed(context, '/details');
6. State Management (Provider)
flutter pub add provider
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class Counter with ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners();
}
}
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => Counter(),
child: MyApp(),
),
);
}
class CounterScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Count:'),
Consumer<Counter>(
builder: (context, counter, child) {
return Text('${counter.count}', style: TextStyle(fontSize: 48));
},
),
ElevatedButton(
onPressed: () {
context.read<Counter>().increment();
},
child: Text('Increment'),
),
],
),
),
);
}
}
7. API 호출
flutter pub add http
import 'package:http/http.dart' as http;
import 'dart:convert';
class User {
final int id;
final String name;
final String email;
User({required this.id, required this.name, required this.email});
factory User.fromJson(Map<String, dynamic> json) {
return User(
id: json['id'],
name: json['name'],
email: json['email'],
);
}
}
Future<List<User>> fetchUsers() async {
final response = await http.get(Uri.parse('https://api.example.com/users'));
if (response.statusCode == 200) {
List<dynamic> body = jsonDecode(response.body);
return body.map((json) => User.fromJson(json)).toList();
} else {
throw Exception('Failed to load users');
}
}
class UserListScreen extends StatefulWidget {
@override
_UserListScreenState createState() => _UserListScreenState();
}
class _UserListScreenState extends State<UserListScreen> {
late Future<List<User>> futureUsers;
@override
void initState() {
super.initState();
futureUsers = fetchUsers();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Users')),
body: FutureBuilder<List<User>>(
future: futureUsers,
builder: (context, snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: snapshot.data!.length,
itemBuilder: (context, index) {
final user = snapshot.data![index];
return ListTile(
title: Text(user.name),
subtitle: Text(user.email),
);
},
);
} else if (snapshot.hasError) {
return Center(child: Text('Error: ${snapshot.error}'));
}
return Center(child: CircularProgressIndicator());
},
),
);
}
}
8. Form
class LoginForm extends StatefulWidget {
@override
_LoginFormState createState() => _LoginFormState();
}
class _LoginFormState extends State<LoginForm> {
final _formKey = GlobalKey<FormState>();
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
@override
void dispose() {
_emailController.dispose();
_passwordController.dispose();
super.dispose();
}
void _handleSubmit() {
if (_formKey.currentState!.validate()) {
print('Email: ${_emailController.text}');
print('Password: ${_passwordController.text}');
}
}
@override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
children: [
TextFormField(
controller: _emailController,
decoration: InputDecoration(labelText: 'Email'),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter email';
}
return null;
},
),
TextFormField(
controller: _passwordController,
decoration: InputDecoration(labelText: 'Password'),
obscureText: true,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter password';
}
return null;
},
),
SizedBox(height: 16),
ElevatedButton(
onPressed: _handleSubmit,
child: Text('Submit'),
),
],
),
);
}
}
정리 및 체크리스트
핵심 요약
- Flutter: 크로스플랫폼 UI 프레임워크
- Dart: 프로그래밍 언어
- 빠른 성능: Native 컴파일
- 일관된 UI: 자체 렌더링
- Hot Reload: 즉시 반영
- EAS: 클라우드 빌드
구현 체크리스트
- Flutter 설치
- 프로젝트 생성
- 기본 Widgets 사용
- State Management
- Navigation 구현
- API 호출
- Form 구현
- 배포
같이 보면 좋은 글
- React Native 완벽 가이드
- Dart 완벽 가이드
- 모바일 앱 개발 가이드
이 글에서 다루는 키워드
Flutter, Dart, Mobile, iOS, Android, Cross-platform, Web
자주 묻는 질문 (FAQ)
Q. React Native와 비교하면 어떤가요?
A. Flutter가 더 빠르고 일관된 UI를 제공합니다. React Native는 JavaScript 생태계를 활용할 수 있습니다.
Q. 학습 곡선은 어떤가요?
A. Dart를 배워야 해서 초반에는 어렵지만, 익숙해지면 생산적입니다.
Q. 웹도 지원하나요?
A. 네, Flutter Web으로 웹 앱도 만들 수 있습니다.
Q. 프로덕션에서 사용해도 되나요?
A. 네, Google, Alibaba, BMW 등 대기업에서 사용하고 있습니다.