0. Flutter TikTok Clone - 초기 프로젝트 세팅

Flutter 프로젝트 생성

📌 학습 목표

  • Flutter 프로젝트의 기본 구조를 이해합니다
  • MaterialApp의 기본 설정 방법을 배웁니다
  • 프로젝트 초기 파일 구조를 파악합니다
  • 테마 색상 설정 방법을 이해합니다

📂 파일 구조

1
2
3
4
5
6
7
8
9
10
11
12
13
tiktok/
├── lib/
│ ├── main.dart # 앱 진입점
│ └── constants/
│ ├── sizes.dart # 크기 상수 (빈 파일)
│ └── gaps.dart # 간격 위젯 (빈 파일)
├── pubspec.yaml # 프로젝트 설정 파일
├── android/ # Android 플랫폼 코드
├── ios/ # iOS 플랫폼 코드
├── web/ # Web 플랫폼 코드
├── windows/ # Windows 플랫폼 코드
├── linux/ # Linux 플랫폼 코드
└── macos/ # macOS 플랫폼 코드

💻 초기 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(const TikTokApp());
}

class TikTokApp extends StatelessWidget {
const TikTokApp({super.key});

@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'TikTok Clone',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFFE9435A)),
useMaterial3: false,
),
home: Container(),
);
}
}

📖 코드 상세 설명

1. main() 함수

1
2
3
void main() {
runApp(const TikTokApp());
}
  • void main(): Dart 프로그램의 진입점(Entry Point)입니다
  • runApp(): Flutter 앱을 시작하는 함수입니다
  • const TikTokApp(): 앱의 최상위 위젯을 생성합니다
    • const를 사용하여 컴파일 타임에 상수로 만들어 성능을 향상시킵니다

2. TikTokApp 클래스

1
2
class TikTokApp extends StatelessWidget {
const TikTokApp({super.key});
  • StatelessWidget: 상태가 변하지 않는 위젯입니다
  • const 생성자: 불필요한 재빌드를 방지합니다
  • super.key: 부모 클래스의 key 매개변수를 전달합니다
    • key는 위젯을 고유하게 식별하는데 사용됩니다

3. MaterialApp 설정

1
2
3
4
5
6
7
8
return MaterialApp(
title: 'TikTok Clone',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFFE9435A)),
useMaterial3: false,
),
home: Container(),
);

MaterialApp 속성 설명:

  • title: 앱의 제목

    • 태스크 매니저에서 보이는 이름입니다
    • iOS에서는 사용되지 않습니다
  • theme: 앱의 전체적인 테마 설정

    • ThemeData: Material Design 테마를 정의합니다
  • colorScheme: 앱의 색상 체계

    • ColorScheme.fromSeed(): 시드 컬러를 기반으로 전체 색상 팔레트를 자동 생성합니다
    • seedColor: Color(0xFFE9435A): TikTok의 시그니처 핑크색
      • 0xFF: 완전 불투명 (Alpha 255)
      • E9435A: RGB 값 (233, 67, 90)
  • useMaterial3: Material Design 3 사용 여부

    • false: Material Design 2 사용 (기존 디자인 시스템)
    • Material 2는 더 안정적이고 검증된 디자인을 제공합니다
  • home: 앱의 기본 화면

    • 현재는 빈 Container() 사용

📦 pubspec.yaml 설정

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
name: tiktok
description: "Tiktok Clone Project"
publish_to: 'none'

version: 1.0.0+1

environment:
sdk: '>=3.3.0 <4.0.0'

dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.6

dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^3.0.1

flutter:
uses-material-design: true

주요 설정 설명:

  1. name: tiktok

    • 프로젝트 이름 (패키지 이름으로 사용됩니다)
  2. publish_to: ‘none’

    • pub.dev에 실수로 배포되는 것을 방지합니다
    • 개인 프로젝트에 권장됩니다
  3. version: 1.0.0+1

    • 앱 버전 번호
    • 1.0.0: 버전 이름 (사용자에게 보이는 버전)
    • +1: 빌드 번호 (내부적으로 사용하는 번호)
  4. environment.sdk

    • Dart SDK 버전 범위 지정
    • >=3.3.0 <4.0.0: 3.3.0 이상, 4.0.0 미만
  5. dependencies

    • flutter: Flutter SDK
    • cupertino_icons: iOS 스타일 아이콘 패키지
  6. dev_dependencies

    • flutter_test: 테스트 프레임워크
    • flutter_lints: 코드 품질 검사 도구
  7. uses-material-design: true

    • Material Design 아이콘과 폰트를 사용 가능하게 합니다

🎨 시각적 다이어그램

프로젝트 구조

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
┌─────────────────────────────────────┐
│ Flutter Project │
├─────────────────────────────────────┤
│ │
│ ┌───────────────────────────────┐ │
│ │ main.dart │ │
│ │ ┌────────────────────────┐ │ │
│ │ │ void main() │ │ │
│ │ │ runApp(TikTokApp) │ │ │
│ │ └────────────────────────┘ │ │
│ │ │ │
│ │ ┌────────────────────────┐ │ │
│ │ │ TikTokApp Widget │ │ │
│ │ │ (StatelessWidget) │ │ │
│ │ │ │ │ │
│ │ │ ┌──────────────┐ │ │ │
│ │ │ │ MaterialApp │ │ │ │
│ │ │ │ - title │ │ │ │
│ │ │ │ - theme │ │ │ │
│ │ │ │ - home │ │ │ │
│ │ │ └──────────────┘ │ │ │
│ │ └────────────────────────┘ │ │
│ └───────────────────────────────┘ │
│ │
│ ┌───────────────────────────────┐ │
│ │ constants/ │ │
│ │ - sizes.dart │ │
│ │ - gaps.dart │ │
│ └───────────────────────────────┘ │
└─────────────────────────────────────┘

앱 실행 흐름

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1. main() 함수 실행
↓
2. runApp(TikTokApp()) 호출
↓
3. TikTokApp 위젯 생성
↓
4. build() 메서드 실행
↓
5. MaterialApp 반환
↓
6. 테마 적용 (색상, Material 3)
↓
7. home에 Container() 표시
↓
8. 빈 화면 출력

✅ 체크리스트

  • Flutter 프로젝트가 정상적으로 생성되었는가?
  • main.dart 파일이 lib/ 폴더에 있는가?
  • MaterialApp의 테마 색상이 설정되었는가?
  • useMaterial3가 false로 설정되었는가?
  • constants 폴더가 생성되었는가?
  • pubspec.yaml의 기본 설정이 완료되었는가?

🔑 핵심 개념 정리

개념 설명 왜 중요한가?
main() 앱의 시작점 모든 Flutter 앱은 main()에서 시작합니다
runApp() 위젯 트리를 화면에 렌더링 Flutter 앱을 실행하는 필수 함수입니다
StatelessWidget 불변하는 위젯 상태가 없어 더 효율적입니다
MaterialApp Material Design 앱의 루트 네비게이션, 테마 등을 관리합니다
ColorScheme.fromSeed 시드 컬러에서 팔레트 생성 일관된 색상 체계를 자동으로 만듭니다
const 컴파일 타임 상수 불필요한 재빌드를 방지합니다

💡 실습 과제

  1. 색상 변경하기

    1
    2
    3
    // 다른 색상으로 시도해보세요
    seedColor: const Color(0xFF0000FF) // 파란색
    seedColor: const Color(0xFF00FF00) // 초록색
  2. 앱 제목 변경하기

    1
    title: 'My First App',
  3. Material 3로 변경해보기

    1
    2
    useMaterial3: true,
    // 차이점을 관찰해보세요

❓ 자주 묻는 질문

Q1: const를 왜 사용하나요?

A: const는 컴파일 타임에 상수로 만들어 불필요한 재빌드를 방지하고 메모리를 절약합니다. 변하지 않는 위젯은 항상 const로 만드는 것이 좋습니다.

Q2: StatelessWidget vs StatefulWidget 차이는?

A: StatelessWidget은 한 번 그려지면 변하지 않습니다. StatefulWidget은 setState()로 상태를 변경하고 다시 그릴 수 있습니다. 초기 설정에는 변할 일이 없으므로 StatelessWidget을 사용합니다.

Q3: ColorScheme.fromSeed의 장점은?

A: 하나의 시드 컬러에서 primary, secondary, background 등 전체 색상 팔레트를 자동으로 생성해 일관된 디자인을 유지할 수 있습니다.

Q4: 왜 home에 빈 Container()를 넣나요?

A: MaterialApp은 home이 필수이기 때문입니다. 나중에 실제 화면을 만들면 이 Container를 교체합니다.


Sizes 상수 시스템 구축

📌 학습 목표

  • 크기 상수를 관리하는 시스템을 이해합니다
  • 하드코딩을 피하는 방법을 배웁니다
  • 일관된 디자인 시스템을 구축하는 방법을 학습합니다
  • static const의 사용법을 익힙니다

📂 파일 위치

1
2
3
lib/
└── constants/
└── sizes.dart

💻 sizes.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
31
class Sizes {
static const size1 = 1.0;
static const size2 = 2.0;
static const size3 = 3.0;
static const size4 = 4.0;
static const size5 = 5.0;
static const size6 = 6.0;
static const size7 = 7.0;
static const size8 = 8.0;
static const size9 = 9.0;
static const size10 = 10.0;
static const size11 = 11.0;
static const size12 = 12.0;
static const size14 = 14.0;
static const size16 = 16.0;
static const size20 = 20.0;
static const size24 = 24.0;
static const size28 = 28.0;
static const size32 = 32.0;
static const size36 = 36.0;
static const size40 = 40.0;
static const size44 = 44.0;
static const size48 = 48.0;
static const size52 = 52.0;
static const size56 = 56.0;
static const size60 = 60.0;
static const size64 = 64.0;
static const size72 = 72.0;
static const size80 = 80.0;
static const size96 = 96.0;
}

📖 코드 상세 설명

1. 클래스 구조

1
2
3
class Sizes {
// 상수들...
}
  • 왜 클래스를 사용하나요?
    • 관련된 상수들을 하나로 묶어 관리합니다
    • 네임스페이스 역할을 하여 다른 곳의 변수명과 충돌을 방지합니다
    • Sizes.size16처럼 명확하게 사용할 수 있습니다

2. static const 키워드

1
static const size16 = 16.0;

static 키워드:

  • 클래스 인스턴스를 생성하지 않고 사용할 수 있습니다
  • 메모리에 한 번만 할당됩니다
  • Sizes.size16처럼 클래스명으로 직접 접근합니다

const 키워드:

  • 컴파일 타임에 값이 결정되는 상수입니다
  • 런타임에 변경할 수 없습니다
  • 메모리를 절약하고 성능을 향상시킵니다

왜 둘 다 사용하나요?

1
2
3
4
5
6
// ❌ 나쁜 방법
final size16 = 16.0; // 인스턴스마다 생성됨
static size16 = 16.0; // 변경 가능

// ✅ 좋은 방법
static const size16 = 16.0; // 한 번만 생성, 변경 불가

3. 크기 값 체계

작은 크기 (1-12):

1
2
3
4
5
static const size1 = 1.0;   // 매우 작은 여백, 테두리
static const size2 = 2.0; // 작은 여백
static const size4 = 4.0; // 기본 여백
static const size8 = 8.0; // 일반 여백
static const size12 = 12.0; // 중간 여백

중간 크기 (14-32):

1
2
3
4
5
static const size14 = 14.0; // 작은 패딩
static const size16 = 16.0; // 기본 패딩
static const size20 = 20.0; // 일반 패딩
static const size24 = 24.0; // 큰 패딩
static const size32 = 32.0; // 매우 큰 패딩

큰 크기 (36-96):

1
2
3
4
static const size36 = 36.0; // 아이콘 크기
static const size48 = 48.0; // 큰 아이콘/버튼
static const size64 = 64.0; // 프로필 이미지
static const size96 = 96.0; // 큰 이미지/컨테이너

🎯 사용 예시

Before (하드코딩 ❌)

1
2
3
4
5
Container(
width: 16.0, // 숫자를 직접 입력
height: 48.0, // 일관성 없음
padding: EdgeInsets.all(24.0), // 변경이 어려움
)

After (상수 사용 ✅)

1
2
3
4
5
Container(
width: Sizes.size16, // 명확한 의미
height: Sizes.size48, // 일관성 유지
padding: EdgeInsets.all(Sizes.size24), // 쉬운 변경
)

🎨 시각적 다이어그램

크기 체계 시각화

1
2
3
4
5
6
7
8
9
작은 크기                  중간 크기                  큰 크기
─────────────────────────────────────────────────────────────
1px ▏ 14px ▐ 36px ████
2px ▌ 16px █ 48px ██████
4px █ 20px ██ 64px ████████
8px ██ 24px ███ 96px ████████████
12px ███ 32px ████

용도: 여백, 테두리 용도: 패딩, 마진 용도: 아이콘, 이미지

Sizes 클래스 사용 흐름

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
┌─────────────────────────────────────┐
│ Sizes Class │
├─────────────────────────────────────┤
│ │
│ static const size1 = 1.0 │
│ static const size2 = 2.0 │
│ static const size4 = 4.0 │
│ ... │
│ static const size96 = 96.0 │
│ │
└─────────────────────────────────────┘
↓ Use
┌────────────────────────────────────────────────┐
│ Used in other files │
├────────────────────────────────────────────────┤
│ │
│ import 'package:tiktok/constants/sizes.dart'; │
│ │
│ Container( │
│ width: Sizes.size16, ← access │
│ height: Sizes.size48, │
│ ) │
│ │
└────────────────────────────────────────────────┘

💡 왜 이런 시스템이 필요한가?

1. 일관성 (Consistency)

1
2
3
4
5
6
7
8
9
// ❌ 하드코딩: 비슷한 크기가 여러 곳에서 다르게 사용됨
padding: EdgeInsets.all(16.0)
padding: EdgeInsets.all(15.0) // 실수
padding: EdgeInsets.all(17.0) // 실수

// ✅ 상수 사용: 항상 같은 값
padding: EdgeInsets.all(Sizes.size16)
padding: EdgeInsets.all(Sizes.size16)
padding: EdgeInsets.all(Sizes.size16)

2. 유지보수 (Maintainability)

1
2
3
4
5
6
7
// ❌ 하드코딩: 모든 곳을 찾아서 수정해야 함
// 100개 파일에서 16.0을 20.0으로 변경? 😱

// ✅ 상수 사용: 한 곳만 수정
class Sizes {
static const size16 = 20.0; // 여기만 바꾸면 모든 곳 변경!
}

3. 가독성 (Readability)

1
2
3
4
5
// ❌ 하드코딩: 의미를 알 수 없음
Container(width: 16.0) // 16은 무슨 의미?

// ✅ 상수 사용: 의미가 명확함
Container(width: Sizes.size16) // 크기 16을 사용한다는 의미!

4. 디자인 시스템 (Design System)

1
2
3
4
5
모든 UI는 정해진 크기만 사용
↓
일관된 디자인 시스템 구축
↓
전문적인 앱 완성

✅ 체크리스트

  • Sizes 클래스가 생성되었는가?
  • static const로 모든 상수가 선언되었는가?
  • 1부터 96까지 필요한 크기가 모두 포함되었는가?
  • 타입이 double (1.0, 2.0…)로 되어 있는가?

🔑 핵심 개념 정리

개념 설명 예시
static 클래스 레벨 멤버 Sizes.size16 인스턴스 없이 사용
const 컴파일 타임 상수 변경 불가, 메모리 효율적
하드코딩 방지 숫자 직접 입력 금지 16.0 → Sizes.size16
디자인 시스템 일관된 크기 체계 모든 UI가 동일한 크기 사용

💡 실습 과제

  1. Sizes 클래스를 사용하여 Container 만들기

    1
    2
    3
    4
    5
    Container(
    width: Sizes.size64,
    height: Sizes.size64,
    padding: EdgeInsets.all(Sizes.size16),
    )
  2. 자주 사용하는 크기 찾기

    • 어떤 크기가 가장 많이 사용될 것 같나요?
    • 패딩, 마진, 아이콘에 어떤 크기를 쓸까요?
  3. 없는 크기가 필요하다면?

    1
    2
    // 만약 size18이 필요하다면?
    static const size18 = 18.0; // 추가!

❓ 자주 묻는 질문

Q1: 왜 size13, size15 같은 숫자는 없나요?

A: 디자인 시스템에서는 일관된 간격을 위해 특정 숫자만 사용합니다. 4의 배수나 자주 사용하는 숫자만 포함하여 선택의 폭을 줄이고 일관성을 높입니다.

Q2: double 타입 대신 int를 사용하면 안되나요?

A: Flutter에서 크기는 대부분 double을 사용합니다. 1이 아닌 1.0을 사용하면 타입 변환 없이 바로 사용할 수 있어 편리합니다.

Q3: Sizes 클래스의 인스턴스를 만들어야 하나요?

A: 아니요! static이므로 Sizes.size16처럼 클래스명으로 바로 접근합니다. 인스턴스를 만들 필요가 없습니다.

Q4: 다른 파일에서 어떻게 사용하나요?

A: 파일 상단에 import 'package:tiktok/constants/sizes.dart';를 추가하면 됩니다.


Gaps 위젯 시스템 구축

📌 학습 목표

  • 재사용 가능한 간격 위젯을 만드는 방법을 배웁니다
  • Vertical과 Horizontal 간격의 차이를 이해합니다
  • SizedBox 위젯의 활용법을 익힙니다
  • const 위젯을 통한 성능 최적화를 학습합니다

📂 파일 위치

1
2
3
lib/
└── constants/
└── gaps.dart

💻 gaps.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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
import 'package:flutter/material.dart';
import 'package:tiktok/constants/sizes.dart';

class Gaps {
// Vertical Gaps
static const v1 = SizedBox(height: Sizes.size1);
static const v2 = SizedBox(height: Sizes.size2);
static const v3 = SizedBox(height: Sizes.size3);
static const v4 = SizedBox(height: Sizes.size4);
static const v5 = SizedBox(height: Sizes.size5);
static const v6 = SizedBox(height: Sizes.size6);
static const v7 = SizedBox(height: Sizes.size7);
static const v8 = SizedBox(height: Sizes.size8);
static const v9 = SizedBox(height: Sizes.size9);
static const v10 = SizedBox(height: Sizes.size10);
static const v11 = SizedBox(height: Sizes.size11);
static const v12 = SizedBox(height: Sizes.size12);
static const v14 = SizedBox(height: Sizes.size14);
static const v16 = SizedBox(height: Sizes.size16);
static const v20 = SizedBox(height: Sizes.size20);
static const v24 = SizedBox(height: Sizes.size24);
static const v28 = SizedBox(height: Sizes.size28);
static const v32 = SizedBox(height: Sizes.size32);
static const v36 = SizedBox(height: Sizes.size36);
static const v40 = SizedBox(height: Sizes.size40);
static const v44 = SizedBox(height: Sizes.size44);
static const v48 = SizedBox(height: Sizes.size48);
static const v52 = SizedBox(height: Sizes.size52);
static const v56 = SizedBox(height: Sizes.size56);
static const v60 = SizedBox(height: Sizes.size60);
static const v64 = SizedBox(height: Sizes.size64);
static const v72 = SizedBox(height: Sizes.size72);
static const v80 = SizedBox(height: Sizes.size80);
static const v96 = SizedBox(height: Sizes.size96);

// Horizontal Gaps
static const h1 = SizedBox(width: Sizes.size1);
static const h2 = SizedBox(width: Sizes.size2);
static const h3 = SizedBox(width: Sizes.size3);
static const h4 = SizedBox(width: Sizes.size4);
static const h5 = SizedBox(width: Sizes.size5);
static const h6 = SizedBox(width: Sizes.size6);
static const h7 = SizedBox(width: Sizes.size7);
static const h8 = SizedBox(width: Sizes.size8);
static const h9 = SizedBox(width: Sizes.size9);
static const h10 = SizedBox(width: Sizes.size10);
static const h11 = SizedBox(width: Sizes.size11);
static const h12 = SizedBox(width: Sizes.size12);
static const h14 = SizedBox(width: Sizes.size14);
static const h16 = SizedBox(width: Sizes.size16);
static const h20 = SizedBox(width: Sizes.size20);
static const h24 = SizedBox(width: Sizes.size24);
static const h28 = SizedBox(width: Sizes.size28);
static const h32 = SizedBox(width: Sizes.size32);
static const h36 = SizedBox(width: Sizes.size36);
static const h40 = SizedBox(width: Sizes.size40);
static const h44 = SizedBox(width: Sizes.size44);
static const h48 = SizedBox(width: Sizes.size48);
static const h52 = SizedBox(width: Sizes.size52);
static const h56 = SizedBox(width: Sizes.size56);
static const h60 = SizedBox(width: Sizes.size60);
static const h64 = SizedBox(width: Sizes.size64);
static const h72 = SizedBox(width: Sizes.size72);
static const h80 = SizedBox(width: Sizes.size80);
static const h96 = SizedBox(width: Sizes.size96);
}

📖 코드 상세 설명

1. import 문

1
2
import 'package:flutter/material.dart';
import 'package:tiktok/constants/sizes.dart';

package:flutter/material.dart:

  • SizedBox 위젯을 사용하기 위해 필요합니다
  • Flutter의 Material Design 위젯을 제공합니다

package:tiktok/constants/sizes.dart:

  • 앞서 만든 Sizes 클래스를 사용하기 위해 import합니다
  • Sizes.size16 같은 상수를 사용할 수 있게 됩니다

2. Vertical Gaps (수직 간격)

1
static const v16 = SizedBox(height: Sizes.size16);

구성 요소:

  • static const: Sizes와 동일하게 한 번만 생성되는 상수입니다
  • v16: “vertical 16”의 약자입니다
  • SizedBox: Flutter의 빈 공간을 만드는 위젯입니다
  • height: 수직 방향의 크기를 지정합니다
  • Sizes.size16: 앞서 만든 크기 상수를 재사용합니다

왜 SizedBox를 사용하나요?

1
2
3
4
5
6
7
8
// SizedBox는 보이지 않는 빈 공간을 만듭니다
Column(
children: [
Text('첫 번째'),
SizedBox(height: 16), // 16픽셀 빈 공간
Text('두 번째'),
],
)

3. Horizontal Gaps (수평 간격)

1
static const h16 = SizedBox(width: Sizes.size16);

Vertical과의 차이:

  • v16: height 사용 → 위아래 간격 (↕)
  • h16: width 사용 → 좌우 간격 (↔)
1
2
3
4
5
6
7
Row(
children: [
Text('왼쪽'),
SizedBox(width: 16), // 16픽셀 빈 공간
Text('오른쪽'),
],
)

4. Sizes 클래스 재사용

1
2
3
4
// Sizes의 값을 그대로 사용
static const v16 = SizedBox(height: Sizes.size16);
↑
이미 정의된 상수 재사용

장점:

  • Sizes를 변경하면 Gaps도 자동으로 변경됩니다
  • 중복된 숫자를 입력할 필요가 없습니다
  • 일관성이 보장됩니다

🎯 사용 예시

Before (매번 SizedBox 작성 ❌)

1
2
3
4
5
6
7
8
9
10
Column(
children: [
Text('제목'),
SizedBox(height: 16.0), // 반복
Text('내용'),
SizedBox(height: 16.0), // 반복
Text('더보기'),
SizedBox(height: 24.0), // 다른 크기도 있음
],
)

After (Gaps 사용 ✅)

1
2
3
4
5
6
7
8
9
10
Column(
children: [
Text('제목'),
Gaps.v16, // 간단!
Text('내용'),
Gaps.v16, // 일관성!
Text('더보기'),
Gaps.v24, // 명확!
],
)

🎨 시각적 다이어그램

Vertical Gaps (수직 간격)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
┌─────────────────┐
│ Text Widget │
└─────────────────┘
↓
Gaps.v16 (height: 16)
↓
▼ 16px 빈 공간
↓
┌─────────────────┐
│ Text Widget │
└─────────────────┘
↓
Gaps.v24 (height: 24)
↓
▼ 24px 빈 공간
▼
↓
┌─────────────────┐
│ Text Widget │
└─────────────────┘

Horizontal Gaps (수평 간격)

1
2
3
4
5
6
┌──────┐      ┌──────┐      ┌──────┐
│ Text │ → │ Text │ → │ Text │
└──────┘ └──────┘ └──────┘
◄─► ◄──►
h16 h24
(16px) (24px)

Gaps 클래스 구조

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
31
32
33
34
35
36
37
┌─────────────────────────────────────┐
│ Gaps Class │
├─────────────────────────────────────┤
│ │
│ ┌────────────────────────────┐ │
│ │ Vertical Gaps (v) │ │
│ │ v1, v2, ... v96 │ │
│ │ SizedBox(height: ...) │ │
│ └────────────────────────────┘ │
│ │
│ ┌────────────────────────────┐ │
│ │ Horizontal Gaps (h) │ │
│ │ h1, h2, ... h96 │ │
│ │ SizedBox(width: ...) │ │
│ └────────────────────────────┘ │
│ │
└─────────────────────────────────────┘
↓ Use
┌─────────────────────────────────────┐
│ Use in Column/Row │
├─────────────────────────────────────┤
│ Column( │
│ children: [ │
│ Text('A'), │
│ Gaps.v16, ← Vertical gap │
│ Text('B'), │
│ ], │
│ ) │
│ │
│ Row( │
│ children: [ │
│ Text('X'), │
│ Gaps.h16, ← Horizontal gap │
│ Text('Y'), │
│ ], │
│ ) │
└─────────────────────────────────────┘

💡 왜 Gaps 시스템이 필요한가?

1. 코드 간결성 (Simplicity)

1
2
3
4
5
6
7
// ❌ 길고 복잡함
SizedBox(height: Sizes.size16)
SizedBox(width: Sizes.size20)

// ✅ 짧고 명확함
Gaps.v16
Gaps.h20

2. 타입 안정성 (Type Safety)

1
2
3
4
5
6
// ❌ 실수 가능
SizedBox(width: Sizes.size16) // Column에서는 height를 써야 하는데!

// ✅ 명확한 의도
Gaps.v16 // v = vertical, 수직이구나!
Gaps.h16 // h = horizontal, 수평이구나!

3. 성능 최적화 (Performance)

1
2
3
4
5
// const SizedBox는 한 번만 생성됨
static const v16 = SizedBox(height: Sizes.size16);

// 매번 같은 Gaps.v16을 사용해도
// 메모리에는 한 개만 존재!

4. 가독성 향상 (Readability)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// ❌ 뭘 하는 건지 파악이 어려움
Column(
children: [
Text('A'),
SizedBox(height: 16),
Text('B'),
SizedBox(height: 16),
],
)

// ✅ 의도가 명확함
Column(
children: [
Text('A'),
Gaps.v16, // 16px 수직 간격
Text('B'),
Gaps.v16, // 16px 수직 간격
],
)

🔄 main.dart에서 Gaps 사용하기

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
import 'package:flutter/material.dart';
import 'package:tiktok/constants/sizes.dart'; // Sizes import

void main() {
runApp(const TikTokApp());
}

class TikTokApp extends StatelessWidget {
const TikTokApp({super.key});

@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'TikTok Clone',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFFE9435A)),
useMaterial3: false,
),
home: Padding(
padding: const EdgeInsets.all(Sizes.size14), // Sizes 사용!
child: Container(),
),
);
}
}

변경 사항:

  1. import 'package:tiktok/constants/sizes.dart'; 추가
  2. EdgeInsets.all(Sizes.size14) 사용
  3. 숫자 14.0 대신 Sizes.size14 사용

✅ 체크리스트

  • Gaps 클래스가 생성되었는가?
  • Vertical Gaps (v1~v96)가 모두 정의되었는가?
  • Horizontal Gaps (h1~h96)가 모두 정의되었는가?
  • Sizes.dart를 import했는가?
  • static const SizedBox로 정의되었는가?
  • v는 height, h는 width를 사용하는가?

🔑 핵심 개념 정리

개념 설명 예시
SizedBox 빈 공간을 만드는 위젯 SizedBox(height: 16)
Vertical Gap 위아래 간격 (Column) Gaps.v16
Horizontal Gap 좌우 간격 (Row) Gaps.h16
const 위젯 한 번만 생성되는 위젯 메모리 절약, 성능 향상
재사용성 Sizes 상수 재활용 일관성 유지

💡 실습 과제

  1. Column에서 Gaps 사용하기

    1
    2
    3
    4
    5
    6
    7
    8
    9
    Column(
    children: [
    Text('첫 줄'),
    Gaps.v16,
    Text('둘째 줄'),
    Gaps.v24,
    Text('셋째 줄'),
    ],
    )
  2. Row에서 Gaps 사용하기

    1
    2
    3
    4
    5
    6
    7
    8
    9
    Row(
    children: [
    Icon(Icons.star),
    Gaps.h8,
    Text('별점'),
    Gaps.h16,
    Text('5.0'),
    ],
    )
  3. 다양한 크기 실험하기

    • Gaps.v8과 Gaps.v16의 차이를 확인해보세요
    • Gaps.h24와 Gaps.h32를 비교해보세요

❓ 자주 묻는 질문

Q1: v와 h를 헷갈리면 어떻게 하나요?

A: v = vertical (수직, ↕), h = horizontal (수평, ↔)으로 기억하세요. Column은 세로 배치이므로 v를, Row는 가로 배치이므로 h를 사용합니다.

Q2: SizedBox 대신 Padding을 쓰면 안되나요?

A: Padding은 위젯을 감싸는 용도이고, SizedBox는 순수하게 빈 공간을 만듭니다. 간격을 만들 때는 SizedBox가 더 적합합니다.

Q3: 왜 모든 크기를 다 만드나요? 필요한 것만 만들면 안될까요?

A: 미리 다 만들어두면 나중에 일일이 추가할 필요가 없어 편리합니다. 사용하지 않는 것은 최적화 과정에서 제거됩니다.

Q4: const를 빼면 어떻게 되나요?

A: 작동은 하지만 매번 새로운 SizedBox가 생성되어 메모리를 낭비하고 성능이 떨어집니다.


FVM 버전 관리 설정

📌 학습 목표

  • FVM(Flutter Version Management)의 개념을 이해합니다
  • 프로젝트별 Flutter 버전 관리 방법을 배웁니다
  • .fvmrc 설정 파일의 역할을 이해합니다
  • Flutter 버전 업데이트 과정을 학습합니다

📂 관련 파일

1
2
3
4
5
6
7
8
9
10
프로젝트 루트/
├── .fvmrc # FVM 버전 설정
├── .fvm/
│ ├── fvm_config.json # FVM 상세 설정
│ ├── flutter_sdk -> ... # Flutter SDK 심볼릭 링크
│ ├── version # 버전 번호
│ └── release # 릴리즈 정보
├── .gitignore # FVM 관련 ignore 추가
└── .vscode/
└── settings.json # VSCode FVM 설정

💻 설정 파일 내용

1. .fvmrc

1
2
3
{
"flutter": "3.32.8"
}

역할:

  • 프로젝트에서 사용할 Flutter 버전을 지정합니다
  • FVM이 이 파일을 읽고 해당 버전을 활성화합니다
  • 팀원 모두가 같은 Flutter 버전을 사용하게 합니다

2. .fvm/fvm_config.json

1
2
3
{
"flutterSdkVersion": "3.32.8"
}

역할:

  • FVM 내부 설정 파일입니다
  • 실제 사용 중인 Flutter SDK 버전 정보를 저장합니다
  • .fvmrc와 동기화됩니다

3. .gitignore 추가

1
2
3
4
# FVM Version Cache
.fvm/flutter_sdk

# 다른 기존 내용들...

왜 flutter_sdk를 ignore하나요?

  • flutter_sdk는 실제 Flutter SDK로의 심볼릭 링크입니다
  • 각 개발자의 컴퓨터마다 경로가 다릅니다
  • Git에 포함하지 않고 FVM이 자동으로 생성하게 합니다

4. .vscode/settings.json 추가

1
2
3
4
5
6
7
8
9
{
"dart.flutterSdkPath": ".fvm/flutter_sdk",
"search.exclude": {
"**/.fvm": true
},
"files.watcherExclude": {
"**/.fvm": true
}
}

각 설정의 의미:

  • dart.flutterSdkPath: VSCode가 FVM의 Flutter SDK를 사용하도록 지정
  • search.exclude: .fvm 폴더를 검색에서 제외
  • files.watcherExclude: .fvm 폴더 변경 감지 제외 (성능 향상)

📖 FVM 상세 설명

1. FVM이란?

1
2
3
4
FVM (Flutter Version Management)
= Flutter 버전 관리 도구

프로젝트마다 다른 Flutter 버전을 사용할 수 있게 해줍니다!

왜 필요한가?

1
2
3
4
5
6
7
8
프로젝트 A → Flutter 3.32.8 필요
프로젝트 B → Flutter 3.10.0 필요

FVM 없이:
Flutter 버전 변경할 때마다 재설치 필요 😱

FVM 사용:
프로젝트 폴더 이동하면 자동으로 버전 전환! 😊

2. FVM 작동 원리

1
2
3
4
5
6
7
8
9
10
11
12
13
1. FVM이 여러 Flutter 버전을 저장
~/fvm/versions/
├── 3.32.8/
├── 3.10.0/
└── ...

2. 프로젝트에서 .fvmrc 읽기
{ "flutter": "3.32.8" }

3. 해당 버전으로 심볼릭 링크 생성
.fvm/flutter_sdk → ~/fvm/versions/3.32.8

4. IDE와 터미널이 링크된 버전 사용

3. 커밋 변경 사항 분석

변경된 파일 7개:

  1. .fvmrc: Flutter 버전을 3.32.8로 지정
  2. .gitignore: FVM 캐시 ignore 추가
  3. .vscode/settings.json: VSCode FVM 설정
  4. ios/…/Runner.xcscheme: iOS 빌드 설정 업데이트
  5. ios/Runner/AppDelegate.swift: iOS 앱 델리게이트 업데이트
  6. pubspec.lock: 종속성 버전 업데이트
  7. pubspec.yaml: SDK 버전 범위 업데이트

pubspec.yaml 변경:

1
2
environment:
sdk: '>=3.3.0 <4.0.0' # Dart SDK 버전 범위

🎨 시각적 다이어그램

FVM 버전 관리 흐름

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
┌────────────────────────────────────────┐
│ Developer's Computer │
├────────────────────────────────────────┤
│ │
│ ~/fvm/versions/ │
│ ┌──────────────────────────────┐ │
│ │ 3.32.8/ (Flutter SDK) │ │
│ │ 3.10.0/ (Flutter SDK) │ │
│ │ 3.0.0/ (Flutter SDK) │ │
│ └──────────────────────────────┘ │
│ ↑ │
│ │ Managed by FVM │
│ ↓ │
│ ┌──────────────────────────────┐ │
│ │ Project/.fvm/ │ │
│ │ ┌─────────────────────┐ │ │
│ │ │ flutter_sdk │ │ │
│ │ │ ↓ (Symbolic Link) │ │ │
│ │ │ ~/fvm/versions/ │ │ │
│ │ │ 3.32.8 │ │ │
│ │ └─────────────────────┘ │ │
│ └──────────────────────────────┘ │
│ ↓ │
│ ┌──────────────────────────────┐ │
│ │ VSCode / Terminal │ │
│ │ Using version 3.32.8! │ │
│ └──────────────────────────────┘ │
└────────────────────────────────────────┘

버전 전환 과정

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Step 1: 프로젝트 폴더 진입
$ cd tiktok_project

Step 2: FVM이 .fvmrc 파일 읽기
{ "flutter": "3.32.8" }

Step 3: 해당 버전이 설치되어 있는지 확인
~/fvm/versions/3.32.8 ✅ 있음

Step 4: 심볼릭 링크 생성/업데이트
.fvm/flutter_sdk → ~/fvm/versions/3.32.8

Step 5: 개발 도구들이 링크된 버전 사용
VSCode, Android Studio, 터미널 모두 3.32.8 사용!

💡 FVM 사용법

1. FVM 설치 (최초 1회)

1
2
3
# macOS/Linux
brew tap leoafarias/fvm
brew install fvm

2. Flutter 버전 설치

1
2
3
4
5
# 특정 버전 설치
fvm install 3.32.8

# 최신 stable 버전 설치
fvm install stable

3. 프로젝트에 버전 설정

1
2
3
4
# 프로젝트 폴더에서
fvm use 3.32.8

# .fvmrc 파일이 자동 생성됩니다

4. FVM Flutter 명령어 실행

1
2
3
4
5
6
7
8
# FVM을 통해 Flutter 명령어 실행
fvm flutter --version
fvm flutter pub get
fvm flutter run

# 또는 global 설정
fvm global 3.32.8
flutter --version # 이제 3.32.8 사용

🔧 팀 협업 시나리오

상황: 새 팀원이 프로젝트 clone

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 1. 프로젝트 clone
git clone <repository>
cd tiktok_project

# 2. FVM 설치 확인
fvm --version

# 3. .fvmrc 읽고 Flutter 설치
fvm install
# → 자동으로 3.32.8 설치

# 4. 종속성 설치
fvm flutter pub get

# 5. 실행
fvm flutter run

팀원 모두 같은 Flutter 버전 사용! ✅

✅ 체크리스트

  • FVM이 설치되어 있는가?
  • .fvmrc 파일이 생성되었는가?
  • Flutter 버전이 3.32.8로 설정되었는가?
  • .gitignore에 .fvm/flutter_sdk가 추가되었는가?
  • VSCode settings.json이 설정되었는가?
  • fvm flutter --version으로 버전 확인이 되는가?

🔑 핵심 개념 정리

개념 설명 예시
FVM Flutter 버전 관리 도구 프로젝트별 다른 Flutter 버전
.fvmrc 프로젝트 Flutter 버전 지정 {"flutter": "3.32.8"}
심볼릭 링크 다른 위치를 가리키는 링크 .fvm/flutter_sdk → ~/fvm/versions/3.32.8
버전 일관성 팀원 모두 같은 버전 사용 환경 차이로 인한 버그 방지

💡 실습 과제

  1. 현재 Flutter 버전 확인

    1
    fvm flutter --version
  2. 설치된 FVM 버전 목록 보기

    1
    fvm list
  3. 프로젝트 Flutter 버전 확인

    1
    cat .fvmrc
  4. 다른 버전 설치해보기

    1
    2
    fvm install 3.10.0
    # 여러 버전 관리 가능!

❓ 자주 묻는 질문

Q1: FVM 없이 개발하면 안되나요?

A: 가능하지만, 팀원마다 다른 Flutter 버전을 사용하면 “내 컴퓨터에서는 되는데?” 같은 문제가 발생합니다. FVM으로 버전을 통일하는 것이 좋습니다.

Q2: .fvm 폴더를 Git에 포함해야 하나요?

A: .fvmrc와 fvm_config.json만 포함하고, flutter_sdk는 제외합니다. 각 개발자가 FVM으로 자동 생성하게 합니다.

Q3: VSCode가 올바른 Flutter SDK를 못 찾으면?

A: VSCode를 재시작하거나 Command Palette(Cmd+Shift+P)에서 “Reload Window”를 실행하세요.

Q4: Flutter 버전을 업데이트하려면?

A:

1
2
fvm install 3.33.0    # 새 버전 설치
fvm use 3.33.0 # 프로젝트에 적용

Q5: fvm flutter와 flutter 명령어 차이는?

A: fvm flutter는 프로젝트에 설정된 버전을 사용하고, flutter는 시스템 전역 버전을 사용합니다. FVM 프로젝트에서는 항상 fvm flutter를 사용하세요.


🎓 전체 복습 및 요약

📚 배운 내용 정리

이번 강의에서 배운 4가지 핵심 주제:

  1. Flutter 프로젝트 생성

    • MaterialApp 기본 구조
    • 테마와 색상 설정
    • 프로젝트 파일 구조
  2. Sizes 상수 시스템

    • static const를 사용한 크기 관리
    • 하드코딩 방지
    • 일관된 디자인 시스템
  3. Gaps 위젯 시스템

    • SizedBox를 활용한 간격 관리
    • Vertical/Horizontal 구분
    • 재사용 가능한 위젯
  4. FVM 버전 관리

    • 프로젝트별 Flutter 버전 관리
    • 팀 협업 환경 구축
    • 버전 일관성 유지

🔗 개념 연결 맵

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
    ┌───────────────────────────────────┐
│ Initial Flutter Project Setup │
└───────────────────────────────────┘
↓
┌─────────────┴──────────────┐
↓ ↓
┌──────────────────┐ ┌────────────────────┐
│ Project Creation │ │ Version Management │
│ MaterialApp │ │ FVM │
└──────────────────┘ └────────────────────┘
↓ ↓
↓ Same environment for all team members
↓
┌──────────────────┐
│ Design System │
└──────────────────┘
↓
┌───┴───┐
↓ ↓
┌───────┐ ┌────────┐
│ Sizes │ │ Gaps │
└───────┘ └────────┘
↓ ↓
Consistent Reusable
Sizes Spacing

💪 실전 프로젝트 시작하기

이제 배운 내용을 바탕으로 실전 코드를 작성할 준비가 되었습니다!

다음 단계:

  1. Sizes와 Gaps를 활용한 UI 구성
  2. 실제 화면 만들기 (SignUpScreen, LoginScreen 등)
  3. 네비게이션과 라우팅
  4. 상태 관리

🎯 최종 체크리스트

프로젝트 기본 설정:

  • Flutter 프로젝트가 생성되었다
  • MaterialApp이 설정되었다
  • 테마 색상이 적용되었다

디자인 시스템:

  • Sizes 클래스가 완성되었다
  • Gaps 클래스가 완성되었다
  • main.dart에서 Sizes를 사용하고 있다

개발 환경:

  • FVM이 설치되었다
  • Flutter 3.32.8이 설정되었다
  • VSCode가 FVM을 인식한다
  • 팀원도 같은 환경을 구축할 수 있다

📝 핵심 코드 패턴 정리

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
31
32
// 1. 크기 사용
Container(
width: Sizes.size64,
height: Sizes.size48,
padding: EdgeInsets.all(Sizes.size16),
)

// 2. 간격 사용
Column(
children: [
Text('첫 번째'),
Gaps.v16,
Text('두 번째'),
],
)

Row(
children: [
Icon(Icons.star),
Gaps.h8,
Text('별점'),
],
)

// 3. 테마 색상 사용
MaterialApp(
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: const Color(0xFFE9435A),
),
),
)

📚 부록

A. 명령어 정리

Flutter 명령어:

1
2
3
4
flutter create tiktok          # 프로젝트 생성
flutter pub get # 패키지 설치
flutter run # 앱 실행
flutter --version # 버전 확인

FVM 명령어:

1
2
3
4
5
fvm install 3.32.8             # Flutter 버전 설치
fvm use 3.32.8 # 프로젝트에 버전 설정
fvm list # 설치된 버전 목록
fvm flutter run # FVM Flutter로 실행
fvm global 3.32.8 # 전역 버전 설정

Git 명령어:

1
2
3
git log --oneline              # 커밋 히스토리 확인
git show <commit_hash> # 커밋 내용 보기
git diff # 변경 사항 확인

B. 트러블슈팅

문제 1: FVM 명령어를 찾을 수 없음

1
2
3
# PATH에 dart pub global 경로 추가
export PATH="$PATH":"$HOME/.pub-cache/bin"
# 또는 .zshrc / .bashrc에 영구 추가

문제 2: VSCode가 Flutter SDK를 찾지 못함

1
2
3
4
5
// .vscode/settings.json 확인
{
"dart.flutterSdkPath": ".fvm/flutter_sdk"
}
// VSCode 재시작

문제 3: Sizes/Gaps를 찾을 수 없음

1
2
3
// import 확인
import 'package:tiktok/constants/sizes.dart';
import 'package:tiktok/constants/gaps.dart';

C. 유용한 VSCode 확장

  1. Flutter - Flutter 개발 필수
  2. Dart - Dart 언어 지원
  3. Pubspec Assist - 패키지 추가 도우미
  4. Error Lens - 에러 인라인 표시
  5. GitLens - Git 기능 강화

D. 참고 자료