Flutter Basic Chapter 1 - Flutter 프로젝트 생성과 Hello World

Flutter 프로젝트 뼈대 세우기

🎯 이번 단계에서 배울 것

  • flutter create가 만들어 주는 기본 파일과 폴더 구조 파악
  • .gitignore, analysis_options.yaml, pubspec.yaml 등 핵심 설정 파일 역할 이해
  • Android, iOS, Web, Desktop(Windows·macOS·Linux) 플랫폼별 초기 코드 살펴보기
  • 기본 위젯 테스트 템플릿(test/widget_test.dart)이 어떤 구조로 생성되는지 확인

📂 파일 구조

1
2
3
4
5
6
7
새로 만들어지는 파일
- .gitignore / .metadata
- README.md / analysis_options.yaml
- pubspec.yaml / pubspec.lock
- android/**, ios/**, linux/**, macos/**, web/**, windows/** 플랫폼 코드 전체
- lib/main.dart (기본 앱 진입점)
- test/widget_test.dart (위젯 테스트 템플릿)

📝 1단계: Flutter 프로젝트 생성하기

실행한 명령 (터미널):

1
flutter create toonflix

🔍 코드 상세 설명

1. 버전 관리 제외 목록(.gitignore)

1
2
3
4
5
6
7
# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.pub-cache/
.build/
  • 자동 생성·빌드 산출물은 버전 관리에서 제외해 저장소가 가벼워집니다.
  • 플랫폼별 빌드 아티팩트를 올리지 않아 협업 시 충돌을 줄일 수 있습니다.

2. 분석 규칙(analysis_options.yaml)

1
2
3
4
5
include: package:flutter_lints/flutter.yaml

linter:
rules:
avoid_print: false
  • Flutter 공식 린트 세트를 포함하여 코드 일관성을 유지합니다.
  • 기본 규칙에서 print 허용으로 초반 디버깅을 쉽게 합니다.

3. 프로젝트 메타 정보(pubspec.yaml)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
name: toonflix
publish_to: 'none'
description: A new Flutter project.

environment:
sdk: '>=2.18.2 <3.0.0'

dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.2

dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^2.0.0
  • 패키지 이름과 설명이 프로젝트를 대표합니다.
  • SDK 범위와 기본 의존성(flutter, cupertino_icons)이 명시되었습니다.
  • 테스트와 린트 패키지는 개발 단계에서만 사용됩니다.

4. 플랫폼별 진입 파일 예시

1
2
3
// android/app/src/main/kotlin/.../MainActivity.kt
class MainActivity: FlutterActivity() {
}
1
2
3
4
5
6
7
8
9
10
11
// ios/Runner/AppDelegate.swift
@UIApplicationMain
class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
  • Android는 FlutterActivity를 상속해 Flutter 엔진을 구동합니다.
  • iOS는 FlutterAppDelegate를 확장하여 플러그인 등록 코드를 포함합니다.

비교 예시:

1
2
3
4
5
# Flutter/Dart/Pub related   # ← 새 항목 추가 전
(doc/api 등의 빌드 산출물이 추적됨)

# Flutter/Dart/Pub related # ← 새 항목 추가 후
.build/ 등이 무시되어 깃 기록이 깔끔해짐

시각화:

1
2
3
4
5
6
7
8
9
10
┌─ 프로젝트 루트
│ ├─ lib/ → Dart 소스
│ ├─ android/ → Kotlin & Gradle
│ ├─ ios/ → Swift & Xcode 설정
│ ├─ macos/ → macOS Runner
│ ├─ linux/ → GTK Runner
│ ├─ windows/ → Win32 Runner
│ ├─ web/ → 웹 정적 자산
│ └─ test/ → 위젯 테스트 기본 템플릿
└──────────────────────────────────────────

🎨 화면 미리보기

1
2
3
4
5
6
터미널
┌──────────────────────────────────────┐
│ flutter create toonflix │
│ → Running "flutter pub get" │
│ → "toonflix" created successfully. │
└──────────────────────────────────────┘

📊 동작 흐름

1
2
3
4
5
1. flutter create toonflix 실행
2. Flutter SDK가 템플릿 파일 복사
3. pubspec.yaml 기반으로 의존성 다운로드(pub get)
4. 플랫폼별 Runner 프로젝트 생성
5. 기본 테스트 및 분석 설정 파일 생성

✅ 체크리스트

  • .gitignore가 빌드 산출물을 모두 제외하고 있는가?
  • analysis_options.yaml에서 적용된 린트 규칙을 이해했는가?
  • pubspec.yaml의 의존성과 SDK 범위를 확인했는가?
  • 각 플랫폼 폴더가 생성되어 있는가?
  • test/widget_test.dart가 기본 템플릿으로 추가된 것을 확인했는가?

💡 연습 과제

  1. 프로젝트 설명 수정: pubspec.yamldescription을 본인 프로젝트 목적에 맞게 수정해보세요.
  2. 린트 규칙 추가: analysis_options.yamlprefer_const_constructors와 같은 규칙을 더해보고 경고 변화를 확인하세요.
  3. 플랫폼 빌드 시도: 안드로이드 스튜디오나 Xcode로 Runner 프로젝트를 열어 빌드 과정을 체험해보세요.

Hello World 화면 구성

🎯 이번 단계에서 배울 것

  • Flutter 앱의 진입점(main()runApp) 이해
  • StatelessWidget, MaterialApp, Scaffold의 역할 파악
  • AppBar, Center, Text 위젯으로 기본 UI 구성하기
  • 자동 생성된 위젯 테스트 템플릿이 현재 코드와 어떻게 어긋나는지 확인

📂 파일 구조

1
2
3
4
5
수정되는 파일
- lib/main.dart (커스텀 Hello World UI 구성)

기본 유지 파일
- test/widget_test.dart (Counter 예제 테스트 템플릿)

📝 1단계: Hello World UI 빌드하기

전체 코드 (lib/main.dart):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import 'package:flutter/material.dart';

void main() {
runApp(App());
}

class App extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Hello flutter!'),
),
body: Center(
child: Text('Hello world!'),
),
),
);
}
}

🔍 코드 상세 설명

1. main() 함수와 runApp

1
2
3
void main() {
runApp(App());
}
  • Dart 애플리케이션의 진입점으로, 앱 실행 시 가장 먼저 호출됩니다.
  • runApp은 전달한 위젯(App)을 위젯 트리의 루트로 등록하고 렌더링을 시작합니다.

2. StatelessWidget으로 화면 정의

1
2
3
4
5
6
class App extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(...);
}
}
  • 상태 변화가 필요 없는 정적인 화면 데이터에 적합합니다.
  • build 메서드는 UI 트리를 반환하며, Flutter가 이 반환값을 사용해 화면을 그립니다.

3. MaterialAppScaffold 구조

1
2
3
4
5
6
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('Hello flutter!')),
body: Center(child: Text('Hello world!')),
),
);
  • MaterialApp은 Material Design 스타일의 전역 설정(네비게이션, 테마 등)을 제공합니다.
  • Scaffold는 상단 앱바, 본문 영역 등 기본 레이아웃 뼈대를 제공합니다.

4. UI 구성 위젯

1
2
3
4
5
6
appBar: AppBar(
title: Text('Hello flutter!'),
),
body: Center(
child: Text('Hello world!'),
),
  • AppBar는 화면 제목 영역을, Center는 자식 위젯을 중앙에 배치합니다.
  • Text 위젯으로 간단한 문자열을 출력합니다.

비교 예시:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Before (Flutter 기본 템플릿)
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}

// After (커밋 적용)
class App extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('Hello flutter!')),
body: Center(child: Text('Hello world!')),
),
);
}
}

시각화:

1
2
3
4
5
6
7
┌─────────────────────────────┐
│ Hello flutter! [ ] │ AppBar
├─────────────────────────────┤
│ │
│ Hello world! │ Center → Text
│ │
└─────────────────────────────┘

🎨 화면 미리보기

1
2
3
4
5
6
위젯 트리
App
└── MaterialApp
└── Scaffold
├── AppBar → Text('Hello flutter!')
└── Center → Text('Hello world!')

📊 동작 흐름

1
2
3
4
5
1. 사용자가 앱을 실행
2. Flutter 엔진이 main()을 호출
3. runApp(App())으로 루트 위젯 등록
4. App.build() → MaterialApp → Scaffold → UI 구성
5. 화면에 Hello World UI 렌더링

✅ 체크리스트

  • main()runApp() 호출 흐름을 설명할 수 있는가?
  • StatelessWidgetbuild() 메서드의 역할을 이해했는가?
  • MaterialAppScaffold의 차이를 설명할 수 있는가?
  • AppBar, Center, Text 위젯이 각자 맡은 역할을 알고 있는가?
  • 기본 테스트(test/widget_test.dart)가 아직 MyApp을 참조하여 실패할 수 있음을 인지했는가?

💡 연습 과제

  1. 테마 적용: MaterialApptheme: ThemeData(primarySwatch: Colors.blue)를 추가해 색상을 바꿔보세요.
  2. 텍스트 스타일 변경: Text('Hello world!')style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold)를 적용해보세요.
  3. 테스트 갱신: test/widget_test.dart에서 MyApp 대신 App을 pump하도록 수정해 보고 테스트를 실행해보세요.

자동 생성 테스트 코드 이해하기

🎯 이번 단계에서 배울 것

  • Flutter가 기본으로 제공하는 위젯 테스트 구조 파악
  • 현재 UI와 테스트 코드의 불일치 원인 분석
  • WidgetTester를 활용해 상호작용을 검증하는 흐름 이해

📂 파일 구조

1
2
검토할 파일
- test/widget_test.dart

📝 1단계: 기본 테스트 읽어보기

전체 코드 (test/widget_test.dart):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// This is a basic Flutter widget test.
//
// To perform an interaction with a widget in your test, use the WidgetTester
// utility in the flutter_test package. For example, you can send tap and scroll
// gestures. You can also use WidgetTester to find child widgets in the widget
// tree, read text, and verify that the values of widget properties are correct.

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';

import 'package:toonflix/main.dart';

void main() {
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(const MyApp());

// Verify that our counter starts at 0.
expect(find.text('0'), findsOneWidget);
expect(find.text('1'), findsNothing);

// Tap the '+' icon and trigger a frame.
await tester.tap(find.byIcon(Icons.add));
await tester.pump();

// Verify that our counter has incremented.
expect(find.text('0'), findsNothing);
expect(find.text('1'), findsOneWidget);
});
}

🔍 코드 상세 설명

1. testWidgets 흐름

  • pumpWidget으로 테스트 대상 위젯을 렌더링하고 프레임을 강제로 갱신합니다.
  • find.text, find.byIcon으로 위젯을 검색하고 expect로 검증합니다.

2. 현재 실패하는 이유

  • 커밋된 실제 코드에는 MyApp이 존재하지 않고 App 클래스만 있습니다.
  • Counter 버튼 UI도 삭제되었으므로 '0', '1', Icons.add를 찾을 수 없어 테스트가 실패합니다.

비교 예시:

1
2
await tester.pumpWidget(const MyApp()); // 기본 템플릿 기대 코드
await tester.pumpWidget(App()); // 현재 프로젝트에 맞는 코드

시각화:

1
2
3
4
5
6
7
8
9
10
테스트 기대 UI
┌─ AppBar: "Flutter Demo" ─────────────┐
│ 0 │
│ [+] button │
└──────────────────────────────────────┘

실제 UI (현재 프로젝트)
┌─ AppBar: "Hello flutter!" ───────────┐
│ Hello world! │
└──────────────────────────────────────┘

🎨 화면 미리보기

1
2
3
테스트 실행 예상 결과
- 기본 코드 유지: green (모든 검증 통과)
- 현재 코드 상태: red (위젯 탐색 실패로 AssertionError)

📊 동작 흐름

1
2
3
1. WidgetTester가 MyApp을 렌더링하려 시도
2. MyApp 클래스를 찾지 못해 런타임 오류 발생 또는
3. (MyApp을 만든 후라도) '+' 아이콘과 Counter 텍스트 부재로 expect 실패

✅ 체크리스트

  • testWidgets 구조를 이해했는가?
  • 테스트가 현재 실패하는 이유를 설명할 수 있는가?
  • 실제 UI에 맞춰 테스트를 수정할 계획이 있는가?

💡 연습 과제

  1. 테스트 업데이트: pumpWidget(App())으로 교체하고, “Hello world!” 텍스트가 존재하는지 검사하도록 테스트를 바꿔보세요.
  2. 추가 검증 작성: AppBar 제목이 “Hello flutter!”인지 확인하는 단언문을 추가해보세요.
  3. 위젯 찾기 연습: find.byType(AppBar) 등을 사용하여 원하는 위젯을 찾는 방법을 연습해보세요.

📚 전체 흐름 정리

  • flutter create 명령으로 모든 플랫폼 폴더와 설정 파일이 생성되었습니다.
  • lib/main.dart를 수정하여 간단한 Hello World UI를 구성했습니다.
  • 자동 생성된 테스트는 아직 Counter 예제를 기준으로 하므로 현 상태에서는 실패합니다.

✅ 최종 점검 리스트

  • 프로젝트 구조와 핵심 설정 파일 역할을 명확히 이해했다.
  • Hello World UI를 직접 설명하거나 다시 작성할 수 있다.
  • 기본 테스트 템플릿을 현재 코드에 맞게 조정할 수 있다.

출처 : https://nomadcoders.co/flutter-for-beginners