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.0Sizes.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. 참고 자료