Ch 01. Flutter 프로젝트 생성과 구조
Dart로 CLI 도구를 만들고, REST API 서버를 만들고, 패키지를 배포했습니다. 이제 같은 언어로 모바일 앱을 만들 차례입니다. Flutter는 Dart로 작성된 크로스플랫폼 UI 프레임워크입니다. 하나의 코드베이스로 iOS, Android, 웹, 데스크톱 앱을 모두 만들 수 있습니다.
이번 PART에서 만들 앱은 PART 06의 todo_server와 통신하는 할 일 관리 앱입니다. 백엔드와 프론트엔드가 모두 Dart로 작성된 Full-Stack Dart 프로젝트입니다.
Flutter 설치 확인
flutter --versionflutter doctor
flutter doctor는 개발 환경의 문제를 진단합니다. Android Studio나 Xcode가 없으면 일부 항목이 실패하지만, 이 교재에서는 기본적으로 웹 또는 데스크톱 타겟으로 실행하므로 크게 걱정하지 않아도 됩니다.
프로젝트 생성
flutter create todo_appcd todo_app
생성된 구조를 살펴봅니다.
todo_app/
├── lib/
│ └── main.dart ← 앱 진입점
├── test/
│ └── widget_test.dart
├── android/ ← Android 플랫폼 코드
├── ios/ ← iOS 플랫폼 코드
├── web/ ← 웹 플랫폼 코드
├── linux/, macos/, windows/
└── pubspec.yaml
Flutter 프로젝트에서 우리가 주로 작업하는 폴더는 lib/입니다. 나머지는 각 플랫폼이 자동으로 관리합니다.
lib/ 폴더 구조 설계
기본 생성된 main.dart 하나에 모든 코드를 넣으면 금방 복잡해집니다. 처음부터 역할별로 폴더를 나눕니다.
lib/
├── main.dart ← 진입점
├── models/
│ └── todo.dart ← 데이터 모델
├── services/
│ └── api_service.dart ← HTTP 통신
├── providers/
│ └── todo_provider.dart ← 상태 관리
└── screens/
├── login_screen.dart ← 로그인 화면
└── todo_screen.dart ← 할 일 목록 화면
이 구조는 "기능별 분리(separation of concerns)" 원칙을 따릅니다. 화면(screens)은 UI만 담당하고, 서비스(services)는 네트워크 통신을, 프로바이더(providers)는 상태를 관리합니다.
main.dart 기본 구조
// 새 파일: lib/main.dart
import 'package:flutter/material.dart';
import 'screens/todo_screen.dart';
void main() {
runApp(const TodoApp());
}
class TodoApp extends StatelessWidget {
const TodoApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Todo App',
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: const Color(0xFF6750A4),
),
useMaterial3: true,
),
home: const TodoScreen(),
);
}
}
runApp()은 Flutter 앱의 시작점입니다. MaterialApp은 Material Design 테마와 라우팅을 제공하는 최상위 위젯입니다.
Todo 모델 정의
PART 06의 todo_server가 반환하는 JSON 구조에 맞는 모델을 만듭니다.
// 새 파일: lib/models/todo.dart
class Todo {
final int id;
final String title;
final bool completed;
final DateTime createdAt;
const Todo({
required this.id,
required this.title,
required this.completed,
required this.createdAt,
});
factory Todo.fromJson(Map<String, dynamic> json) {
return Todo(
id: json['id'] as int,
title: json['title'] as String,
completed: json['completed'] as bool,
createdAt: DateTime.parse(json['createdAt'] as String),
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'title': title,
'completed': completed,
'createdAt': createdAt.toIso8601String(),
};
}
Todo copyWith({
int? id,
String? title,
bool? completed,
DateTime? createdAt,
}) {
return Todo(
id: id ?? this.id,
title: title ?? this.title,
completed: completed ?? this.completed,
createdAt: createdAt ?? this.createdAt,
);
}
@override
String toString() => 'Todo(id: $id, title: $title, completed: $completed)';
}
copyWith 메서드는 Dart에서 불변 객체를 다룰 때 매우 유용합니다. 일부 필드만 변경한 새 객체를 만들 수 있습니다.
앱 실행
# 웹으로 실행flutter run -d chrome# 사용 가능한 디바이스 확인flutter devices# 특정 디바이스로 실행flutter run -d <device_id>
기본 카운터 앱이 실행되면 Flutter 환경이 정상적으로 구성된 것입니다. 코드를 수정하고 저장하면 핫 리로드가 즉시 반영됩니다. 앱을 재시작하지 않고도 UI 변경사항을 바로 확인할 수 있습니다.
핫 리로드와 핫 리스타트의 차이를 알아 둡니다. 핫 리로드(r 키)는 위젯 트리만 다시 빌드하고 상태를 유지합니다. 핫 리스타트(R 키)는 앱을 완전히 재시작합니다. 상태에 관련된 변경이면 핫 리스타트가 필요합니다.
pubspec.yaml 의존성 추가
이후 챕터에서 사용할 패키지를 미리 추가합니다.
# 수정: pubspec.yamlname: todo_appdescription: "A Full-Stack Dart todo application."publish_to: 'none'version: 1.0.0+1environment: sdk: '>=3.0.0 <4.0.0'dependencies: flutter: sdk: flutter http: ^1.2.0 provider: ^6.1.0dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^4.0.0flutter: uses-material-design: true
flutter pub get
이번 챕터 정리
flutter create명령으로 프로젝트를 생성하고,lib/폴더 구조를 역할별로 설계했습니다.MaterialApp과StatelessWidget으로 앱의 기본 뼈대를 만들었습니다.- PART 06 서버의 JSON 응답에 맞는
Todo모델을fromJson,toJson,copyWith와 함께 구현했습니다. http,provider패키지를 추가했습니다.
다음 챕터에서는 Flutter의 핵심인 위젯을 이용해서 UI를 구성합니다.