[2026] 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. 설치 및 프로젝트 생성
설치
아래 코드는 bash를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
# macOS
brew install flutter
# Windows
# Flutter SDK 다운로드 및 PATH 추가
프로젝트 생성
flutter create my_app
cd my_app
flutter run
3. 기본 Widgets
다음은 dart를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
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
다음은 dart를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
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
다음은 dart를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// 화면 이동
Navigator.push(
context,
MaterialPageRoute(builder: (context) => DetailsScreen()),
);
// 뒤로가기
Navigator.pop(context);
// 데이터 전달
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DetailsScreen(userId: 1),
),
);
Named Routes
아래 코드는 dart를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
MaterialApp(
initialRoute: '/',
routes: {
'/': (context) => HomeScreen(),
'/details': (context) => DetailsScreen(),
},
);
// 사용
Navigator.pushNamed(context, '/details');
6. State Management (Provider)
flutter pub add provider
다음은 dart를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
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
다음은 dart를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
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
다음은 dart를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
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 등 대기업에서 사용하고 있습니다.