├── README.md
├── src
├── content
│ └── docs
│ │ ├── part9
│ │ ├── monorepo.md
│ │ └── folder-structure.mdx
│ │ ├── part1
│ │ ├── changelog.md
│ │ ├── introduction.md
│ │ ├── llms.mdx
│ │ ├── setup.md
│ │ ├── first-project.mdx
│ │ └── project-structure.mdx
│ │ ├── index.mdx
│ │ ├── part8
│ │ ├── build-modes.md
│ │ ├── deploy-procedure.md
│ │ ├── cicd-codemagic.md
│ │ └── error-tracking.md
│ │ ├── part4
│ │ └── state-management-intro.md
│ │ ├── part2
│ │ ├── dart-intro.md
│ │ ├── basic-syntax.md
│ │ ├── type-system.md
│ │ └── records.md
│ │ ├── part3
│ │ └── widgets.md
│ │ ├── appendix
│ │ ├── tools.md
│ │ └── error-handling.md
│ │ ├── part5
│ │ └── advanced-routing.md
│ │ ├── part10
│ │ └── custom-painting.md
│ │ └── part7
│ │ ├── widget-test.md
│ │ └── integration-test.md
├── assets
│ └── houston.webp
├── content.config.ts
└── styles
│ └── custom.css
├── .vscode
├── extensions.json
├── launch.json
└── settings.json
├── tsconfig.json
├── .gitignore
├── public
└── favicon.svg
├── package.json
├── add-mermaid-classname.ts
├── .github
└── workflows
│ └── deploy.yml
├── SUMMARY.md
├── astro.config.mjs
├── sidebar.config.mjs
└── llm.txt
/README.md:
--------------------------------------------------------------------------------
1 | # Flutter 배우기
2 |
3 | 안녕하세요 :)
--------------------------------------------------------------------------------
/src/content/docs/part9/monorepo.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: melos를 이용한 모노레포
3 | ---
4 |
--------------------------------------------------------------------------------
/src/assets/houston.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChangJoo-Park/learn-flutter/HEAD/src/assets/houston.webp
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": ["astro-build.astro-vscode"],
3 | "unwantedRecommendations": []
4 | }
5 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "astro/tsconfigs/strict",
3 | "include": [
4 | ".astro/types.d.ts",
5 | "**/*"
6 | ],
7 | "exclude": [
8 | "dist",
9 | "node_modules"
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | "command": "./node_modules/.bin/astro dev",
6 | "name": "Development server",
7 | "request": "launch",
8 | "type": "node-terminal"
9 | }
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/src/content.config.ts:
--------------------------------------------------------------------------------
1 | import { defineCollection } from 'astro:content';
2 | import { docsLoader } from '@astrojs/starlight/loaders';
3 | import { docsSchema } from '@astrojs/starlight/schema';
4 |
5 | export const collections = {
6 | docs: defineCollection({ loader: docsLoader(), schema: docsSchema() }),
7 | };
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # build output
2 | dist/
3 | # generated types
4 | .astro/
5 |
6 | # dependencies
7 | node_modules/
8 |
9 | # logs
10 | npm-debug.log*
11 | yarn-debug.log*
12 | yarn-error.log*
13 | pnpm-debug.log*
14 |
15 |
16 | # environment variables
17 | .env
18 | .env.production
19 |
20 | # macOS-specific files
21 | .DS_Store
22 |
23 | public/beoe
24 |
--------------------------------------------------------------------------------
/src/content/docs/part1/changelog.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 변경사항
3 | ---
4 |
5 |
6 | ### 2025년 05월 16일
7 |
8 | - 📦 1. 시작하기 > 소개에 설문조사 추가
9 |
10 | ### 2025년 05월 15일
11 |
12 | - 📦 1. 시작하기에 **LLM 설정** 추가
13 | - 📦 1. 시작하기에 **변경사항** 추가
14 | - 💡 2. Dart 언어 기초 중 컬렉션과 반복문에 collection 패키지 안내 추가
15 | - `flutter pub run build_runner build` 를 `dart run build_runner build` 로 변경
16 | - JSON 직렬화 (freezed, json_serializable)에 freezed_annotation 팁 추가
17 |
18 | ### 2025년 05월 14일
19 |
20 | 초안 공개
21 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "dart.flutterSdkPath": "/Users/changjoopark/.local/share/mise/installs/flutter/3.29.3-stable",
3 | "debug.javascript.defaultRuntimeExecutable": {
4 | "pwa-node": "/Users/changjoopark/.local/share/mise/shims/node"
5 | },
6 | "typescript.validate.enable": true,
7 | "javascript.validate.enable": true,
8 | "files.exclude": {
9 | "**/node_modules/**/*": true
10 | },
11 | "search.exclude": {
12 | "**/node_modules/**/*": true
13 | },
14 | "typescript.disableAutomaticTypeAcquisition": true,
15 | "typescript.tsserver.experimental.enableProjectDiagnostics": false,
16 | "typescript.tsserver.enableTracing": true
17 | }
18 |
--------------------------------------------------------------------------------
/public/favicon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/styles/custom.css:
--------------------------------------------------------------------------------
1 | @import url("https://statics.goorm.io/fonts/GoormSans/v1.0.0/GoormSans.min.css");
2 | @import url("https://statics.goorm.io/fonts/GoormSansCode/v1.0.1/GoormSansCode.min.css");
3 |
4 |
5 | :root {
6 | --sl-font-system: 'Goorm Sans', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI',
7 | Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji',
8 | 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
9 | --sl-font-system-mono: 'Goorm Sans Code', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono',
10 | 'Courier New', monospace;
11 | --__sl-font: var(--sl-font, var(--sl-font-system)), var(--sl-font-system);
12 | --__sl-font-mono: var(--sl-font-mono, var(--sl-font-system-mono)), var(--sl-font-system-mono);
13 | }
14 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "flutter-in-korean",
3 | "type": "module",
4 | "version": "0.0.1",
5 | "scripts": {
6 | "dev": "astro dev",
7 | "start": "astro dev",
8 | "build": "astro build",
9 | "preview": "astro preview",
10 | "upgrade": "npx @astrojs/upgrade",
11 | "astro": "astro"
12 | },
13 | "dependencies": {
14 | "@astrojs/sitemap": "^3.4.0",
15 | "@astrojs/starlight": "^0.34.3",
16 | "@beoe/cache": "^0.1.1",
17 | "@beoe/rehype-mermaid": "^0.4.2",
18 | "@expressive-code/plugin-line-numbers": "^0.41.2",
19 | "astro": "^5.7.12",
20 | "astro-expressive-code": "^0.41.2",
21 | "playwright": "^1.52.0",
22 | "rehype-mermaid": "^3.0.0",
23 | "sharp": "^0.32.5",
24 | "starlight-giscus": "^0.6.1",
25 | "starlight-llms-txt": "^0.5.1"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/content/docs/index.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Flutter 앱 개발 가이드
3 | description: 모바일 및 태블릿 앱을 만들며 겪었던 문제를 공유합니다.
4 | template: splash
5 | hero:
6 | tagline: 모바일 및 태블릿 앱을 만들며 겪었던 문제를 공유합니다.
7 | image:
8 | file: ../../assets/houston.webp
9 | actions:
10 | - text: 시작하기
11 | link: /learn-flutter/part1/introduction/
12 | icon: right-arrow
13 | ---
14 |
15 | import { Card, CardGrid } from '@astrojs/starlight/components';
16 |
17 | ## Next steps
18 |
19 |
20 |
21 | Edit `src/content/docs/index.mdx` to see this page change.
22 |
23 |
24 | Add Markdown or MDX files to `src/content/docs` to create new pages.
25 |
26 |
27 | Edit your `sidebar` and other config in `astro.config.mjs`.
28 |
29 |
30 | Learn more in [the Starlight Docs](https://starlight.astro.build/).
31 |
32 |
33 |
--------------------------------------------------------------------------------
/add-mermaid-classname.ts:
--------------------------------------------------------------------------------
1 | import { visit, CONTINUE } from "unist-util-visit"
2 | import type { Plugin } from 'unified';
3 | import type { Root, Element } from 'hast';
4 |
5 | const visitor = (node: any) => {
6 | const dataLanguageMermaid = "mermaid"
7 | const typeElement = "element"
8 | const tagNamePre = "pre"
9 | const classMermaid = dataLanguageMermaid
10 |
11 | const isPreElement = (node: any) => typeof node.type !== undefined && node.type === typeElement
12 | && node.tagName !== undefined && node.tagName === tagNamePre
13 | && node.properties !== undefined && node.properties.dataLanguage === dataLanguageMermaid
14 |
15 | if(!isPreElement(node)) {
16 | return CONTINUE
17 | }
18 |
19 | const element = node as Element
20 | const properties = element.properties
21 | const className = properties.className as Array
22 | properties.className = [...className, classMermaid]
23 |
24 | return CONTINUE
25 | }
26 |
27 | const addMermaidClass: Plugin = () =>
28 | (ast: Root) => visit(ast, visitor)
29 |
30 | export default addMermaidClass
31 |
--------------------------------------------------------------------------------
/src/content/docs/part1/introduction.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 소개
3 | ---
4 |
5 | 안녕하세요!
6 |
7 | 이 문서는 Flutter 프레임워크를 이용하여 앱을 개발하는 방법을 소개합니다.
8 |
9 | 현재 초안 단계이며 수정 및 추가 내용이 있을 예정입니다.
10 |
11 | 저희 팀은 4개 이상의 프로젝트를 Flutter 프레임워크를 이용하여 만들었습니다.
12 | 처음부터 Flutter로 작성한 앱과 기존의 네이티브로 개발한 앱을 Flutter로 마이그레이션 한 경험이 있습니다.
13 |
14 | iOS(+ iPadOS), Android(+ 태블릿)에서 동작하는 여러 앱 서비스를 Flutter로 만들어 운영중이며 약 100만 MAU를 가진 서비스를 안정적으로 만들어 배포하였습니다.
15 |
16 | 위 경험을 토대로 Flutter 프레임워크를 이용하여 앱 개발하면서 필요한 내용들을 본 문서에 담았습니다.
17 |
18 | 이 문서 혹은 개발과 관련된 문의는 댓글 혹은 changjoo.app@gmail.com 으로 문의바랍니다.
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/.github/workflows/deploy.yml:
--------------------------------------------------------------------------------
1 | name: Deploy to GitHub Pages
2 |
3 | on:
4 | # Trigger the workflow every time you push to the `main` branch
5 | # Using a different branch name? Replace `main` with your branch’s name
6 | push:
7 | branches: [ main ]
8 | # Allows you to run this workflow manually from the Actions tab on GitHub.
9 | workflow_dispatch:
10 |
11 | # Allow this job to clone the repo and create a page deployment
12 | permissions:
13 | contents: read
14 | pages: write
15 | id-token: write
16 |
17 | jobs:
18 | build:
19 | runs-on: ubuntu-latest
20 | steps:
21 | - name: Checkout your repository using git
22 | uses: actions/checkout@v4
23 | - name: Install, build, and upload your site
24 | uses: withastro/action@v3
25 | # with:
26 | # path: . # The root location of your Astro project inside the repository. (optional)
27 | # node-version: 20 # The specific version of Node that should be used to build your site. Defaults to 20. (optional)
28 | # package-manager: pnpm@latest # The Node package manager that should be used to install dependencies and build your site. Automatically detected based on your lockfile. (optional)
29 |
30 | deploy:
31 | needs: build
32 | runs-on: ubuntu-latest
33 | environment:
34 | name: github-pages
35 | url: ${{ steps.deployment.outputs.page_url }}
36 | steps:
37 | - name: Deploy to GitHub Pages
38 | id: deployment
39 | uses: actions/deploy-pages@v4
40 |
--------------------------------------------------------------------------------
/src/content/docs/part1/llms.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: LLM 설정
3 | ---
4 |
5 | 이 문서는 코드 에디터를 위한 llms.txt 파일을 지원합니다.
6 |
7 | 다음 경로를 사용하는 에디터에 적용하시면 이 문서를 기준으로 프로젝트를 진행하실 수 있습니다.
8 |
9 |
10 | 루트 llms.txt
11 |
12 | ```
13 | https://changjoo-park.github.io/learn-flutter/llms.txt
14 | ```
15 |
16 |
17 | 간소화 버전
18 |
19 | ```
20 | https://changjoo-park.github.io/learn-flutter/llms-small.txt
21 | ```
22 |
23 | 전체 버전
24 |
25 | ```
26 | https://changjoo-park.github.io/learn-flutter/llms-full.txt
27 | ```
28 |
29 |
30 |
31 | ## Visual Studio Code
32 |
33 | 프롬프트에 다음과 같이 입력합니다.
34 |
35 | ```text
36 | #fetch https://changjoo-park.github.io/learn-flutter/llms-full.txt
37 | ```
38 |
39 | 프로젝트 레벨에서 이용하려면
40 |
41 | 1. 터미널에서 프로젝트 경로로 이동 후 다음과 같이 입력합니다.
42 |
43 | ```sh
44 | curl -L https://changjoo-park.github.io/learn-flutter/llms-full.txt --create-dirs -o .vscode/learn_flutter.md
45 | ```
46 |
47 | 2. `.vscode/settings.json` 에 다음을 입력합니다.
48 |
49 | ```json title=".vscode/settings.json"
50 | {
51 | "github.copilot.chat.codeGeneration.instructions": [
52 | {
53 | "file": "./.vscode/learn_flutter.md"
54 | }
55 | ]
56 | }
57 | ```
58 |
59 |
60 |
61 | ## Cursor
62 |
63 | 프롬프트에 다음과 같이 입력합니다.
64 |
65 | ```text
66 | @web https://changjoo-park.github.io/learn-flutter/llms-full.txt
67 | ```
68 |
69 | 계속 사용하려면
70 |
71 | 1. `CMD` + `Shift` + `P` 를 눌러 명령 팔레트를 엽니다.
72 | 2. `Add new custom docs` 를 입력합니다.
73 | 3. 아래 내용을 입력합니다.
74 |
75 | ```text
76 | https://changjoo-park.github.io/learn-flutter/llms-full.txt
77 | ```
78 |
79 | 4. 채팅 UI에서 @docs 를 입력한 후 추가된 문서를 선택합니다.
80 |
--------------------------------------------------------------------------------
/SUMMARY.md:
--------------------------------------------------------------------------------
1 | # Flutter 온보딩 핸드북 목차
2 |
3 | ## 📦 Part 1. 시작하기: 환경 설정과 첫 프로젝트
4 |
5 | - Flutter 소개 및 특징
6 | - 개발 환경 구성
7 | - Flutter SDK 설치
8 | - Visual Studio Code 설정
9 | - 에뮬레이터 / 실기기 연결
10 | - 첫 프로젝트 생성 및 실행
11 | - Flutter 프로젝트 구조 이해
12 |
13 | ## 💡 Part 2. Dart 언어 기초
14 |
15 | - Dart 소개
16 | - 기본 문법 및 변수
17 | - 타입 시스템 & 제네릭
18 | - 클래스, 생성자, 팩토리
19 | - 비동기 프로그래밍 (Future, async/await, Stream)
20 | - 컬렉션과 반복문
21 | - 예외 처리
22 | - Extension / Mixin
23 | - 레코드 & 패턴매칭 (Dart 3 이상)
24 |
25 | ## 🧱 Part 3. Flutter의 기본 구성 요소
26 |
27 | ### 위젯 개념과 주요 위젯
28 |
29 | - Stateless / Stateful 위젯
30 | - Widget Tree 이해
31 | - 주요 위젯
32 | - Text, Button, Image, Icon
33 | - Container, SizedBox, Padding
34 | - TextField, Form, GestureDetector, InkWell
35 | - Visibility, Offstage, Divider, Tooltip
36 |
37 | ### 레이아웃 위젯
38 |
39 | - Row, Column, Flex
40 | - Stack, Align, Positioned
41 | - Expanded, Flexible, Spacer
42 | - SingleChildScrollView, Wrap
43 | - AspectRatio, LayoutBuilder
44 | - OrientationBuilder, MediaQuery
45 | - ConstrainedBox, IntrinsicHeight 등
46 |
47 | ## 🎨 Part 4. 상태 관리
48 |
49 | - 상태 관리 입문
50 | - setState, ValueNotifier
51 | - InheritedWidget, Provider
52 | - Riverpod 소개 및 실습
53 | - 실습: TodoList 개선 (상태 관리 포함)
54 |
55 | ## 🚦 Part 5. 네비게이션과 화면 구성
56 |
57 | - Navigator 1.0 (push/pop)
58 | - Navigator 2.0 개념
59 | - go_router 사용법
60 | - 라우트 가드, ShellRoute, DeepLink
61 | - 실습: 복수 화면 전환 및 데이터 전달
62 | - Drawer, BottomNavigationBar, TabBar
63 |
64 | ## 🔌 Part 6. 외부와의 연동 (서버 & Firebase)
65 |
66 | - Dio를 통한 API 통신
67 | - Interceptor, cancelToken, 오류 처리
68 | - JSON 직렬화 (`json_serializable`, `freezed`)
69 | - Firebase 연동
70 | - 초기 설정
71 | - Firebase Cloud Messaging
72 | - Firebase Auth & Firestore (간단히)
73 | - Firebase Analytics / Crashlytics
74 |
75 | ## 🧪 Part 7. 테스트와 디버깅
76 |
77 | - 단위 테스트 (unit)
78 | - 위젯 테스트 (widget)
79 | - 통합 테스트 (integration)
80 | - mockito, golden test, coverage
81 | - Flutter DevTools 사용법
82 | - 로그 관리 (talker)
83 |
84 | ## 🚀 Part 8. 앱 배포 및 운영
85 |
86 | - 빌드 모드 (debug / profile / release)
87 | - Android / iOS 배포 절차
88 | - keystore, signing, TestFlight
89 | - Codemagic을 활용한 CI/CD 구성
90 | - 환경 분리 및 flavor 설정
91 | - 사용자 분석 도구
92 | - Firebase Analytics
93 | - Posthog
94 | - 에러 추적
95 | - Crashlytics, Sentry
96 |
97 | ## 🧭 Part 9. 프로젝트 구조 & 아키텍처
98 |
99 | - 기능별 vs 계층별 폴더 구조
100 | - 클린 아키텍처 도입하기
101 | - 의존성 주입 개념
102 | - 패키지 작성 및 관리
103 | - pub.dev 탐색 / dev_dependencies 구분
104 | - internal 패키지 분리 전략
105 | - 모노레포 구조 및 melos 도입
106 | - 사내 Flutter 코드 스타일 가이드
107 |
108 | ## 🌍 Part 10. 보완 학습: 확장성과 품질
109 |
110 | - CustomPainter와 RenderBox 이해
111 | - 위젯 캐싱
112 | - RepaintBoundary
113 | - 애니메이션 구성 (Hero, AnimatedXXX)
114 | - 접근성 (Semantics 등)
115 | - 다국어 처리 (intl, flutter_localizations)
116 | - 퍼포먼스 튜닝 체크리스트
117 | - 추천 패키지 모음
118 |
119 | ## 📚 부록
120 |
121 | - 개발 도구와 링크 모음
122 | - 공식 문서, 블로그, 영상 추천
123 | - Flutter 오류 대응법 가이드
124 | - 코드 템플릿 및 예제 모음 링크
125 | - 자주 묻는 질문 (FAQ)
126 | - 소셜 로그인
127 | - 네이버 로그인
128 | - 카카오 로그인
129 | - 애플 로그인
130 | - iOS 라이브 액티비티
131 | - WidgetBook 문서화
132 | - 주석
133 | - 코드 스타일
134 | - llms.txt
135 |
--------------------------------------------------------------------------------
/astro.config.mjs:
--------------------------------------------------------------------------------
1 | import { rehypeMermaid } from "@beoe/rehype-mermaid";
2 | import { getCache } from "@beoe/cache";
3 | import starlightGiscus from 'starlight-giscus'
4 |
5 | const googleAnalyticsId = 'G-FTYYK8J5MY'
6 |
7 | const cache = await getCache();
8 |
9 | // @ts-check
10 | import { defineConfig } from "astro/config";
11 | import sitemap from "@astrojs/sitemap";
12 | import starlight from "@astrojs/starlight";
13 | import astroExpressiveCode from "astro-expressive-code";
14 | import { pluginLineNumbers } from "@expressive-code/plugin-line-numbers";
15 | import { sidebars } from "./sidebar.config.mjs";
16 | import starlightLlmsTxt from 'starlight-llms-txt'
17 |
18 |
19 | // https://astro.build/config
20 | export default defineConfig({
21 | compressHTML: true,
22 | prefetch: false,
23 | base: "learn-flutter",
24 | site: "https://changjoo-park.github.io/",
25 | integrations: [
26 | sitemap(),
27 | astroExpressiveCode({
28 | themes: ["dracula"],
29 | plugins: [
30 | pluginLineNumbers(),
31 | ],
32 | }),
33 | starlight({
34 | credits: true,
35 | title: "Flutter 배우기",
36 | customCss: [
37 | './src/styles/custom.css',
38 | ],
39 | head: [
40 | // Adding google analytics
41 | {
42 | tag: 'script',
43 | attrs: {
44 | src: `https://www.googletagmanager.com/gtag/js?id=${googleAnalyticsId}`,
45 | },
46 | },
47 | {
48 | tag: 'script',
49 | content: `
50 | window.dataLayer = window.dataLayer || [];
51 | function gtag(){dataLayer.push(arguments);}
52 | gtag('js', new Date());
53 |
54 | gtag('config', '${googleAnalyticsId}');
55 | `,
56 | },
57 | ],
58 | social: [
59 | {
60 | icon: "github",
61 | label: "GitHub",
62 | href: "https://github.com/changjoo-park/learn-flutter",
63 | },
64 | ],
65 | editLink: {
66 | text: "Edit this page on GitHub",
67 | icon: "github",
68 | href: "https://github.com/changjoo-park/learn-flutter/edit/main/docs/",
69 | },
70 | sidebar: sidebars,
71 | plugins: [
72 | starlightLlmsTxt({
73 | projectName: "Flutter 배우기",
74 | }),
75 | starlightGiscus({
76 | repo: 'changjoo-park/learn-flutter',
77 | repoId: 'R_kgDOOpJgKQ',
78 | category: 'Q&A',
79 | categoryId: 'DIC_kwDOOpJgKc4CqIHA',
80 | inputPosition: 'top',
81 | mapping: 'pathname',
82 | reactionsEnabled: true,
83 | emitMetadata: true,
84 | theme: 'preferred_color_scheme',
85 | lang: 'ko',
86 | })
87 | ],
88 | }),
89 | ],
90 | markdown: {
91 | rehypePlugins: [
92 | [
93 | rehypeMermaid,
94 | {
95 | strategy: "file", // alternatively use "data-url"
96 | fsPath: "public/beoe", // add this to gitignore
97 | webPath: "/beoe",
98 | darkScheme: "class",
99 | cache,
100 | },
101 | ],
102 | ],
103 | },
104 | });
105 |
--------------------------------------------------------------------------------
/src/content/docs/part8/build-modes.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 빌드 모드 (debug / profile / release)
3 | ---
4 |
5 | Flutter는 세 가지 주요 빌드 모드를 제공합니다. 각 모드는 개발 과정의 다른 단계에서 사용되며, 각기 다른 최적화와 기능을 제공합니다.
6 |
7 | ## 빌드 모드 개요
8 |
9 | Flutter의 빌드 모드는 다음과 같습니다:
10 |
11 | ## Debug 모드
12 |
13 | Debug 모드는 개발 과정에서 주로 사용하는 모드입니다.
14 |
15 | ### 특징
16 |
17 | - **Hot Reload/Restart**: 코드 변경 사항을 빠르게 확인할 수 있습니다.
18 | - **디버깅 도구**: 콘솔 로그, 디버거 연결, 인스펙터 등 개발 도구 사용 가능합니다.
19 | - **확인용 배너**: 앱 우측 상단에 DEBUG 배너가 표시됩니다.
20 | - **비최적화 빌드**: 성능이 최적화되지 않고 디버깅 정보가 포함되어 있습니다.
21 |
22 | ### 실행 방법
23 |
24 | ```bash
25 | # 명시적으로 debug 모드로 실행
26 | flutter run --debug
27 |
28 | # 기본값이므로 일반적으로 다음과 같이 실행
29 | flutter run
30 | ```
31 |
32 | ### 사용 시나리오
33 |
34 | - 앱 개발 및 기능 테스트
35 | - 코드 디버깅
36 | - UI 구현 및 확인
37 |
38 | ## Profile 모드
39 |
40 | Profile 모드는 성능 분석과 프로파일링을 위한 모드입니다.
41 |
42 | ### 특징
43 |
44 | - **성능 트래킹**: Timeline, DevTools 등을 통한 성능 측정 가능
45 | - **일부 디버깅 비활성화**: Hot Reload, 일부 디버깅 기능은 비활성화
46 | - **실제 성능과 유사**: Release 모드와 유사한 성능 특성을 가지지만, 프로파일링 도구 사용 가능
47 | - **Flutter Inspector**: UI 레이아웃 및 렌더링 분석 가능
48 |
49 | ### 실행 방법
50 |
51 | ```bash
52 | flutter run --profile
53 | ```
54 |
55 | > **주의**: Profile 모드는 에뮬레이터/시뮬레이터에서 정확한 성능 측정이 어려우므로 실제 기기에서 실행하는 것이 좋습니다.
56 |
57 | ### 사용 시나리오
58 |
59 | - 앱 성능 분석
60 | - 병목 현상 파악
61 | - 메모리 사용량 및 프레임 드롭 확인
62 | - 실제 기기에서의 사용자 경험 검증
63 |
64 | ## Release 모드
65 |
66 | Release 모드는 최종 사용자에게 배포하기 위한 최적화된 빌드 모드입니다.
67 |
68 | ### 특징
69 |
70 | - **최적화된 성능**: 모든 성능 최적화 기능 활성화
71 | - **코드 최소화**: 사용하지 않는 코드 제거 및 최소화
72 | - **디버깅 기능 비활성화**: 모든 디버깅 도구와 코드 제거
73 | - **R8/ProGuard (Android)**: 코드 축소, 난독화 및 최적화
74 |
75 | ### 실행 방법
76 |
77 | ```bash
78 | flutter run --release
79 | ```
80 |
81 | ### 빌드 방법
82 |
83 | ```bash
84 | # Android APK 빌드
85 | flutter build apk --release
86 |
87 | # Android App Bundle 빌드
88 | flutter build appbundle --release
89 |
90 | # iOS 빌드
91 | flutter build ios --release
92 | ```
93 |
94 | ### 사용 시나리오
95 |
96 | - 앱 스토어 제출
97 | - 사용자 배포
98 | - 최종 성능 테스트
99 | - 배포 전 검증
100 |
101 | ## 모드 간 비교
102 |
103 | | 기능 | Debug | Profile | Release |
104 | | ---------------- | ----- | ------- | ------- |
105 | | 성능 최적화 | ❌ | ✅ | ✅ |
106 | | 코드 크기 최적화 | ❌ | ✅ | ✅ |
107 | | Hot Reload | ✅ | ❌ | ❌ |
108 | | 디버거 연결 | ✅ | 제한적 | ❌ |
109 | | 성능 오버헤드 | 높음 | 낮음 | 없음 |
110 | | 앱 크기 | 큼 | 중간 | 작음 |
111 | | 프로파일링 도구 | ✅ | ✅ | ❌ |
112 | | Assert 문 실행 | ✅ | ❌ | ❌ |
113 |
114 | ## 모드 전환 시 주의사항
115 |
116 | ### Debug에서 Release로 전환 시 확인 사항
117 |
118 | 1. **assert 문**: Debug 모드에서만, Release 모드에서는 무시됩니다.
119 | 2. **환경 변수**: kDebugMode, kProfileMode, kReleaseMode 플래그를 사용한 조건부 코드 확인
120 | 3. **로그 출력**: 불필요한 print() 문 제거 검토
121 |
122 | ```dart
123 | // 빌드 모드에 따른 조건부 코드 예시
124 | if (kDebugMode) {
125 | print('이 메시지는 Debug 모드에서만 출력됩니다');
126 | } else if (kProfileMode) {
127 | // 프로파일 모드 특화 코드
128 | } else if (kReleaseMode) {
129 | // 릴리즈 모드 특화 코드
130 | }
131 | ```
132 |
133 | 4. **플랫폼 채널**: 네이티브 코드와의 통신이 제대로 작동하는지 확인
134 | 5. **타이밍 차이**: 디버그 모드보다 릴리즈 모드에서 실행 속도가 빠를 수 있음을 고려
135 |
136 | ## 빌드 모드 활용 팁
137 |
138 | ### 다양한 모드 테스트
139 |
140 | 개발 과정에서 정기적으로 Profile 및 Release 모드로 앱을 테스트하여 실제 사용자 경험을 확인하는 것이 좋습니다.
141 |
142 | ### 조건부 코드 작성
143 |
144 | ```dart
145 | // 개발 중에만 필요한 코드
146 | if (kDebugMode) {
147 | // 개발 환경에서만 사용할 추가 기능
148 | enableDevFeatures();
149 | }
150 |
151 | // 릴리즈에서만 활성화할 코드
152 | if (kReleaseMode) {
153 | // 분석 도구 초기화 등
154 | initializeAnalytics();
155 | }
156 | ```
157 |
158 | ### Flavor와 함께 사용
159 |
160 | 빌드 모드는 Flavor(제품 환경)와 함께 사용하여 개발, 스테이징, 프로덕션 환경을 구분할 수 있습니다.
161 |
162 | ```
163 | Debug + Dev Flavor = 개발 환경 테스트
164 | Profile + Staging Flavor = 스테이징 성능 테스트
165 | Release + Production Flavor = 최종 배포 빌드
166 | ```
167 |
168 | ## 결론
169 |
170 | Flutter의 세 가지 빌드 모드(Debug, Profile, Release)는 각각 다른 목적으로 사용되며, 개발 과정의 다양한 단계에서 활용됩니다. 개발 중에는 Debug 모드를 사용하여 빠른 반복 개발을, 성능 테스트에는 Profile 모드를, 최종 배포에는 Release 모드를 사용하는 것이 권장됩니다. 각 모드의 특성을 이해하고 적절히 활용하면 효율적인 개발과 최적화된 앱 배포가 가능합니다.
171 |
--------------------------------------------------------------------------------
/src/content/docs/part4/state-management-intro.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 상태 관리 입문
3 | ---
4 |
5 | Flutter 앱을 개발하면서 가장 중요한 개념 중 하나는 '상태 관리(State Management)'입니다. 이 장에서는 Flutter에서의 상태 관리 개념과 다양한 상태 관리 방법에 대해 알아보겠습니다.
6 |
7 | ## 상태(State)란 무엇인가?
8 |
9 | 상태는 앱의 동작 중에 변할 수 있는 데이터를 의미합니다. 사용자 입력, 네트워크 응답, 시간 경과 등에 따라 앱의 UI가 변경되어야 할 때, 이러한 변경사항을 관리하는 데이터를 '상태'라고 합니다.
10 |
11 | Flutter에서 상태는 크게 다음과 같이 분류할 수 있습니다:
12 |
13 |
14 | ### 1. 임시 상태(Ephemeral State)
15 |
16 | - 단일 위젯 내에서만 사용되는 간단한 상태
17 | - UI의 일시적인 변화를 관리 (예: 버튼 눌림 상태, 입력 필드 포커스 등)
18 | - `StatefulWidget`과 `setState()`로 쉽게 관리 가능
19 |
20 | ### 2. 앱 상태(App State)
21 |
22 | - 앱의 여러 부분에서 공유되는 데이터
23 | - 장기적으로 유지되어야 하는 정보 (예: 사용자 설정, 인증 토큰, 쇼핑 카트 내용)
24 | - 전역적으로 접근 가능해야 하며, 효율적인 관리가 필요
25 | - 상태 관리 라이브러리(Provider, Riverpod, Bloc 등)를 활용하여 관리
26 |
27 | ## 상태 관리가 중요한 이유
28 |
29 | 상태 관리는 다음과 같은 이유로 중요합니다:
30 |
31 | 1. **UI 일관성**: 상태 변화에 따라 UI가 일관되게 업데이트되어야 함
32 | 2. **코드 구조화**: 앱의 상태 로직과 UI 로직을 분리하여 유지보수성 향상
33 | 3. **성능 최적화**: 필요한 부분만 효율적으로 다시 빌드하여 성능 개선
34 | 4. **확장성**: 앱의 규모가 커져도 상태를 효과적으로 관리할 수 있는 구조 필요
35 |
36 |
37 | ## 상태 관리 방식의 진화
38 |
39 | Flutter에서의 상태 관리는 다음과 같이 진화해 왔습니다:
40 |
41 | ### 1. StatefulWidget과 setState()
42 |
43 | Flutter의 기본적인 상태 관리 메커니즘입니다. 간단한 상태를 위젯 내부에서 관리합니다.
44 |
45 | ```dart
46 | class CounterWidget extends StatefulWidget {
47 | @override
48 | _CounterWidgetState createState() => _CounterWidgetState();
49 | }
50 |
51 | class _CounterWidgetState extends State {
52 | int _count = 0;
53 |
54 | void _incrementCount() {
55 | setState(() {
56 | _count++;
57 | });
58 | }
59 |
60 | @override
61 | Widget build(BuildContext context) {
62 | return Column(
63 | children: [
64 | Text('카운트: $_count'),
65 | ElevatedButton(
66 | onPressed: _incrementCount,
67 | child: Text('증가'),
68 | ),
69 | ],
70 | );
71 | }
72 | }
73 | ```
74 |
75 | 특징:
76 |
77 | - 장점: 간단하고 직관적
78 | - 단점: 깊은 위젯 트리에서 상태 전달이 어려움(Prop drilling)
79 |
80 | ### 2. InheritedWidget
81 |
82 | Flutter의 내장 메커니즘으로, 위젯 트리 아래로 데이터를 효율적으로 전달합니다.
83 |
84 | ```dart
85 | class CounterInheritedWidget extends InheritedWidget {
86 | final int count;
87 | final Function incrementCount;
88 |
89 | CounterInheritedWidget({
90 | required this.count,
91 | required this.incrementCount,
92 | required Widget child,
93 | }) : super(child: child);
94 |
95 | @override
96 | bool updateShouldNotify(CounterInheritedWidget oldWidget) {
97 | return count != oldWidget.count;
98 | }
99 |
100 | static CounterInheritedWidget of(BuildContext context) {
101 | return context.dependOnInheritedWidgetOfExactType()!;
102 | }
103 | }
104 | ```
105 |
106 | 특징:
107 |
108 | - 장점: 위젯 트리를 통한 데이터 전파
109 | - 단점: 직접 구현하기 복잡함
110 |
111 | ### 3. Provider 패턴
112 |
113 | InheritedWidget을 래핑한 패키지로, 더 사용하기 쉬운 API를 제공합니다.
114 |
115 | ```dart
116 | // 상태 클래스
117 | class CounterModel with ChangeNotifier {
118 | int _count = 0;
119 | int get count => _count;
120 |
121 | void increment() {
122 | _count++;
123 | notifyListeners();
124 | }
125 | }
126 |
127 | // Provider 설정
128 | ChangeNotifierProvider(
129 | create: (context) => CounterModel(),
130 | child: MyApp(),
131 | ),
132 |
133 | // 데이터 사용
134 | Consumer(
135 | builder: (context, counter, child) {
136 | return Text('카운트: ${counter.count}');
137 | },
138 | )
139 | ```
140 |
141 | 특징:
142 |
143 | - 장점: 사용하기 쉽고 직관적
144 | - 단점: 복잡한 상태 관리에는 한계가 있음
145 |
146 | ### 4. 현대적 상태 관리 솔루션
147 |
148 | 현재는 다양한 상태 관리 라이브러리가 존재합니다:
149 |
150 | - **Riverpod**: Provider의 개선 버전으로, 컴파일 타임 안전성 및 테스트 용이성 강화
151 | - **Bloc/Cubit**: 비즈니스 로직을 명확하게 분리하는 패턴
152 | - **MobX**: 반응형 프로그래밍 기반의 상태 관리
153 | - **Redux**: 예측 가능한 상태 컨테이너
154 |
155 | ## 상태 관리 선택 가이드
156 |
157 | 어떤 상태 관리 솔루션을 선택해야 할까요? 다음 요소를 고려하세요:
158 |
159 | 1. **앱 복잡도**: 단순한 앱은 setState()나 Provider로도 충분할 수 있음
160 | 2. **팀 경험**: 팀이 이미 익숙한 솔루션이 있다면 고려
161 | 3. **학습 곡선**: 일부 솔루션은 배우기 어려울 수 있음
162 | 4. **성능 요구사항**: 대규모 앱의 경우 성능 최적화된 솔루션 필요
163 | 5. **테스트 용이성**: 상태 로직의 테스트 용이성도 중요한 고려사항
164 |
165 | ## 이 장의 다음 섹션들
166 |
167 | 이 장의 나머지 부분에서는 Flutter의 다양한 상태 관리 방법을 자세히 다룰 것입니다:
168 |
169 | 1. **setState와 ValueNotifier**: Flutter의 기본 상태 관리 메커니즘
170 | 2. **InheritedWidget과 Provider**: 위젯 트리를 통한 상태 공유
171 | 3. **Riverpod**: 현대적인 상태 관리 솔루션
172 | 4. **TodoList 실습**: 실제 애플리케이션에 상태 관리 적용하기
173 |
174 | ## 요약
175 |
176 | - 상태 관리는 Flutter 애플리케이션 개발에서 핵심 개념
177 | - 상태는 임시 상태와 앱 상태로 분류됨
178 | - 다양한 상태 관리 솔루션이 존재하며, 앱의 복잡도와 요구사항에 따라 선택
179 | - 효과적인 상태 관리는 유지보수성, 성능, 확장성을 향상시킴
180 |
181 | 다음 섹션에서는 Flutter의 기본 상태 관리 메커니즘인 `setState()`와 `ValueNotifier`에 대해 자세히 알아보겠습니다.
182 |
--------------------------------------------------------------------------------
/sidebar.config.mjs:
--------------------------------------------------------------------------------
1 | export const sidebars = [
2 | {
3 | label: "📦 1. 시작하기",
4 | items: [
5 | { label: "소개", slug: "part1/introduction" },
6 | { label: "변경사항", slug: "part1/changelog" },
7 | { label: "개발 환경 구성", slug: "part1/setup" },
8 | { label: "LLM 설정", slug: "part1/llms", badge: 'new' },
9 | { label: "첫 프로젝트 생성 및 실행", slug: "part1/first-project" },
10 | { label: "Flutter 프로젝트 구조 이해", slug: "part1/project-structure" },
11 | ],
12 | },
13 | {
14 | label: "💡 2. Dart 언어 기초",
15 | items: [
16 | { label: "Dart 소개", slug: "part2/dart-intro" },
17 | { label: "기본 문법 및 변수", slug: "part2/basic-syntax" },
18 | { label: "타입 시스템 & 제네릭", slug: "part2/type-system" },
19 | { label: "클래스, 생성자, 팩토리", slug: "part2/classes" },
20 | { label: "컬렉션과 반복문", slug: "part2/collections" },
21 | { label: "비동기 프로그래밍", slug: "part2/async" },
22 | { label: "예외 처리", slug: "part2/exceptions" },
23 | { label: "Extension / Mixin", slug: "part2/extensions" },
24 | { label: "레코드 & 패턴매칭", slug: "part2/records" },
25 | ],
26 | },
27 | {
28 | label: "🧱 3. Flutter의 기본 구성 요소",
29 | items: [
30 | { label: "위젯 개념과 주요 위젯", slug: "part3/widgets" },
31 | {
32 | label: "Stateless / Stateful 위젯 상세",
33 | slug: "part3/stateless-stateful",
34 | },
35 | { label: "Widget Tree 이해", slug: "part3/widget-tree" },
36 | { label: "주요 위젯", slug: "part3/basic-widgets" },
37 | { label: "레이아웃 위젯", slug: "part3/layout-widgets" },
38 | ],
39 | },
40 | {
41 | label: "🎨 4. 상태 관리",
42 | items: [
43 | { label: "상태 관리 입문", slug: "part4/state-management-intro" },
44 | {
45 | label: "setState, ValueNotifier",
46 | slug: "part4/setstate-valuenotifier",
47 | },
48 | { label: "InheritedWidget, Provider", slug: "part4/inherited-provider" },
49 | { label: "Riverpod 소개 및 실습", slug: "part4/riverpod" },
50 | ],
51 | },
52 | {
53 | label: "🚦 5. 네비게이션과 화면 구성",
54 | items: [
55 | { label: "Navigator 1.0", slug: "part5/navigator1" },
56 | { label: "Navigator 2.0", slug: "part5/navigator2" },
57 | { label: "go_router 사용법", slug: "part5/go-router" },
58 | {
59 | label: "라우트 가드, ShellRoute, DeepLink",
60 | slug: "part5/advanced-routing",
61 | },
62 | { label: "실습: 복수 화면 전환", slug: "part5/multi-screen" },
63 | {
64 | label: "Drawer, BottomNavigationBar, TabBar",
65 | slug: "part5/navigation-widgets",
66 | },
67 | ],
68 | },
69 | {
70 | label: "🔌 6. 외부와의 연동",
71 | items: [
72 | { label: "Dio를 통한 API 통신", slug: "part6/dio" },
73 | {
74 | label: "JSON 직렬화 (freezed, json_serializable)",
75 | slug: "part6/json-serialization",
76 | },
77 | ],
78 | },
79 | {
80 | label: "🧪 7. 테스트와 디버깅",
81 | items: [
82 | { label: "단위 테스트", slug: "part7/unit-test" },
83 | { label: "위젯 테스트", slug: "part7/widget-test" },
84 | { label: "통합 테스트", slug: "part7/integration-test" },
85 | // { label: "Flutter DevTools", slug: "part7/devtools" },
86 | // { label: "로그 관리", slug: "part7/logging" },
87 | ],
88 | },
89 | {
90 | label: "🚀 8. 앱 배포 및 운영",
91 | items: [
92 | { label: "빌드 모드", slug: "part8/build-modes" },
93 | { label: "Android / iOS 배포", slug: "part8/deploy-procedure" },
94 | { label: "Codemagic CI/CD", slug: "part8/cicd-codemagic", badge: "🚧" },
95 | {
96 | label: "환경 분리 및 flavor",
97 | slug: "part8/environment-flavors",
98 | badge: "BETA",
99 | },
100 | {
101 | label: "사용자 분석 도구",
102 | slug: "part8/analytics-tools",
103 | badge: "BETA",
104 | },
105 | { label: "에러 추적", slug: "part8/error-tracking" },
106 | ],
107 | },
108 | {
109 | label: "🧭 9. 프로젝트 구조 & 아키텍처",
110 | items: [
111 | { label: "기능별 vs 계층별 폴더 구조", slug: "part9/folder-structure" },
112 | { label: "멀티 모듈 아키텍처", slug: "part9/multi-module" },
113 | ],
114 | },
115 | {
116 | label: "🌍 10. 보완 학습",
117 | items: [
118 | { label: "CustomPainter와 RenderBox", slug: "part10/custom-painting" },
119 | { label: "위젯 캐싱", slug: "part10/widget-caching" },
120 | { label: "애니메이션", slug: "part10/animations" },
121 | { label: "접근성", slug: "part10/accessibility" },
122 | { label: "다국어 처리", slug: "part10/internationalization" },
123 | { label: "성능 최적화", slug: "part10/performance", badge: "BETA" },
124 | // { label: "추천 패키지", slug: "part10/recommended-packages" },
125 | ],
126 | },
127 | {
128 | label: "📚 부록",
129 | items: [
130 | // { label: "개발 도구와 링크", slug: "appendix/tools" },
131 | { label: "Flutter 오류 대응법", slug: "appendix/error-handling" },
132 | { label: "코드 템플릿", slug: "appendix/code-templates" },
133 | { label: "소셜 로그인", slug: "appendix/social-login", badge: "🚧" },
134 | { label: "iOS 라이브 액티비티", slug: "appendix/live-activities" },
135 | { label: "WidgetBook", slug: "appendix/widgetbook" },
136 | { label: "FAQ", slug: "appendix/faq" },
137 | ],
138 | },
139 | ];
140 |
--------------------------------------------------------------------------------
/src/content/docs/part1/setup.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 개발 환경 구성
3 | banner:
4 | content: |
5 | 안녕하세요! 이 페이지를 보시는 분들에게 설문을 받고 있습니다.
6 | 여기를 눌러서 참여부탁드려요!
7 | ---
8 |
9 | Flutter 개발을 시작하기 위해 필요한 환경을 구성해 보겠습니다. 이 과정에는 Flutter SDK 설치, 코드 에디터 설정, 그리고 개발 도구 구성이 포함됩니다.
10 |
11 | ### 여러 버전을 사용하고 싶을 때
12 |
13 | 가이드에 있는 공식적인 방법도 좋지만 여러 버전을 사용하고 싶을 때는 [Flutter Version Manager](https://fvm.app) 또는 [mise](https://mise.jdx.dev)를 사용하세요. Flutter의 최신 버전 업데이트 이후에 바로 사용하는 경우 패키지가 호환이 안되는 경우가 있어 버전을 유지하는데 어려움을 겪을 수 있습니다.
14 |
15 | 저는 mise를 조금 더 추천 드립니다. Flutter Version Manager와 달리 Flutter 외에 앱 개발에 필요한 Ruby, Node.js 등의 버전도 관리할 수 있습니다.
16 |
17 | ## Flutter SDK 설치
18 |
19 | ### Windows에서 설치
20 |
21 | 1. [Flutter 공식 사이트](https://flutter.dev/docs/get-started/install/windows)에서 Flutter SDK를 다운로드합니다.
22 | 2. 다운로드한 zip 파일을 원하는 위치에 압축 해제합니다 (예: `C:\dev\flutter`).
23 | 3. 환경 변수 설정:
24 | - 시스템 환경 변수에서 `Path` 변수에 Flutter SDK의 `bin` 폴더 경로를 추가합니다.
25 | - 예: `C:\dev\flutter\bin`
26 | 4. 명령 프롬프트를 열고 다음 명령어를 실행하여 설치를 확인합니다:
27 | ```bash
28 | flutter doctor
29 | ```
30 |
31 | ### macOS에서 설치
32 |
33 | 1. **Homebrew를 이용한 설치** (권장):
34 |
35 | ```bash
36 | brew install --cask flutter
37 | ```
38 |
39 | 2. **수동 설치**:
40 | - [Flutter 공식 사이트](https://flutter.dev/docs/get-started/install/macos)에서 Flutter SDK를 다운로드합니다.
41 | - 다운로드한 zip 파일을 원하는 위치에 압축 해제합니다 (예: `~/development/flutter`).
42 | - 환경 변수 설정: `.zshrc` 또는 `.bash_profile` 파일에 다음 내용을 추가합니다:
43 | ```bash
44 | export PATH="$PATH:~/development/flutter/bin"
45 | ```
46 | - 터미널을 열고 다음 명령어를 실행하여 설치를 확인합니다:
47 | ```bash
48 | flutter doctor
49 | ```
50 |
51 | ### Linux에서 설치
52 |
53 | 1. [Flutter 공식 사이트](https://flutter.dev/docs/get-started/install/linux)에서 Flutter SDK를 다운로드합니다.
54 | 2. 다운로드한 tar.xz 파일을 원하는 위치에 압축 해제합니다:
55 | ```bash
56 | tar xf flutter_linux_-stable.tar.xz -C ~/development
57 | ```
58 | 3. 환경 변수 설정: `.bashrc` 또는 `.zshrc` 파일에 다음 내용을 추가합니다:
59 | ```bash
60 | export PATH="$PATH:~/development/flutter/bin"
61 | ```
62 | 4. 터미널을 열고 다음 명령어를 실행하여 설치를 확인합니다:
63 | ```bash
64 | flutter doctor
65 | ```
66 |
67 | ## Visual Studio Code 설정
68 |
69 | Visual Studio Code는 Flutter 개발을 위한 권장 에디터입니다.
70 |
71 | ### VS Code 설치
72 |
73 | 1. [Visual Studio Code 공식 사이트](https://code.visualstudio.com/)에서 에디터를 다운로드하고 설치합니다.
74 | 2. VS Code를 실행합니다.
75 |
76 | ### Flutter와 Dart 플러그인 설치
77 |
78 | 1. VS Code의 Extensions 탭(`Ctrl+Shift+X` 또는 `Cmd+Shift+X`)을 엽니다.
79 | 2. "Flutter"를 검색하고 Flutter 확장 프로그램을 설치합니다 (Dart 확장 프로그램도 자동으로 설치됩니다).
80 | 3. VS Code를 재시작합니다.
81 |
82 | ### Dart DevTools 설정
83 |
84 | Dart DevTools는 Flutter 앱 디버깅에 유용한 도구입니다.
85 |
86 | 1. VS Code에서 Command Palette(`Ctrl+Shift+P` 또는 `Cmd+Shift+P`)를 엽니다.
87 | 2. "Flutter: Open DevTools"를 입력하고 선택합니다.
88 | 3. 브라우저에서 DevTools가 열립니다.
89 |
90 | ## 에뮬레이터 / 실기기 연결
91 |
92 | ### Android 에뮬레이터 설정
93 |
94 | 1. [Android Studio](https://developer.android.com/studio)를 다운로드하고 설치합니다.
95 | 2. Android Studio를 실행하고 "SDK Manager"를 엽니다.
96 | 3. "SDK Tools" 탭에서 "Android SDK Build-Tools", "Android SDK Command-line Tools", "Android Emulator", "Android SDK Platform-Tools"를 설치합니다.
97 | 4. "AVD Manager"를 열고 "Create Virtual Device"를 클릭합니다.
98 | 5. 원하는 디바이스를 선택하고 시스템 이미지를 다운로드한 후 에뮬레이터를 생성합니다.
99 | 6. 에뮬레이터를 실행합니다.
100 |
101 | ### iOS 시뮬레이터 설정 (macOS만 해당)
102 |
103 | 1. App Store에서 Xcode를 설치합니다.
104 | 2. Xcode를 실행하고 필요한 구성 요소를 설치합니다.
105 | 3. 터미널에서 다음 명령어를 실행하여, Flutter와 Xcode 간의 라이센스 동의를 진행합니다:
106 | ```bash
107 | sudo xcodebuild -license
108 | ```
109 | 4. iOS 시뮬레이터를 실행합니다:
110 | ```bash
111 | open -a Simulator
112 | ```
113 |
114 | ### 실제 안드로이드 기기 연결
115 |
116 | 1. 안드로이드 디바이스의 설정에서 "개발자 옵션"을 활성화합니다:
117 | - 설정 > 휴대폰 정보 > 소프트웨어 정보 > 빌드 번호를 7번 탭합니다.
118 | 2. 개발자 옵션에서 "USB 디버깅"을 활성화합니다.
119 | 3. USB 케이블로 디바이스를 컴퓨터에 연결합니다.
120 | 4. 디바이스에 표시되는 USB 디버깅 권한 요청을 수락합니다.
121 | 5. 터미널에서 다음 명령어로 연결된 디바이스를 확인합니다:
122 | ```bash
123 | flutter devices
124 | ```
125 |
126 | ### 실제 iOS 기기 연결 (macOS만 해당)
127 |
128 | 1. Apple Developer 계정이 필요합니다.
129 | 2. Xcode를 열고 "Preferences > Accounts"에서 Apple ID를 추가합니다.
130 | 3. USB 케이블로 iOS 기기를 Mac에 연결합니다.
131 | 4. Xcode에서 프로젝트를 열고 디바이스를 선택한 후 "Trust"를 선택합니다.
132 | 5. 터미널에서 다음 명령어로 연결된 디바이스를 확인합니다:
133 | ```bash
134 | flutter devices
135 | ```
136 |
137 | ## Flutter Doctor로 확인하기
138 |
139 | 설치 과정이 완료되면 다음 명령어를 실행하여 환경 설정이 올바르게 되었는지 확인합니다:
140 |
141 | ```bash
142 | flutter doctor -v
143 | ```
144 |
145 | 이 명령어는 Flutter SDK, Android toolchain, iOS toolchain (macOS만 해당), VS Code 등의 설치 상태를 확인하고, 문제가 있다면 해결 방법을 제안합니다.
146 |
147 | ## 추가 설정 (선택사항)
148 |
149 | ### Git 설정
150 |
151 | Flutter 프로젝트는 Git을 이용한 버전 관리를 권장합니다:
152 |
153 | 1. [Git 공식 사이트](https://git-scm.com/)에서 Git을 다운로드하고 설치합니다.
154 | 2. 터미널 또는 명령 프롬프트에서 Git 설정을 구성합니다:
155 | ```bash
156 | git config --global user.name "Your Name"
157 | git config --global user.email "your.email@example.com"
158 | ```
159 |
160 | ### 추가 권장 VS Code 확장 프로그램
161 |
162 | - **Awesome Flutter Snippets**: 유용한 Flutter 코드 스니펫 제공
163 | - **Flutter Widget Snippets**: 위젯 코드 생성 지원
164 | - **Pubspec Assist**: 의존성 관리 도우미
165 | - **Error Lens**: 인라인 오류 하이라이팅
166 | - **Git Lens**: Git 통합 향상
167 |
168 | ## 문제 해결
169 |
170 | ### "flutter: command not found" 오류
171 |
172 | 환경 변수가 올바르게 설정되지 않았을 수 있습니다. 시스템의 PATH 변수에 Flutter bin 디렉토리가 포함되어 있는지 확인하세요.
173 |
174 | ### Android Studio 설치 문제
175 |
176 | 안드로이드 설정에 문제가 있을 경우:
177 |
178 | ```bash
179 | flutter doctor --android-licenses
180 | ```
181 |
182 | 명령어를 실행하여 Android 라이센스를 동의합니다.
183 |
184 | ### 에뮬레이터 성능 문제
185 |
186 | Windows와 Linux 사용자는 BIOS에서 가상화 기술(Intel VT-x 또는 AMD-V)이 활성화되어 있는지 확인하세요.
187 |
188 | ## 결론
189 |
190 | 이제 Flutter 개발을 위한 환경 설정이 완료되었습니다. 다음 섹션에서는 첫 번째 Flutter 프로젝트를 생성하고 실행하는 방법을 알아보겠습니다.
191 |
--------------------------------------------------------------------------------
/src/content/docs/part2/dart-intro.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Dart 소개
3 | ---
4 |
5 | ## Dart란 무엇인가?
6 |
7 | Dart는 Google에서 개발한 클라이언트 최적화 프로그래밍 언어로, 모든 플랫폼에서 빠르고 안정적인 애플리케이션을 개발하기 위해 설계되었습니다. Dart는 Flutter 프레임워크의 기반이 되는 언어이며, 웹, 모바일, 데스크톱 애플리케이션을 개발하는 데 사용됩니다.
8 |
9 | ## Dart의 역사
10 |
11 | - **2011년**: Google I/O에서 처음 발표
12 | - **2013년**: Dart 1.0 출시
13 | - **2018년**: Dart 2.0 출시 (타입 안전성 강화)
14 | - **2021년**: Dart 2.13 출시 (null 안전성 도입)
15 | - **2023년**: Dart 3.0 출시 (레코드, 패턴 매칭 도입)
16 |
17 | Dart는 초기에 JavaScript를 대체하기 위한 웹 프로그래밍 언어로 시작했지만, 현재는 Flutter를 통한 크로스 플랫폼 애플리케이션 개발에 주로 사용됩니다.
18 |
19 | ## Dart의 주요 특징
20 |
21 | ### 1. 객체 지향 언어
22 |
23 | Dart는 클래스 기반의 객체 지향 언어입니다. 모든 것이 객체이며, 모든 객체는 클래스의 인스턴스입니다. 심지어 함수와 `null`도 객체입니다.
24 |
25 | ```dart
26 | class Person {
27 | String name;
28 | int age;
29 |
30 | Person(this.name, this.age);
31 |
32 | void introduce() {
33 | print('안녕하세요, 저는 $name이고 $age살입니다.');
34 | }
35 | }
36 |
37 | void main() {
38 | final person = Person('홍길동', 30);
39 | person.introduce(); // 출력: 안녕하세요, 저는 홍길동이고 30살입니다.
40 | }
41 | ```
42 |
43 | ### 2. 강력한 타입 시스템
44 |
45 | Dart는 정적 타입 언어이지만, 타입 추론을 지원하여 타입 명시를 생략할 수 있습니다.
46 |
47 | ```dart
48 | // 타입 명시
49 | String name = '홍길동';
50 | int age = 30;
51 |
52 | // 타입 추론
53 | var name = '홍길동'; // String으로 추론
54 | var age = 30; // int로 추론
55 | final height = 175.5; // double로 추론
56 | ```
57 |
58 | ### 3. 비동기 프로그래밍 지원
59 |
60 | Dart는 `Future`, `Stream`, `async`, `await` 등을 통해 비동기 프로그래밍을 지원합니다.
61 |
62 | ```dart
63 | Future fetchData() async {
64 | // 비동기 작업 시뮬레이션
65 | await Future.delayed(Duration(seconds: 2));
66 | return '데이터';
67 | }
68 |
69 | void main() async {
70 | print('데이터 요청 시작');
71 | final data = await fetchData();
72 | print('받은 데이터: $data');
73 | }
74 | ```
75 |
76 | ### 4. Null 안전성
77 |
78 | Dart 2.12부터 Null 안전성을 도입하여, 변수가 null 가능성을 타입 시스템에서 명시합니다.
79 |
80 | ```dart
81 | // null이 될 수 없는 변수
82 | String name = '홍길동';
83 | // name = null; // 컴파일 오류
84 |
85 | // null이 될 수 있는 변수
86 | String? nullableName = '홍길동';
87 | nullableName = null; // 허용됨
88 | ```
89 |
90 | ### 5. 다중 플랫폼 지원
91 |
92 | Dart는 여러 플랫폼에서 실행될 수 있습니다:
93 |
94 | - **네이티브 플랫폼**: Dart는 AoT(Ahead-of-Time) 컴파일을 통해 네이티브 바이너리로 컴파일됩니다. Flutter 앱은 이 방식으로 배포됩니다.
95 | - **웹 플랫폼**: Dart는 JavaScript로 컴파일되어 브라우저에서 실행됩니다.
96 | - **개발 환경**: Dart는 JIT(Just-in-Time) 컴파일을 통해 개발 중 핫 리로드와 같은 기능을 제공합니다.
97 |
98 | ### 6. 풍부한 표준 라이브러리
99 |
100 | Dart는 다양한 기능을 제공하는 풍부한 표준 라이브러리를 포함하고 있습니다:
101 |
102 | - 컬렉션 (`List`, `Map`, `Set` 등)
103 | - 비동기 처리 (`Future`, `Stream`)
104 | - 파일 I/O
105 | - HTTP 클라이언트
106 | - 정규 표현식
107 | - 직렬화 지원
108 |
109 | ## Dart 실행 환경
110 |
111 | Dart 코드는 다양한 환경에서 실행될 수 있습니다:
112 |
113 | ### 1. Dart VM
114 |
115 | Dart Virtual Machine(VM)은 Dart 코드를 직접 실행하는 환경으로, 개발 중 코드를 빠르게 실행하고 디버깅할 수 있습니다.
116 |
117 | ```bash
118 | dart run main.dart
119 | ```
120 |
121 | ### 2. Flutter
122 |
123 | Flutter는 Dart를 사용하여 크로스 플랫폼 모바일 애플리케이션을 개발하는 프레임워크입니다.
124 |
125 | ```bash
126 | flutter run
127 | ```
128 |
129 | ### 3. Web (Dart2JS)
130 |
131 | Dart 코드는 JavaScript로 컴파일되어 웹 브라우저에서 실행될 수 있습니다.
132 |
133 | ```bash
134 | dart compile js main.dart -o main.js
135 | ```
136 |
137 | ### 4. Native (Native AOT)
138 |
139 | Dart 코드는 네이티브 바이너리로 컴파일되어 독립 실행 파일로 배포될 수 있습니다.
140 |
141 | ```bash
142 | dart compile exe main.dart -o main
143 | ```
144 |
145 | ## Dart 패키지 생태계
146 |
147 | Dart는 `pub.dev`라는 공식 패키지 저장소를 통해 풍부한 패키지 생태계를 제공합니다. 이 저장소에는 다양한 기능을 제공하는 수천 개의 오픈 소스 패키지가 있습니다.
148 |
149 | 패키지를 프로젝트에 추가하려면 `pubspec.yaml` 파일에 의존성을 추가합니다:
150 |
151 | ```yaml
152 | dependencies:
153 | http: ^1.0.0
154 | path: ^1.8.0
155 | ```
156 |
157 | 그리고 다음 명령으로 패키지를 설치합니다:
158 |
159 | ```bash
160 | dart pub get
161 | ```
162 |
163 | ## Dart와 다른 언어 비교
164 |
165 | ### Java와 비교
166 |
167 | ```dart
168 | // Dart
169 | class Person {
170 | String name;
171 | int age;
172 |
173 | Person(this.name, this.age);
174 |
175 | void sayHello() {
176 | print('Hello, I am $name');
177 | }
178 | }
179 |
180 | // Java
181 | public class Person {
182 | private String name;
183 | private int age;
184 |
185 | public Person(String name, int age) {
186 | this.name = name;
187 | this.age = age;
188 | }
189 |
190 | public void sayHello() {
191 | System.out.println("Hello, I am " + name);
192 | }
193 | }
194 | ```
195 |
196 | ### JavaScript와 비교
197 |
198 | ```dart
199 | // Dart
200 | void main() {
201 | final list = [1, 2, 3, 4, 5];
202 | final doubled = list.map((item) => item * 2).toList();
203 | print(doubled); // [2, 4, 6, 8, 10]
204 | }
205 |
206 | // JavaScript
207 | function main() {
208 | const list = [1, 2, 3, 4, 5];
209 | const doubled = list.map(item => item * 2);
210 | console.log(doubled); // [2, 4, 6, 8, 10]
211 | }
212 | ```
213 |
214 | ### Swift와 비교
215 |
216 | ```dart
217 | // Dart
218 | class Person {
219 | String name;
220 | int? age;
221 |
222 | Person(this.name, {this.age});
223 | }
224 |
225 | // Swift
226 | class Person {
227 | let name: String
228 | var age: Int?
229 |
230 | init(name: String, age: Int? = nil) {
231 | self.name = name
232 | self.age = age
233 | }
234 | }
235 | ```
236 |
237 | ## Dart의 장점
238 |
239 | 1. **통합 개발 환경**: 단일 언어로 모바일, 웹, 데스크톱 앱을 개발할 수 있습니다.
240 | 2. **생산성**: 핫 리로드, 풍부한 도구, 직관적인 문법으로 개발 생산성을 높입니다.
241 | 3. **성능**: AoT 컴파일을 통해 네이티브 성능에 가까운 실행 속도를 제공합니다.
242 | 4. **안정성**: 강력한 타입 시스템과 null 안전성으로 많은 런타임 오류를 방지합니다.
243 | 5. **확장성**: 표준 라이브러리와 풍부한 패키지 생태계를 통해 다양한 기능을 추가할 수 있습니다.
244 |
245 | ## Dart 개발 환경 설정
246 |
247 | ### VS Code에서 Dart 개발 환경 설정
248 |
249 | 1. Dart SDK 설치 (Flutter SDK를 설치했다면 이미 포함되어 있습니다)
250 | 2. VS Code 설치
251 | 3. Dart 확장 프로그램 설치
252 | 4. 새 Dart 프로젝트 생성:
253 | ```bash
254 | dart create my_dart_project
255 | ```
256 | 5. VS Code에서 프로젝트 열기
257 | 6. `main.dart` 파일 실행하기: F5 또는 "Run" 버튼 클릭
258 |
259 | ## 결론
260 |
261 | Dart는 현대적인 애플리케이션 개발을 위한 강력하고 유연한 프로그래밍 언어입니다. 특히 Flutter와 함께 사용하면, 단일 코드베이스로 고품질의 크로스 플랫폼 애플리케이션을 개발할 수 있습니다.
262 |
263 | 다음 장에서는 Dart의 기본 문법과 변수에 대해 더 자세히 알아보겠습니다.
264 |
--------------------------------------------------------------------------------
/llm.txt:
--------------------------------------------------------------------------------
1 | # Tabling User Flutter 프로젝트 LLM 가이드
2 |
3 | ## 1. 프로젝트 개요
4 |
5 | 이 문서는 `tabling_user` Flutter 애플리케이션 프로젝트를 이해하고 개발하는 데 도움을 주기 위한 LLM 가이드입니다. Tabling 사용자 앱은 고객이 레스토랑을 검색하고, 예약하고, 웨이팅 목록에 참여하는 등의 기능을 제공하는 앱입니다.
6 |
7 | ## 2. 프로젝트 구조
8 |
9 | ### 2.1. 모노레포(Monorepo) 구조
10 |
11 | Tabling Flutter 프로젝트는 모노레포 구조로 조직되어 있으며, 여러 앱과 패키지를 포함하고 있습니다.
12 |
13 | tabling_c_flutter/
14 | ├── apps/
15 | │ ├── tabling_user/ # 유저 타입 앱 (이 문서의 주제)
16 | │ ├── tabling_web_app/ # 웹 앱
17 | │ ├── server_board/
18 | │ ├── tabling_web/
19 | │ ├── reservation/
20 | │ ├── ceoboard/
21 | │ ├── storybook/
22 | │ └── tabling_pager/
23 | └── packages/ # 공유 패키지
24 | ├── tabling_ui/ # UI 컴포넌트 패키지
25 | ├── tabling_models/ # 데이터 모델 패키지
26 | ├── tabling_rest_client/ # API 클라이언트 패키지
27 | ├── tabling_shared_data/ # 공유 데이터 패키지
28 | ├── tabling_restaurant_card/ # 레스토랑 카드 UI 컴포넌트
29 | ├── tabling_format/ # 포맷팅 유틸리티
30 | ├── tabling_analytics/ # 분석 도구
31 | ├── tabling_image/ # 이미지 관련 유틸리티
32 | └── tabling_video_player/ # 비디오 플레이어 컴포넌트
33 |
34 | ### 2.2. tabling_user 앱 구조
35 |
36 | tabling_user 앱은 다음과 같은 디렉토리 구조를 가지고 있습니다:
37 |
38 | tabling_user/
39 | ├── lib/
40 | │ ├── analytics/ # 분석 관련 코드
41 | │ ├── animations/ # 애니메이션 관련 코드
42 | │ ├── constants/ # 상수 정의
43 | │ ├── extensions/ # 확장 메소드
44 | │ ├── locales/ # 다국어 지원
45 | │ ├── mixins/ # 믹스인
46 | │ ├── models/ # 앱 특화 모델 (패키지 모델 외)
47 | │ ├── pages/ # 페이지 정의 (라우팅 진입점)
48 | │ ├── providers/ # Riverpod 프로바이더
49 | │ ├── repositories/ # 데이터 리포지토리
50 | │ ├── router/ # 라우팅 설정
51 | │ ├── services/ # 비즈니스 로직 서비스
52 | │ ├── utils/ # 유틸리티 함수
53 | │ ├── view_models/ # 뷰 모델
54 | │ ├── app.dart # 앱 진입점
55 | │ ├── flavors.dart # 환경 설정
56 | │ ├── main.dart # 메인 진입점
57 | │ ├── main_develop.dart # 개발 환경 진입점
58 | │ ├── main_production.dart # 프로덕션 환경 진입점
59 | │ └── main_staging.dart # 스테이징 환경 진입점
60 |
61 | ## 3. 아키텍처 패턴
62 |
63 | ### 3.1. Page -> Screen -> Widget 패턴
64 |
65 | tabling_user 앱은 Page -> Screen -> Widget 패턴을 사용하여 UI를 구성합니다:
66 |
67 | 1. **Page**:
68 | - 라우팅 시스템의 진입점
69 | - 앱의 `/pages` 디렉토리에 정의됨
70 | - 해당 화면의 라우팅, 상태 관리 및 초기화 로직을 담당
71 | - 일반적으로 Provider나 View Model과 연결되어 데이터 상태를 관리
72 | - 화면 내용을 구성하는 Screen 컴포넌트를 포함
73 |
74 | 2. **Screen**:
75 | - `tabling_ui` 패키지의 `/screens` 디렉토리에 정의됨
76 | - 특정 페이지의 주요 레이아웃과 UI 로직을 담당
77 | - 재사용 가능한 단위로 설계됨
78 | - 상위 Page로부터 데이터와 이벤트 핸들러를 전달받음
79 | - 여러 작은 Widget들을 조합하여 화면을 구성
80 |
81 | 3. **Widget**:
82 | - `tabling_ui` 패키지의 `/widgets` 디렉토리에 정의됨
83 | - 작고 재사용 가능한 UI 컴포넌트
84 | - 버튼, 텍스트 필드, 카드 등 기본 UI 요소
85 | - 디자인 시스템에 따라 일관된 스타일과 동작을 제공
86 |
87 | ### 3.2. 상태 관리
88 |
89 | tabling_user 앱은 Riverpod를 사용하여 상태를 관리합니다:
90 |
91 | - `/providers` 디렉토리: 글로벌 상태 및 서비스 프로바이더
92 | - 각 Page 디렉토리 내 `provider.dart`: 해당 페이지 특화 프로바이더
93 | - 각 기능 디렉토리 내 `viewmodel.dart`: 상태와 비즈니스 로직 캡슐화
94 |
95 | ## 4. 주요 패키지 및 종속성
96 |
97 | ### 4.1. 내부 패키지
98 |
99 | 앱은 다음과 같은 내부 패키지를 활용합니다:
100 |
101 | - **tabling_ui**: 디자인 시스템 구현, UI 컴포넌트 제공
102 | - **tabling_models**: 앱에서 사용되는 데이터 모델 정의
103 | - **tabling_rest_client**: API 통신 클라이언트
104 | - **tabling_shared_data**: 앱 간 공유 데이터
105 | - **tabling_restaurant_card**: 레스토랑 카드 UI 컴포넌트
106 | - **tabling_format**: 날짜, 시간, 금액 등 포맷팅 유틸리티
107 | - **tabling_analytics**: 분석 및 이벤트 추적
108 | - **tabling_image**: 이미지 관련 유틸리티
109 |
110 | ### 4.2. 외부 종속성
111 |
112 | 주요 외부 라이브러리:
113 |
114 | - **flutter_riverpod**: 상태 관리
115 | - **go_router**: 라우팅
116 | - **firebase_core, firebase_analytics, firebase_messaging 등**: Firebase 서비스
117 | - **cached_network_image**: 네트워크 이미지 캐싱
118 | - **flutter_facebook_auth, kakao_flutter_sdk_user, sign_in_with_apple**: 소셜 로그인
119 | - **infinite_scroll_pagination**: 무한 스크롤
120 | - **easy_debounce**: 입력 디바운싱
121 | - **permission_handler**: 권한 관리
122 | - **sentry_flutter**: 에러 모니터링
123 | - **geolocator**: 위치 정보
124 | - **flutter_local_notifications**: 로컬 알림
125 | - **animations, flutter_animate, lottie**: 애니메이션 효과
126 |
127 | ## 5. 코드 규칙 및 패턴
128 |
129 | ### 5.1. 파일 명명 규칙
130 |
131 | - **페이지**: `page.dart` (예: `/pages/my/reviews/page.dart`)
132 | - **화면**: `*_screen.dart` (예: `search_screen.dart`)
133 | - **위젯**: 기능/유형에 따른 이름 (예: `tabling_search_field.dart`)
134 | - **프로바이더**: `provider.dart`
135 | - **뷰모델**: `viewmodel.dart` 또는 `view_model.dart`
136 | - **모델**: `*_model.dart` 또는 `entity.dart`
137 |
138 | ### 5.2. 코드 구조화 패턴
139 |
140 | 1. **기능별 디렉토리 구조**:
141 | - 각 주요 기능은 자체 디렉토리에 캡슐화됨
142 | - 예: `/pages/my/reviews/` - 리뷰 관련 페이지 및 로직
143 |
144 | 2. **Provider-ViewModel 패턴**:
145 | - Provider: 상태 관리 및 의존성 주입
146 | - ViewModel: UI 로직과 상태를 캡슐화
147 | - 예: `provider.dart` + `viewmodel.dart`
148 |
149 | 3. **이벤트 기반 통신**:
150 | - 화면 간 통신은 이벤트 기반 패턴 사용
151 | - `ScreenEvent` 클래스를 통한 일관된 이벤트 처리
152 | - 상위 컴포넌트에 이벤트 위임 (콜백 함수)
153 |
154 | ## 6. 개발 워크플로우
155 |
156 | ### 6.1. 새로운 기능 개발
157 |
158 | 1. **모델 정의**:
159 | - 필요한 데이터 모델이 `tabling_models`에 있는지 확인
160 | - 없다면 추가 요청 또는 로컬 모델 정의
161 |
162 | 2. **API 통합**:
163 | - `tabling_rest_client`를 통해 API 호출
164 | - 리포지토리 패턴으로 데이터 접근 추상화
165 |
166 | 3. **상태 관리 설정**:
167 | - Riverpod Provider 및 ViewModel 구현
168 | - 상태 및 비즈니스 로직 구현
169 |
170 | 4. **UI 구현**:
171 | - Page 컴포넌트 구현 (라우팅 진입점)
172 | - 필요한 Screen 컴포넌트 구현 또는 재사용
173 | - `tabling_ui` 위젯 활용
174 |
175 | ### 6.2. 환경 설정
176 |
177 | - **Flavors**: 개발, 스테이징, 프로덕션 환경 구분
178 | - **환경별 진입점**: `main_develop.dart`, `main_staging.dart`, `main_production.dart`
179 | - **firebase_app_check**: 인증된 앱 요청 보장
180 |
181 | ## 7. UI/UX 가이드라인
182 |
183 | ### 7.1. 디자인 시스템
184 |
185 | tabling_ui 패키지는 다음과 같은 디자인 시스템 요소를 제공합니다:
186 |
187 | - **토큰 시스템**:
188 | - `/tokens/core`: 색상, 간격, 타이포그래피 등 기본 디자인 토큰
189 | - 일관된 디자인 적용을 위해 하드코딩된 값 대신 토큰 사용 권장
190 |
191 | - **위젯 컴포넌트**:
192 | - `/widgets`: 버튼, 텍스트 필드, 카드 등 재사용 가능한 UI 컴포넌트
193 | - 커스텀 UI보다 기존 컴포넌트 재사용 권장
194 |
195 | - **테마**:
196 | - `/theme`: 앱 전체 테마 및 스타일링
197 | - 앱 테마를 일관되게 유지하기 위해 테마 설정 준수
198 |
199 | ### 7.2. 반응형 디자인
200 |
201 | - **LayoutBuilder 및 MediaQuery** 활용
202 | - 다양한 화면 크기에 적응하는 UI 설계
203 | - 기기 방향 변경 대응
204 |
205 | ## 8. 테스팅 및 품질 보증
206 |
207 | ### 8.1. 테스트 유형
208 |
209 | - **단위 테스트**: `/test` 디렉토리
210 | - **통합 테스트**: `/integration_test` 디렉토리
211 | - **위젯 테스트**: UI 컴포넌트 테스트
212 |
213 | ### 8.2. 코드 품질 도구
214 |
215 | - **analysis_options.yaml**: 린트 규칙 정의
216 | - **custom_lint**: 추가 린트 규칙
217 | - **lefthook.yml**: 커밋 전 검사
218 |
219 | ## 9. 배포 및 릴리스
220 |
221 | ### 9.1. CI/CD
222 |
223 | - **codemagic.yaml**: CI/CD 파이프라인 구성
224 | - **GitHub Actions**: 추가 자동화 작업
225 |
226 | ### 9.2. 버전 관리
227 |
228 | - **pubspec.yaml**: 앱 버전 및 빌드 번호 관리
229 | - **melos.yaml**: 모노레포 패키지 버전 관리
230 |
231 | ## 10. 문제 해결 및 디버깅
232 |
233 | ### 10.1. 로깅
234 |
235 | - **talker_flutter**: 고급 로깅 및 디버깅 도구
236 | - **sentry_flutter**: 에러 모니터링 및 보고
237 |
238 | ### 10.2. 성능 모니터링
239 |
240 | - **firebase_performance**: 앱 성능 모니터링
241 | - **firebase_crashlytics**: 크래시 보고 및 분석
242 |
243 | ## 11. 공통 개발 작업
244 |
245 | ### 11.1. 새 페이지 추가
246 |
247 | 1. `/pages/[feature]/` 디렉토리 생성
248 | 2. `page.dart` 파일 구현 (Page 컴포넌트)
249 | 3. Provider 및 ViewModel 구현
250 | 4. `/router/` 디렉토리에 라우트 등록
251 |
252 | ### 11.2. 기존 페이지 수정
253 |
254 | 1. 관련 Page 컴포넌트 식별
255 | 2. 필요한 상태 및 이벤트 핸들러 수정
256 | 3. 필요에 따라 Screen 또는 Widget 컴포넌트 수정
257 |
258 | ### 11.3. 새 API 통합
259 |
260 | 1. `tabling_rest_client`에서 API 엔드포인트 확인/추가
261 | 2. 필요한 모델 업데이트/추가
262 | 3. Repository 구현/수정
263 | 4. Provider 및 ViewModel 업데이트
264 |
265 | ## 12. 결론
266 |
267 | tabling_user 프로젝트는 Page -> Screen -> Widget 패턴과 Riverpod를 사용한 상태 관리를 통해 구조화된 Flutter 앱입니다. 다양한 내부 패키지를 활용하여 일관된 UI/UX 및 기능을 제공합니다. 이 가이드를 참고하여 프로젝트의 구조를 이해하고 효율적으로 개발에 기여할 수 있습니다.
268 |
--------------------------------------------------------------------------------
/src/content/docs/part1/first-project.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: 첫 프로젝트 생성 및 실행
3 | ---
4 | import { FileTree } from '@astrojs/starlight/components';
5 |
6 | 이제 Flutter SDK와 개발 환경이 설정되었으므로, 첫 번째 Flutter 프로젝트를 생성하고 실행해 보겠습니다.
7 |
8 | ## 프로젝트 생성하기
9 |
10 | Flutter 프로젝트는 다양한 방법으로 생성할 수 있습니다. 터미널을 사용하거나 Visual Studio Code 또는 Android Studio와 같은 IDE를 사용할 수 있습니다.
11 |
12 | ### 터미널을 이용한 생성
13 |
14 | 1. 터미널을 열고 프로젝트를 생성할 디렉토리로 이동합니다.
15 | 2. 다음 명령어를 실행하여 새 Flutter 프로젝트를 생성합니다:
16 |
17 | ```bash
18 | flutter create my_first_app
19 | ```
20 |
21 | 이 명령어는 `my_first_app`이라는 이름의 새 Flutter 프로젝트를 생성합니다.
22 |
23 | 3. 프로젝트 디렉토리로 이동합니다:
24 |
25 | ```bash
26 | cd my_first_app
27 | ```
28 |
29 | ### VS Code를 이용한 생성
30 |
31 | 1. Visual Studio Code를 실행합니다.
32 | 2. Command Palette(`Ctrl+Shift+P` 또는 `Cmd+Shift+P`)를 열고 "Flutter: New Project"를 입력하고 선택합니다.
33 | 3. 프로젝트 이름을 입력합니다 (예: "my_first_app").
34 | 4. 프로젝트를 저장할 디렉토리를 선택합니다.
35 | 5. VS Code가 자동으로 새 Flutter 프로젝트를 생성합니다.
36 |
37 | ### Android Studio를 이용한 생성
38 |
39 | 1. Android Studio를 실행합니다.
40 | 2. "Create New Flutter Project"를 선택합니다.
41 | 3. "Flutter Application"을 선택하고 "Next"를 클릭합니다.
42 | 4. 프로젝트 이름과 저장 위치, Flutter SDK 경로를 지정하고 "Next"를 클릭합니다.
43 | 5. 추가 정보를 입력하고 "Finish"를 클릭합니다.
44 |
45 | ## 프로젝트 구조 탐색
46 |
47 | Flutter 프로젝트가 생성되면, 다음과 같은 기본 파일 구조가 만들어집니다:
48 |
49 |
50 |
51 |
52 | - my_first_app/
53 | - .dart_tool/ # Dart 도구 관련 파일
54 | - .idea/ # IDE 설정 (Android Studio)
55 | - android/ # 안드로이드 특화 코드
56 | - build/ # 빌드 출력 파일
57 | - ios/ # iOS 특화 코드
58 | - lib/ # Dart 코드
59 | - main.dart # 앱의 진입점
60 | - linux/ # Linux 특화 코드
61 | - macos/ # macOS 특화 코드
62 | - test/ # 테스트 코드
63 | - web/ # 웹 특화 코드
64 | - windows/ # Windows 특화 코드
65 | - .gitignore # Git 무시 파일
66 | - .metadata # Flutter 메타데이터
67 | - analysis_options.yaml # Dart 분석 설정
68 | - pubspec.lock # 의존성 버전 잠금 파일
69 | - pubspec.yaml # 프로젝트 설정 및 의존성
70 | - README.md # 프로젝트 설명
71 |
72 |
73 |
74 |
75 | 이 중에서 가장 중요한 파일은 다음과 같습니다:
76 |
77 | - **lib/main.dart**: 앱의 메인 코드가 위치한 파일입니다.
78 | - **pubspec.yaml**: 앱의 메타데이터와 의존성을 정의하는 파일입니다.
79 | - **android/**, **ios/**: 플랫폼별 설정이 포함된 디렉토리입니다.
80 |
81 | ## 기본 앱 코드 이해하기
82 |
83 | 기본적으로 생성된 `lib/main.dart` 파일을 살펴보겠습니다. 이 파일은 간단한 카운터 앱을 구현하고 있습니다:
84 |
85 | ```dart
86 | import 'package:flutter/material.dart';
87 |
88 | void main() {
89 | runApp(const MyApp());
90 | }
91 |
92 | class MyApp extends StatelessWidget {
93 | const MyApp({super.key});
94 |
95 | @override
96 | Widget build(BuildContext context) {
97 | return MaterialApp(
98 | title: 'Flutter Demo',
99 | theme: ThemeData(
100 | colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
101 | useMaterial3: true,
102 | ),
103 | home: const MyHomePage(title: 'Flutter Demo Home Page'),
104 | );
105 | }
106 | }
107 |
108 | class MyHomePage extends StatefulWidget {
109 | const MyHomePage({super.key, required this.title});
110 |
111 | final String title;
112 |
113 | @override
114 | State createState() => _MyHomePageState();
115 | }
116 |
117 | class _MyHomePageState extends State {
118 | int _counter = 0;
119 |
120 | void _incrementCounter() {
121 | setState(() {
122 | _counter++;
123 | });
124 | }
125 |
126 | @override
127 | Widget build(BuildContext context) {
128 | return Scaffold(
129 | appBar: AppBar(
130 | backgroundColor: Theme.of(context).colorScheme.inversePrimary,
131 | title: Text(widget.title),
132 | ),
133 | body: Center(
134 | child: Column(
135 | mainAxisAlignment: MainAxisAlignment.center,
136 | children: [
137 | const Text(
138 | 'You have pushed the button this many times:',
139 | ),
140 | Text(
141 | '$_counter',
142 | style: Theme.of(context).textTheme.headlineMedium,
143 | ),
144 | ],
145 | ),
146 | ),
147 | floatingActionButton: FloatingActionButton(
148 | onPressed: _incrementCounter,
149 | tooltip: 'Increment',
150 | child: const Icon(Icons.add),
151 | ),
152 | );
153 | }
154 | }
155 | ```
156 |
157 | 이 코드의 주요 구성 요소를 간략하게 설명하면:
158 |
159 | 1. **main() 함수**: 앱의 진입점으로, `runApp()`을 호출하여 앱을 시작합니다.
160 | 2. **MyApp 클래스**: 앱의 전체적인 테마와 구조를 정의하는 StatelessWidget입니다.
161 | 3. **MyHomePage 클래스**: 앱의 홈 화면을 정의하는 StatefulWidget입니다.
162 | 4. **\_MyHomePageState 클래스**: 홈 화면의 상태를 관리하는 State 클래스입니다.
163 |
164 | ## 앱 실행하기
165 |
166 | 이제 생성된 Flutter 앱을 실행해 보겠습니다.
167 |
168 | ### 터미널에서 실행
169 |
170 | 프로젝트 디렉토리에서 다음 명령어를 실행합니다:
171 |
172 | ```bash
173 | flutter run
174 | ```
175 |
176 | 이 명령어는 연결된 기기나 에뮬레이터에서 앱을 실행합니다. 여러 기기가 연결되어 있다면, 실행할 기기를 선택하라는 메시지가 표시됩니다.
177 |
178 | 특정 기기에서 실행하려면 다음과 같이 명령할 수 있습니다:
179 |
180 | ```bash
181 | flutter run -d
182 | ```
183 |
184 | 기기 ID는 `flutter devices` 명령어로 확인할 수 있습니다.
185 |
186 | ### VS Code에서 실행
187 |
188 | 1. VS Code에서 프로젝트를 엽니다.
189 | 2. 하단 상태 표시줄에서 기기 선택기를 클릭하여 실행할 기기를 선택합니다.
190 | 3. F5 키를 누르거나 "Run > Start Debugging"을 선택합니다.
191 |
192 | ### Android Studio에서 실행
193 |
194 | 1. Android Studio에서 프로젝트를 엽니다.
195 | 2. 상단 도구 모음에서 기기 선택기를 사용하여 실행할 기기를 선택합니다.
196 | 3. 실행 버튼(▶)을 클릭합니다.
197 |
198 | ## 앱 수정하기
199 |
200 | 이제 앱을 조금 수정해 보겠습니다. `lib/main.dart` 파일을 열고 다음과 같이 변경해 보세요:
201 |
202 | 1. 앱 제목 변경:
203 |
204 | ```dart
205 | return MaterialApp(
206 | title: '내 첫 번째 Flutter 앱',
207 | // ...
208 | ```
209 |
210 | 2. 홈 화면 타이틀 변경:
211 |
212 | ```dart
213 | home: const MyHomePage(title: '내 첫 번째 Flutter 앱'),
214 | ```
215 |
216 | 3. 카운터 텍스트 변경:
217 |
218 | ```dart
219 | const Text(
220 | '버튼을 눌러 카운터를 증가시키세요:',
221 | ),
222 | ```
223 |
224 | ## 핫 리로드 사용하기
225 |
226 | Flutter의 가장 강력한 기능 중 하나는 핫 리로드입니다. 이는 앱을 다시 시작하지 않고도 코드 변경 사항을 즉시 확인할 수 있게 해줍니다.
227 |
228 | 1. 앱이 실행 중인 상태에서 코드를 수정합니다.
229 | 2. 변경 사항을 저장합니다.
230 | 3. VS Code 또는 Android Studio에서는 자동으로 핫 리로드가 실행됩니다.
231 | 4. 터미널에서 실행 중인 경우, `r` 키를 눌러 핫 리로드를 실행합니다.
232 |
233 | 핫 리로드는 상태를 유지하므로, 예를 들어 카운터 값은 리셋되지 않습니다.
234 |
235 | ## 핫 리스타트 사용하기
236 |
237 | 때로는 변경 사항이 핫 리로드로 적용되지 않을 수 있습니다. 이런 경우 핫 리스타트를 사용할 수 있습니다:
238 |
239 | 1. VS Code 또는 Android Studio에서 핫 리스타트 버튼을 클릭합니다.
240 | 2. 터미널에서 실행 중인 경우, `R` 키(대문자)를 눌러 핫 리스타트를 실행합니다.
241 |
242 | 핫 리스타트는 앱을 다시 시작하지만, 컴파일 과정은 건너뛰므로 일반적인 재시작보다 빠릅니다.
243 |
244 | ## 더 많은 기기에서 실행하기
245 |
246 | Flutter 앱은 다양한 플랫폼에서 실행할 수 있습니다.
247 |
248 | ### 웹에서 실행하기
249 |
250 | 웹 버전을 실행하려면 다음 명령어를 사용합니다:
251 |
252 | ```bash
253 | flutter run -d chrome
254 | ```
255 |
256 | 또는 VS Code와 Android Studio에서 Chrome을 기기로 선택할 수 있습니다.
257 |
258 | ### 데스크톱에서 실행하기
259 |
260 | 데스크톱 버전을 실행하려면 다음 명령어를 사용합니다:
261 |
262 | ```bash
263 | # Windows
264 | flutter run -d windows
265 |
266 | # macOS
267 | flutter run -d macos
268 |
269 | # Linux
270 | flutter run -d linux
271 | ```
272 |
273 | ## 릴리즈 모드로 실행하기
274 |
275 | 기본적으로 `flutter run` 명령어는 디버그 모드로 앱을 실행합니다. 릴리즈 모드로 실행하려면 다음 명령어를 사용합니다:
276 |
277 | ```bash
278 | flutter run --release
279 | ```
280 |
281 | 릴리즈 모드는 디버그 정보가 제거되고 성능이 최적화된 버전입니다.
282 |
283 | ## 앱 빌드하기
284 |
285 | 개발이 완료된 앱을 배포 가능한 형태로 빌드하려면 다음 명령어를 사용합니다:
286 |
287 | ```bash
288 | # Android APK
289 | flutter build apk
290 |
291 | # Android App Bundle
292 | flutter build appbundle
293 |
294 | # iOS
295 | flutter build ios
296 |
297 | # 웹
298 | flutter build web
299 |
300 | # Windows
301 | flutter build windows
302 |
303 | # macOS
304 | flutter build macos
305 |
306 | # Linux
307 | flutter build linux
308 | ```
309 |
310 | ## 결론
311 |
312 | 축하합니다! 첫 번째 Flutter 앱을 성공적으로 생성하고 실행했습니다. 이 기본 앱을 출발점으로 삼아, Flutter의 다양한 위젯과 기능을 탐색하며 더 복잡한 앱을 개발할 수 있습니다.
313 |
314 | 다음 섹션에서는 Flutter 프로젝트의 구조를 더 자세히 살펴보고, 앱 개발에 필요한 주요 개념들을 배워보겠습니다.
315 |
--------------------------------------------------------------------------------
/src/content/docs/part3/widgets.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 위젯 개념과 주요 위젯
3 | ---
4 |
5 | Flutter의 핵심은 위젯(Widget)입니다. Flutter 애플리케이션은 여러 위젯들로 구성되어 있으며, 위젯은 UI의 구성 요소를 나타냅니다. 이 장에서는 Flutter 위젯의 기본 개념과 위젯 시스템의 작동 방식을 알아보겠습니다.
6 |
7 | ## 위젯이란?
8 |
9 | 위젯(Widget)은 Flutter에서 UI를 구성하는 기본 단위입니다. 버튼, 텍스트, 이미지, 레이아웃, 스크롤 등 화면에 보이는 모든 요소는 위젯입니다. Flutter의 철학은 "모든 것이 위젯"이라는 개념에 기반하고 있습니다.
10 |
11 | 위젯은 다음과 같은 특징을 가지고 있습니다:
12 |
13 | 1. **불변성(Immutable)**: 위젯은 생성된 후 변경할 수 없습니다. UI를 변경하려면 새로운 위젯을 생성해야 합니다.
14 | 2. **계층 구조**: 위젯은 트리 구조로 조직되며, 부모 위젯은 자식 위젯을 포함할 수 있습니다.
15 | 3. **선언적 UI**: Flutter는 현재 애플리케이션 상태에 따라 UI가 어떻게 보여야 하는지 선언적으로 정의합니다.
16 | 4. **합성(Composition)**: 작은 위젯들을 조합하여 복잡한 UI를 구성합니다.
17 |
18 | ## Flutter 위젯의 주기
19 |
20 | Flutter 위젯은 생성, 구성, 렌더링의 주기를 거칩니다:
21 |
22 |
23 | 1. **위젯 생성**: 위젯 클래스의 인스턴스가 생성됩니다.
24 | 2. **빌드 메서드 호출**: 위젯의 `build()` 메서드가 호출되어 위젯 트리를 구성합니다.
25 | 3. **요소 트리 생성**: 위젯 트리를 기반으로 Element 트리가 생성되거나 업데이트됩니다.
26 | 4. **RenderObject 생성**: Element 트리에 따라 RenderObject 트리가 생성되거나 업데이트됩니다.
27 | 5. **화면에 렌더링**: RenderObject 트리를 기반으로 UI가 화면에 렌더링됩니다.
28 | 6. **위젯 상태 변경**: 상태 변경 시 위젯이 다시 빌드됩니다.
29 |
30 | ## 위젯 유형
31 |
32 | Flutter 위젯은 크게 두 가지 유형으로 나눌 수 있습니다:
33 |
34 | ### 1. Stateless 위젯
35 |
36 | Stateless 위젯은 내부 상태를 가지지 않는 정적인 위젯입니다. 생성 시 전달받은 속성(properties)만 사용하며, 한 번 빌드되면 변경되지 않습니다. 간단한 UI 요소나 변경이 필요 없는 화면에 적합합니다.
37 |
38 | ```dart
39 | class GreetingWidget extends StatelessWidget {
40 | final String name;
41 |
42 | const GreetingWidget({Key? key, required this.name}) : super(key: key);
43 |
44 | @override
45 | Widget build(BuildContext context) {
46 | return Text('안녕하세요, $name님!');
47 | }
48 | }
49 | ```
50 |
51 | ### 2. Stateful 위젯
52 |
53 | Stateful 위젯은 내부 상태를 가지고 있으며, 상태가 변경되면 UI가 다시 빌드됩니다. 사용자 입력, 네트워크 응답, 시간 경과 등에 따라 변경되는 UI 요소에 적합합니다.
54 |
55 | ```dart
56 | class CounterWidget extends StatefulWidget {
57 | const CounterWidget({Key? key}) : super(key: key);
58 |
59 | @override
60 | _CounterWidgetState createState() => _CounterWidgetState();
61 | }
62 |
63 | class _CounterWidgetState extends State {
64 | int _counter = 0;
65 |
66 | void _incrementCounter() {
67 | setState(() {
68 | _counter++;
69 | });
70 | }
71 |
72 | @override
73 | Widget build(BuildContext context) {
74 | return Column(
75 | mainAxisAlignment: MainAxisAlignment.center,
76 | children: [
77 | Text('카운터: $_counter'),
78 | ElevatedButton(
79 | onPressed: _incrementCounter,
80 | child: Text('증가'),
81 | ),
82 | ],
83 | );
84 | }
85 | }
86 | ```
87 |
88 | ## 위젯 트리
89 |
90 | Flutter 애플리케이션의 UI는 위젯 트리로 표현됩니다. 모든 Flutter 앱은 루트 위젯에서 시작하여 자식 위젯들로 구성된 트리 구조를 가집니다.
91 |
92 | 위젯 트리의 특징:
93 |
94 | 1. **부모-자식 관계**: 위젯은 하나의 부모와 여러 자식을 가질 수 있습니다.
95 | 2. **단방향 데이터 흐름**: 데이터는 부모에서 자식으로 흐릅니다.
96 | 3. **렌더링 최적화**: Flutter는 변경된 위젯만 다시 빌드하여 성능을 최적화합니다.
97 |
98 | ## 위젯의 세 가지 트리
99 |
100 | Flutter는 UI 렌더링을 위해 세 가지 트리를 관리합니다:
101 |
102 | 1. **Widget 트리**: UI의 설계도로, 사용자가 작성한 위젯 클래스의 인스턴스들로 구성됩니다.
103 | 2. **Element 트리**: Widget과 RenderObject를 연결하는 중간 계층으로, 위젯의 수명 주기를 관리합니다.
104 | 3. **RenderObject 트리**: 실제 화면에 그려지는 객체들을 표현하며, 레이아웃 계산과 페인팅을 담당합니다.
105 |
106 | ## Flutter 위젯의 구성 방식
107 |
108 | Flutter 위젯은 합성(Composition)을 통해 구성됩니다. 작은 위젯들을 조합하여 복잡한 UI를 만들 수 있습니다.
109 |
110 | ```dart
111 | Scaffold(
112 | appBar: AppBar(
113 | title: Text('Flutter 앱'),
114 | actions: [
115 | IconButton(
116 | icon: Icon(Icons.settings),
117 | onPressed: () {},
118 | ),
119 | ],
120 | ),
121 | body: Center(
122 | child: Column(
123 | mainAxisAlignment: MainAxisAlignment.center,
124 | children: [
125 | Text('Hello, Flutter!'),
126 | SizedBox(height: 20),
127 | ElevatedButton(
128 | onPressed: () {},
129 | child: Text('버튼'),
130 | ),
131 | ],
132 | ),
133 | ),
134 | floatingActionButton: FloatingActionButton(
135 | onPressed: () {},
136 | child: Icon(Icons.add),
137 | ),
138 | )
139 | ```
140 |
141 | 이 예제에서 `Scaffold`, `AppBar`, `Text`, `Center`, `Column` 등 여러 위젯들이 중첩되어 하나의 화면을 구성합니다.
142 |
143 | ## Flutter 기본 위젯의 분류
144 |
145 | Flutter 위젯은 기능에 따라 여러 카테고리로 분류할 수 있습니다:
146 |
147 | ### 구조적 위젯
148 |
149 | 애플리케이션의 구조를 정의하는 위젯입니다:
150 |
151 | - **MaterialApp**: Material Design 앱의 진입점
152 | - **CupertinoApp**: iOS 스타일 앱의 진입점
153 | - **Scaffold**: 기본 앱 구조 제공 (앱바, 드로워, 바텀 시트 등)
154 | - **AppBar**: 앱 상단의 앱 바
155 |
156 | ### 시각적 위젯
157 |
158 | 화면에 콘텐츠를 표시하는 위젯입니다:
159 |
160 | - **Text**: 텍스트 표시
161 | - **Image**: 이미지 표시
162 | - **Icon**: 아이콘 표시
163 | - **Card**: 둥근 모서리와 그림자가 있는 카드
164 |
165 | ### 레이아웃 위젯
166 |
167 | 위젯들을 배치하고 정렬하는 위젯입니다:
168 |
169 | - **Container**: 패딩, 마진, 배경색, 크기 등을 설정할 수 있는 범용 컨테이너
170 | - **Row/Column**: 위젯을 가로/세로로 배열
171 | - **Stack**: 위젯들을 겹쳐서 배치
172 | - **ListView**: 스크롤 가능한 목록
173 |
174 | ### 입력 위젯
175 |
176 | 사용자 입력을 받는 위젯입니다:
177 |
178 | - **TextField**: 텍스트 입력
179 | - **Checkbox**: 체크박스
180 | - **Radio**: 라디오 버튼
181 | - **Slider**: 슬라이더
182 |
183 | ### 상호작용 위젯
184 |
185 | 사용자 상호작용을 처리하는 위젯입니다:
186 |
187 | - **GestureDetector**: 다양한 제스처 인식
188 | - **InkWell**: 터치 효과가 있는 영역
189 | - **Draggable**: 드래그 가능한 위젯
190 |
191 | ### 애니메이션 위젯
192 |
193 | 애니메이션 효과를 제공하는 위젯입니다:
194 |
195 | - **AnimatedContainer**: 속성 변경 시 애니메이션 효과
196 | - **Hero**: 화면 전환 시 애니메이션 효과
197 | - **FadeTransition**: 페이드 인/아웃 효과
198 |
199 | ## 위젯 속성 전달
200 |
201 | Flutter 위젯은 생성자를 통해 속성을 전달받아 UI를 구성합니다:
202 |
203 | ```dart
204 | Container(
205 | width: 200,
206 | height: 100,
207 | margin: EdgeInsets.all(10),
208 | padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
209 | decoration: BoxDecoration(
210 | color: Colors.blue,
211 | borderRadius: BorderRadius.circular(8),
212 | boxShadow: [
213 | BoxShadow(
214 | color: Colors.black26,
215 | offset: Offset(0, 2),
216 | blurRadius: 6,
217 | ),
218 | ],
219 | ),
220 | child: Center(
221 | child: Text(
222 | 'Flutter',
223 | style: TextStyle(
224 | color: Colors.white,
225 | fontSize: 24,
226 | fontWeight: FontWeight.bold,
227 | ),
228 | ),
229 | ),
230 | )
231 | ```
232 |
233 | 이 예제에서 `Container`, `Center`, `Text` 위젯은 각각 다양한 속성(width, height, decoration, style 등)을 전달받아 특정한 모양과 스타일을 가진 UI를 생성합니다.
234 |
235 | ## 위젯 키(Key)
236 |
237 | 위젯 키는 Flutter가 위젯 트리에서 위젯을 식별하는 데 사용됩니다. 특히 동적으로 변경되는 위젯 목록에서 중요한 역할을 합니다.
238 |
239 | ```dart
240 | ListView.builder(
241 | itemCount: items.length,
242 | itemBuilder: (context, index) {
243 | return ListTile(
244 | key: ValueKey(items[index].id), // 고유 ID를 키로 사용
245 | title: Text(items[index].title),
246 | );
247 | },
248 | )
249 | ```
250 |
251 | 위젯 키의 주요 종류:
252 |
253 | - **ValueKey**: 단일 값을 기반으로 한 키
254 | - **ObjectKey**: 객체 식별자를 기반으로 한 키
255 | - **UniqueKey**: 매번 고유한 키를 생성
256 | - **GlobalKey**: 전역적으로 접근 가능한 키, 위젯의 상태에 접근하거나 위젯의 크기/위치를 파악하는 데 사용
257 |
258 | ## 위젯 제약 조건(Constraints)
259 |
260 | Flutter의 레이아웃 시스템은 부모 위젯이 자식 위젯에게 제약 조건(constraints)을 전달하고, 자식 위젯은 이 제약 조건 내에서 자신의 크기를 결정하는 방식으로 작동합니다.
261 |
262 | 제약 조건은 최소/최대 너비와 높이로 구성되며, 자식 위젯은 이 범위 내에서 크기를 결정합니다:
263 |
264 | ```dart
265 | ConstrainedBox(
266 | constraints: BoxConstraints(
267 | minWidth: 100,
268 | maxWidth: 200,
269 | minHeight: 50,
270 | maxHeight: 100,
271 | ),
272 | child: Container(
273 | color: Colors.blue,
274 | width: 150, // minWidth와 maxWidth 사이의 값
275 | height: 75, // minHeight와 maxHeight 사이의 값
276 | ),
277 | )
278 | ```
279 |
280 | ## 위젯 렌더링 과정
281 |
282 | Flutter의 위젯 렌더링 과정은 다음과 같습니다:
283 |
284 | 1. **레이아웃 단계**: 부모 위젯이 자식 위젯에게 제약 조건을 전달하고, 자식 위젯은 자신의 크기를 결정합니다.
285 | 2. **페인팅 단계**: 위젯의 외관이 렌더링됩니다.
286 | 3. **합성 단계**: 렌더링된 레이어들이 화면에 합성됩니다.
287 |
288 |
289 | ## 결론
290 |
291 | Flutter의 위젯 시스템은 UI 구성을 위한 강력하고 유연한 방법을 제공합니다. "모든 것이 위젯"이라는 철학을 통해 일관된 방식으로 복잡한 UI를 구축할 수 있습니다. 위젯의 불변성, 선언적 특성, 합성 패턴은 Flutter가 효율적이고 예측 가능한 UI 프레임워크가 되는 데 중요한 역할을 합니다.
292 |
293 | 다음 장에서는 Stateless 위젯과 Stateful 위젯에 대해 더 자세히 알아보겠습니다.
294 |
--------------------------------------------------------------------------------
/src/content/docs/appendix/tools.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 개발 도구와 링크 모음
3 | ---
4 |
5 | Flutter 개발을 효과적으로 수행하기 위해서는 적절한 도구를 활용하고, 좋은 학습 자료를 참고하는 것이 중요합니다. 이 페이지에서는 Flutter 개발자에게 유용한 도구와 리소스들을 소개합니다.
6 |
7 | ## 개발 도구
8 |
9 | ### IDE 및 에디터
10 |
11 | | 도구 | 설명 | 링크 |
12 | | ------------------ | --------------------------------------------------------------- | ------------------------------------------------ |
13 | | **VS Code** | 가벼운 에디터로 Flutter 확장 프로그램 지원 | [다운로드](https://code.visualstudio.com/) |
14 | | **Android Studio** | Google의 공식 Android 개발 IDE, 전문적인 Flutter 개발 환경 제공 | [다운로드](https://developer.android.com/studio) |
15 |
16 | ### VS Code 필수 확장 프로그램
17 |
18 | | 확장 프로그램 | 설명 |
19 | | ---------------------------- | ------------------------------------ |
20 | | **Flutter** | Flutter SDK와 통합된 지원 제공 |
21 | | **Dart** | Dart 언어 지원 |
22 | | **Awesome Flutter Snippets** | 자주 사용되는 Flutter 코드 조각 제공 |
23 | | **Flutter Widget Snippets** | 위젯 코드 스니펫 제공 |
24 | | **Pubspec Assist** | pubspec.yaml 파일 관리 도우미 |
25 | | **Git History** | Git 이력 관리 시각화 |
26 | | **Error Lens** | 인라인 오류 하이라이팅 |
27 |
28 | ### 디버깅 및 성능 분석 도구
29 |
30 | | 도구 | 설명 | 링크 |
31 | | -------------------- | ---------------------------------- | ----------------------------------------------------------- |
32 | | **Flutter DevTools** | 성능 및 디버깅 도구 모음 | [문서](https://docs.flutter.dev/development/tools/devtools) |
33 | | **Dart Observatory** | 메모리 및 CPU 프로파일링 | [문서](https://dart.dev/tools/dart-devtools) |
34 | | **Flipper** | Facebook의 모바일 앱 디버깅 플랫폼 | [웹사이트](https://fbflipper.com/) |
35 | | **Sentry** | 실시간 에러 추적 | [웹사이트](https://sentry.io/) |
36 |
37 | ### CI/CD 도구
38 |
39 | | 도구 | 설명 | 링크 |
40 | | ------------------ | ----------------------- | ------------------------------------------ |
41 | | **Codemagic** | Flutter 특화 CI/CD 도구 | [웹사이트](https://codemagic.io/) |
42 | | **Bitrise** | 모바일 앱 CI/CD 플랫폼 | [웹사이트](https://www.bitrise.io/) |
43 | | **GitHub Actions** | GitHub 내장 CI/CD | [문서](https://docs.github.com/en/actions) |
44 | | **fastlane** | 앱 자동화 배포 도구 | [웹사이트](https://fastlane.tools/) |
45 |
46 | ## 학습 자료
47 |
48 | ### 공식 문서
49 |
50 | | 리소스 | 설명 | 링크 |
51 | | -------------------------- | ------------------------- | --------------------------------------------- |
52 | | **Flutter 공식 문서** | 상세한 API 문서 및 가이드 | [웹사이트](https://docs.flutter.dev/) |
53 | | **Dart 공식 문서** | Dart 언어 문서 | [웹사이트](https://dart.dev/guides) |
54 | | **Material Design 가이드** | 구글의 디자인 가이드라인 | [웹사이트](https://material.io/design) |
55 | | **Flutter 쿡북** | 일반적인 문제 해결 방법 | [웹사이트](https://docs.flutter.dev/cookbook) |
56 |
57 | ### 추천 블로그 및 뉴스레터
58 |
59 | | 리소스 | 설명 | 링크 |
60 | | --------------------- | ----------------------------- | -------------------------------------------- |
61 | | **Flutter Medium** | Flutter 팀의 공식 블로그 | [링크](https://medium.com/flutter) |
62 | | **Flutter Community** | 커뮤니티 기여 아티클 | [링크](https://medium.com/flutter-community) |
63 | | **Flutter Weekly** | 주간 Flutter 뉴스 및 튜토리얼 | [링크](https://flutterweekly.net/) |
64 | | **Flutter Awesome** | 큐레이션된 패키지 및 가이드 | [링크](https://flutterawesome.com/) |
65 | | **Flutter Force** | 뉴스레터 및 팁 | [링크](https://twitter.com/flutterforce) |
66 |
67 | ### 영상 자료
68 |
69 | | 리소스 | 설명 | 링크 |
70 | | ----------------------- | ------------------------- | -------------------------------------------------- |
71 | | **Flutter 공식 유튜브** | Flutter 팀의 영상 | [링크](https://www.youtube.com/c/flutterdev) |
72 | | **The Flutter Way** | 고품질 UI 구현 튜토리얼 | [링크](https://www.youtube.com/c/TheFlutterWay) |
73 | | **Flutter Explained** | 개념 설명 및 실습 | [링크](https://www.youtube.com/c/FlutterExplained) |
74 | | **Reso Coder** | 아키텍처 및 고급 주제 | [링크](https://www.youtube.com/c/ResoCoder) |
75 | | **Code With Andrea** | 심층적인 Flutter 튜토리얼 | [링크](https://www.youtube.com/c/CodeWithAndrea) |
76 |
77 | ### 온라인 코스
78 |
79 | | 리소스 | 설명 | 링크 |
80 | | ---------------------------------------- | ------------------- | ---------------------------------------------------------------------------------- |
81 | | **Flutter 부트캠프** | Udemy 인기 코스 | [링크](https://www.udemy.com/course/flutter-bootcamp-with-dart/) |
82 | | **Flutter 앱 개발 - Zero to Mastery** | 전체 개발 과정 학습 | [링크](https://www.udemy.com/course/flutter-made-easy-zero-to-mastery/) |
83 | | **Dart and Flutter: 완전 개발자 가이드** | Academind 코스 | [링크](https://www.udemy.com/course/learn-flutter-dart-to-build-ios-android-apps/) |
84 |
85 | ## 커뮤니티
86 |
87 | | 커뮤니티 | 설명 | 링크 |
88 | | ---------------------------- | ----------------------- | ------------------------------------------------------------------ |
89 | | **Stack Overflow** | 질문 및 답변 | [Flutter 태그](https://stackoverflow.com/questions/tagged/flutter) |
90 | | **GitHub Discussions** | Flutter 리포지토리 토론 | [링크](https://github.com/flutter/flutter/discussions) |
91 | | **Discord Flutter 커뮤니티** | 실시간 채팅 | [초대 링크](https://discord.gg/flutter) |
92 | | **Reddit Flutter** | 포럼 토론 | [링크](https://www.reddit.com/r/FlutterDev/) |
93 |
94 | ## 유용한 패키지 모음 사이트
95 |
96 | | 사이트 | 설명 | 링크 |
97 | | -------------------- | ------------------------------- | ----------------------------------- |
98 | | **Pub.dev** | 공식 Dart/Flutter 패키지 저장소 | [링크](https://pub.dev/) |
99 | | **Flutter Gems** | 큐레이션된 Flutter 패키지 | [링크](https://fluttergems.dev/) |
100 | | **It's All Widgets** | Flutter로 제작된 앱 사례 | [링크](https://itsallwidgets.com/) |
101 | | **Flutter Awesome** | 패키지 및 앱 모음 | [링크](https://flutterawesome.com/) |
102 |
103 | ## 문제 해결 자료
104 |
105 | | 리소스 | 설명 | 링크 |
106 | | ------------------------- | ---------------------- | ------------------------------------------------------------------------------- |
107 | | **Flutter GitHub Issues** | 알려진 이슈 및 해결책 | [링크](https://github.com/flutter/flutter/issues) |
108 | | **Flutter Doctor 가이드** | 환경 문제 진단 및 해결 | [문서](https://docs.flutter.dev/get-started/install/windows#run-flutter-doctor) |
109 | | **Flutter 포럼** | 공식 포럼 | [링크](https://flutter.dev/community) |
110 |
111 | ## 코드 품질 및 분석 도구
112 |
113 | | 도구 | 설명 | 링크 |
114 | | ------------------ | ----------------------- | --------------------------------------------------------- |
115 | | **Dart Analyzer** | 코드 정적 분석 도구 | [문서](https://dart.dev/guides/language/analysis-options) |
116 | | **Effective Dart** | Dart 코딩 스타일 가이드 | [문서](https://dart.dev/guides/language/effective-dart) |
117 | | **Flutter Lints** | 권장 린트 규칙 | [패키지](https://pub.dev/packages/flutter_lints) |
118 |
--------------------------------------------------------------------------------
/src/content/docs/part2/basic-syntax.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 기본 문법 및 변수
3 | ---
4 |
5 | ## Dart 프로그램의 구조
6 |
7 | Dart 프로그램은 최상위 함수, 변수, 클래스 등으로 구성됩니다. 모든 Dart 프로그램은 `main()` 함수에서 시작합니다.
8 |
9 | ```dart
10 | // 가장 기본적인 Dart 프로그램
11 | void main() {
12 | print('안녕하세요, Dart!');
13 | }
14 | ```
15 |
16 | `main()` 함수는 프로그램의 진입점이며, `void`는 반환 값이 없음을 의미합니다. 커맨드라인에서 인자를 받을 때는 다음과 같이 작성할 수 있습니다.
17 |
18 | ```dart
19 | void main(List arguments) {
20 | print('프로그램 인자: $arguments');
21 | }
22 | ```
23 |
24 | ## 주석
25 |
26 | Dart에서는 세 가지 유형의 주석을 사용할 수 있습니다.
27 |
28 | ```dart
29 | // 한 줄 주석
30 |
31 | /*
32 | 여러 줄 주석
33 | 여러 줄에 걸쳐 작성할 수 있습니다.
34 | */
35 |
36 | /// 문서화 주석
37 | /// 다트독(dartdoc) 도구가 API 문서를 생성할 때 사용합니다.
38 | /// 클래스, 함수, 변수 등의 설명을 작성할 때 유용합니다.
39 | ```
40 |
41 | ## 기본 데이터 타입
42 |
43 | Dart는 다음과 같은 기본 데이터 타입을 제공합니다:
44 |
45 | ```dart
46 | // 숫자 타입
47 | int integerValue = 42; // 정수
48 | double doubleValue = 3.14; // 실수
49 | num numValue = 10; // int나 double의 상위 타입
50 |
51 | // 문자열 타입
52 | String greeting = '안녕하세요';
53 |
54 | // 불리언 타입
55 | bool isTrue = true;
56 | bool isFalse = false;
57 |
58 | // 리스트 (배열)
59 | List numbers = [1, 2, 3, 4, 5];
60 |
61 | // 맵 (key-value 쌍)
62 | Map person = {
63 | 'name': '홍길동',
64 | 'age': 30,
65 | 'isStudent': false
66 | };
67 |
68 | // 집합 (중복 없는 컬렉션)
69 | Set uniqueNames = {'홍길동', '김철수', '이영희'};
70 | ```
71 |
72 | ## 변수 선언
73 |
74 | Dart에서는 다양한 방법으로 변수를 선언할 수 있습니다.
75 |
76 | ### var
77 |
78 | 타입을 명시적으로 선언하지 않고, 초기값에서 타입을 추론합니다.
79 |
80 | ```dart
81 | var name = '홍길동'; // String으로 추론
82 | var age = 30; // int로 추론
83 | var height = 175.5; // double로 추론
84 |
85 | // 타입이 추론된 후에는 다른 타입의 값을 할당할 수 없습니다.
86 | name = '김철수'; // 가능 (String → String)
87 | // name = 42; // 오류 (String → int)
88 | ```
89 |
90 | ### 명시적 타입
91 |
92 | 변수의 타입을 명시적으로 선언합니다.
93 |
94 | ```dart
95 | String name = '홍길동';
96 | int age = 30;
97 | double height = 175.5;
98 | ```
99 |
100 | ### final과 const
101 |
102 | 한 번 할당하면 변경할 수 없는 상수 변수를 선언합니다.
103 |
104 | ```dart
105 | // final: 런타임에 값이 결정되는 상수
106 | final String name = '홍길동';
107 | final currentTime = DateTime.now(); // 타입 추론 가능
108 |
109 | // const: 컴파일 타임에 값이 결정되는 상수
110 | const int maxUsers = 100;
111 | const double pi = 3.14159;
112 |
113 | // name = '김철수'; // 오류: final 변수는 재할당 불가
114 | // maxUsers = 200; // 오류: const 변수는 재할당 불가
115 | ```
116 |
117 | `final`과 `const`의 차이점:
118 |
119 | - `final`: 런타임에 값이 결정됩니다. 런타임에 계산되는 값도 가능합니다.
120 | - `const`: 컴파일 타임에 값이 결정됩니다. 컴파일 시점에 알 수 있는 상수값만 가능합니다.
121 |
122 | ```dart
123 | // 런타임 값을 사용하는 예
124 | final now = DateTime.now(); // 가능
125 | // const today = DateTime.now(); // 오류: 컴파일 시점에 값을 알 수 없음
126 | ```
127 |
128 | ### late
129 |
130 | `late` 키워드는 변수를 나중에 초기화할 것임을 나타냅니다. Null 안전성이 도입된 Dart 2.12 이후에 유용합니다.
131 |
132 | ```dart
133 | late String name;
134 |
135 | void initName() {
136 | name = '홍길동'; // 나중에 값 할당
137 | }
138 |
139 | void main() {
140 | initName();
141 | print(name); // '홍길동'
142 |
143 | // late 변수는 초기화 전에 접근하면 런타임 오류 발생
144 | late String address;
145 | // print(address); // 오류: 초기화되지 않은 late 변수에 접근
146 | }
147 | ```
148 |
149 | ### 동적 타입 (dynamic)
150 |
151 | `dynamic` 타입은 변수의 타입을 런타임까지 확정하지 않습니다. 타입 안전성이 필요하지 않을 때 사용합니다.
152 |
153 | ```dart
154 | dynamic value = '문자열';
155 | print(value); // '문자열'
156 |
157 | value = 42;
158 | print(value); // 42
159 |
160 | value = true;
161 | print(value); // true
162 | ```
163 |
164 | ## 문자열
165 |
166 | Dart에서는 작은따옴표(`'`) 또는 큰따옴표(`"`)를 사용하여 문자열을 생성할 수 있습니다.
167 |
168 | ```dart
169 | String _single = '작은따옴표 문자열';
170 | String _double = "큰따옴표 문자열";
171 | ```
172 |
173 | ### 문자열 보간(Interpolation)
174 |
175 | 문자열 내에서 변수나 표현식을 사용할 수 있습니다.
176 |
177 | ```dart
178 | String name = '홍길동';
179 | int age = 30;
180 |
181 | // $변수명 형태로 변수 값을 포함할 수 있습니다.
182 | String message = '제 이름은 $name이고, 나이는 $age살입니다.';
183 |
184 | // ${표현식} 형태로 표현식 결과를 포함할 수 있습니다.
185 | String ageNextYear = '내년에는 ${age + 1}살이 됩니다.';
186 | ```
187 |
188 | ### 여러 줄 문자열
189 |
190 | 여러 줄에 걸친 문자열은 삼중 따옴표(`'''` 또는 `"""`)를 사용합니다.
191 |
192 | ```dart
193 | String multiLine = '''
194 | 이것은
195 | 여러 줄에 걸친
196 | 문자열입니다.
197 | ''';
198 |
199 | String anotherMultiLine = """
200 | 이것도
201 | 여러 줄에 걸친
202 | 문자열입니다.
203 | """;
204 | ```
205 |
206 | ### 원시 문자열 (Raw String)
207 |
208 | 문자열 앞에 `r`을 붙이면 이스케이프 시퀀스를 처리하지 않는 원시 문자열이 됩니다.
209 |
210 | ```dart
211 | String escaped = 'C:\\Program Files\\Dart'; // 이스케이프 시퀀스 사용
212 | String raw = r'C:\Program Files\Dart'; // 원시 문자열 (이스케이프 처리 안 함)
213 | ```
214 |
215 | ## 연산자
216 |
217 | ### 산술 연산자
218 |
219 | ```dart
220 | int a = 10;
221 | int b = 3;
222 |
223 | print(a + b); // 13 (덧셈)
224 | print(a - b); // 7 (뺄셈)
225 | print(a * b); // 30 (곱셈)
226 | print(a / b); // 3.3333333333333335 (나눗셈, 결과는 double)
227 | print(a ~/ b); // 3 (정수 나눗셈, 결과는 int)
228 | print(a % b); // 1 (나머지)
229 | ```
230 |
231 | ### 증감 연산자
232 |
233 | ```dart
234 | int a = 10;
235 |
236 | a++; // 후위 증가 (a = a + 1)
237 | ++a; // 전위 증가
238 | print(a); // 12
239 |
240 | a--; // 후위 감소 (a = a - 1)
241 | --a; // 전위 감소
242 | print(a); // 10
243 | ```
244 |
245 | ### 할당 연산자
246 |
247 | ```dart
248 | int a = 10;
249 |
250 | a += 5; // a = a + 5
251 | print(a); // 15
252 |
253 | a -= 3; // a = a - 3
254 | print(a); // 12
255 |
256 | a *= 2; // a = a * 2
257 | print(a); // 24
258 |
259 | a ~/= 5; // a = a ~/ 5
260 | print(a); // 4
261 | ```
262 |
263 | ### 비교 연산자
264 |
265 | ```dart
266 | int a = 10;
267 | int b = 5;
268 |
269 | print(a == b); // false (같음)
270 | print(a != b); // true (다름)
271 | print(a > b); // true (초과)
272 | print(a < b); // false (미만)
273 | print(a >= b); // true (이상)
274 | print(a <= b); // false (이하)
275 | ```
276 |
277 | ### 논리 연산자
278 |
279 | ```dart
280 | bool condition1 = true;
281 | bool condition2 = false;
282 |
283 | print(condition1 && condition2); // false (AND)
284 | print(condition1 || condition2); // true (OR)
285 | print(!condition1); // false (NOT)
286 | ```
287 |
288 | ### 타입 테스트 연산자
289 |
290 | ```dart
291 | var value = '문자열';
292 |
293 | print(value is String); // true (value가 String 타입인지 확인)
294 | print(value is! int); // true (value가 int 타입이 아닌지 확인)
295 |
296 | // as 연산자는 타입 변환에 사용됩니다.
297 | dynamic someValue = 'Dart';
298 | String text = someValue as String;
299 | ```
300 |
301 | ### 조건 연산자
302 |
303 | ```dart
304 | // 조건 ? 값1 : 값2
305 | int a = 10;
306 | int b = 5;
307 | int max = a > b ? a : b; // a가 b보다 크면 a, 아니면 b
308 | print(max); // 10
309 |
310 | // ?? 연산자: 왼쪽 피연산자가 null이면 오른쪽 피연산자 반환
311 | String? name;
312 | String displayName = name ?? '이름 없음';
313 | print(displayName); // '이름 없음'
314 | ```
315 |
316 | ### 캐스케이드 연산자 (..)
317 |
318 | 객체에 대해 연속적인 작업을 수행할 수 있는 캐스케이드 연산자입니다.
319 |
320 | ```dart
321 | class Person {
322 | String name = '';
323 | int age = 0;
324 |
325 | void introduce() {
326 | print('내 이름은 $name이고, 나이는 $age살입니다.');
327 | }
328 | }
329 |
330 | void main() {
331 | var person = Person()
332 | ..name = '홍길동'
333 | ..age = 30
334 | ..introduce();
335 |
336 | // 위 코드는 다음과 동일합니다:
337 | // var person = Person();
338 | // person.name = '홍길동';
339 | // person.age = 30;
340 | // person.introduce();
341 | }
342 | ```
343 |
344 | ## null 안전성
345 |
346 | Dart 2.12부터 도입된 null 안전성을 활용하면 null 참조 오류를 컴파일 타임에 방지할 수 있습니다.
347 |
348 | ### nullable과 non-nullable 타입
349 |
350 | ```dart
351 | // non-nullable 타입 (null을 할당할 수 없음)
352 | String name = '홍길동';
353 | // name = null; // 컴파일 오류
354 |
355 | // nullable 타입 (null을 할당할 수 있음)
356 | String? nullableName = '홍길동';
357 | nullableName = null; // 허용됨
358 | ```
359 |
360 | ### null 검사와 null 조건 접근
361 |
362 | ```dart
363 | String? name = getNullableName();
364 |
365 | // null 검사 후 사용
366 | if (name != null) {
367 | print('이름의 길이: ${name.length}');
368 | }
369 |
370 | // 조건 프로퍼티 접근 (?.): 객체가 null이면 전체 표현식이 null이 됨
371 | print('이름의 길이: ${name?.length}');
372 |
373 | // null 병합 연산자 (??): 왼쪽 피연산자가 null이면 오른쪽 피연산자 반환
374 | print('이름: ${name ?? '이름 없음'}');
375 |
376 | // null 정의 연산자 (??=): 변수가 null이면 값을 할당
377 | name ??= '이름 없음';
378 | ```
379 |
380 | ### Non-null 단언 연산자 (!)
381 |
382 | 변수가 null이 아님을 컴파일러에게 알려주는 연산자입니다. 변수가 실제로 null이면 런타임 오류가 발생합니다.
383 |
384 | ```dart
385 | String? name = '홍길동';
386 |
387 | // name이 null이 아니라고 확신할 때 사용
388 | String nonNullName = name!;
389 |
390 | // 그러나 실제로 null이면 런타임 오류 발생
391 | name = null;
392 | // String error = name!; // 런타임 오류: null 참조
393 | ```
394 |
395 | ## 결론
396 |
397 | 이 장에서는 Dart의 기본 문법과 변수 선언, 데이터 타입, 연산자, null 안전성 등에 대해 알아보았습니다. 이러한 기본 개념은 Dart 프로그래밍의 토대가 되며, Flutter를 활용한 앱 개발에도 필수적입니다.
398 |
399 | 다음 장에서는 Dart의 타입 시스템과 제네릭에 대해 더 자세히 알아보겠습니다.
400 |
--------------------------------------------------------------------------------
/src/content/docs/part8/deploy-procedure.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Android / iOS 배포 절차
3 | ---
4 |
5 | Flutter 앱을 개발한 후 사용자들에게 제공하기 위해 앱 스토어에 배포하는 절차를 알아봅니다. Android와 iOS 플랫폼은 각각 다른 배포 프로세스를 가지고 있습니다.
6 |
7 | ## 배포 준비 체크리스트
8 |
9 | 앱 스토어에 제출하기 전에 다음 항목을 먼저 확인하세요:
10 |
11 | - [ ] 모든 주요 기능 테스트 완료
12 | - [ ] 앱 아이콘 및 스플래시 스크린 구현
13 | - [ ] 다양한 화면 크기/해상도 테스트
14 | - [ ] 접근성 지원 확인
15 | - [ ] 개인정보 처리방침 준비
16 | - [ ] 앱 스크린샷 및 설명 준비
17 |
18 | ## Android 앱 배포 절차
19 |
20 | ### 1. 배포용 키스토어 생성
21 |
22 | Android 앱을 서명하기 위한 키스토어(keystore) 파일을 생성해야 합니다. 이 키는 앱 업데이트 시 동일한 키로 서명해야 하므로 안전하게 보관해야 합니다.
23 |
24 | ```bash
25 | keytool -genkey -v -keystore ~/upload-keystore.jks -keyalg RSA -keysize 2048 -validity 10000 -alias upload
26 | ```
27 |
28 | ### 2. 키스토어 설정
29 |
30 | `android/app/build.gradle` 파일에 키스토어 정보를 추가합니다. 보안을 위해 다음과 같이 별도의 파일로 관리합니다.
31 |
32 | 1. `android/key.properties` 파일 생성:
33 |
34 | ```properties
35 | storePassword=<키스토어 비밀번호>
36 | keyPassword=<키 비밀번호>
37 | keyAlias=upload
38 | storeFile=<키스토어 파일 경로, 예: /Users/username/upload-keystore.jks>
39 | ```
40 |
41 | 2. `android/app/build.gradle` 파일 수정:
42 |
43 | ```txt
44 | // 파일 상단에 다음 코드 추가
45 | def keystoreProperties = new Properties()
46 | def keystorePropertiesFile = rootProject.file('key.properties')
47 | if (keystorePropertiesFile.exists()) {
48 | keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
49 | }
50 |
51 | android {
52 | // 기존 코드 ...
53 |
54 | signingConfigs {
55 | release {
56 | keyAlias keystoreProperties['keyAlias']
57 | keyPassword keystoreProperties['keyPassword']
58 | storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
59 | storePassword keystoreProperties['storePassword']
60 | }
61 | }
62 | buildTypes {
63 | release {
64 | signingConfig signingConfigs.release
65 | // 기타 릴리즈 설정 ...
66 | }
67 | }
68 | }
69 | ```
70 |
71 | ### 3. 앱 버전 설정
72 |
73 | `pubspec.yaml` 파일에서 앱 버전을 설정합니다.
74 |
75 | ```yaml
76 | version: 1.0.0+1 # <버전 이름>+<빌드 번호>
77 | ```
78 |
79 | ### 4. 앱 매니페스트 설정
80 |
81 | `android/app/src/main/AndroidManifest.xml` 파일에서 필요한 권한과 설정을 확인합니다.
82 |
83 | ```xml
84 |
85 |
86 |
87 |
88 |
89 |
93 |
94 |
95 |
96 | ```
97 |
98 | ### 5. 앱 번들/APK 생성
99 |
100 | Google Play는 Android App Bundle(AAB) 형식을 권장합니다.
101 |
102 | ```bash
103 | # App Bundle 생성 (권장)
104 | flutter build appbundle
105 |
106 | # 또는 APK 생성
107 | flutter build apk --release
108 | ```
109 |
110 | 빌드된 파일은 다음 위치에서 찾을 수 있습니다:
111 |
112 | - App Bundle: `build/app/outputs/bundle/release/app-release.aab`
113 | - APK: `build/app/outputs/flutter-apk/app-release.apk`
114 |
115 | ### 6. Google Play Console에 앱 등록
116 |
117 | 1. [Google Play Console](https://play.google.com/console)에 로그인합니다.
118 | 2. "새 앱 만들기"를 선택합니다.
119 | 3. 앱 정보(이름, 언어, 앱/게임 여부, 유료/무료 여부)를 입력합니다.
120 | 4. 개인정보 처리방침 URL을 제공합니다.
121 | 5. 다음 정보를 등록합니다:
122 | - 앱 카테고리 및 태그
123 | - 연락처 정보
124 | - 스크린샷, 프로모션 이미지, 앱 아이콘
125 | - 앱 설명(짧은 설명 및 전체 설명)
126 |
127 | ### 7. 앱 번들 업로드
128 |
129 | 1. "앱 릴리즈" > "프로덕션" 트랙 선택
130 | 2. "새 릴리즈 만들기" 클릭
131 | 3. 생성한 App Bundle(.aab) 파일 업로드
132 | 4. 릴리즈 노트 작성
133 | 5. 검토 후 출시
134 |
135 | ### 8. 출시 및 검토
136 |
137 | Google Play 검토 프로세스는 보통 몇 시간에서 며칠까지 소요될 수 있습니다. 검토 완료 후 앱이 출시됩니다.
138 |
139 | ## iOS 앱 배포 절차
140 |
141 | ### 1. Apple Developer Program 가입
142 |
143 | iOS 앱을 App Store에 배포하려면 연간 $99의 비용으로 [Apple Developer Program](https://developer.apple.com/programs/)에 가입해야 합니다.
144 |
145 | ### 2. Xcode에서 인증서 및 프로비저닝 프로필 설정
146 |
147 | 1. Xcode 열기: `open ios/Runner.xcworkspace`
148 | 2. "Signing & Capabilities" 탭에서 팀 선택 및 자동 서명 활성화
149 | 3. 번들 ID 설정 (고유한 식별자, 예: com.yourcompany.appname)
150 |
151 | ### 3. 앱 버전 및 빌드 번호 설정
152 |
153 | `pubspec.yaml` 파일에서 버전을 설정합니다:
154 |
155 | ```yaml
156 | version: 1.0.0+1 # <버전 이름>+<빌드 번호>
157 | ```
158 |
159 | iOS 특정 버전은 Xcode의 Runner 프로젝트 설정이나 `ios/Runner/Info.plist` 파일에서도 확인/수정할 수 있습니다.
160 |
161 | ### 4. iOS 앱 설정
162 |
163 | `ios/Runner/Info.plist` 파일에서 필요한 설정을 확인합니다:
164 |
165 | ```xml
166 | CFBundleDisplayName
167 | 앱 이름
168 |
169 |
170 | NSCameraUsageDescription
171 | 카메라 사용 이유 설명
172 | ```
173 |
174 | ### 5. 앱 아이콘 설정
175 |
176 | `ios/Runner/Assets.xcassets/AppIcon.appiconset`에 다양한 크기의 앱 아이콘을 추가합니다.
177 |
178 | ### 6. 릴리즈 빌드 생성
179 |
180 | ```bash
181 | flutter build ios --release
182 | ```
183 |
184 | ### 7. Xcode에서 Archive 생성
185 |
186 | 1. Xcode에서 "Product" > "Destination" > "Any iOS Device" 선택
187 | 2. "Product" > "Archive" 선택
188 | 3. Archive가 완료되면 Xcode Organizer가 자동으로 열립니다
189 |
190 | ### 8. TestFlight를 통한 테스트 (선택 사항)
191 |
192 | TestFlight를 통해 앱을 테스터에게 배포하여 최종 테스트를 진행할 수 있습니다.
193 |
194 | 1. Xcode Organizer에서 Archive 선택
195 | 2. "Distribute App" > "App Store Connect" > "Upload" 선택
196 | 3. 앱 배포 옵션 설정 (자동 서명 권장)
197 | 4. 업로드 완료 후 [App Store Connect](https://appstoreconnect.apple.com/)에서 TestFlight 구성
198 | 5. 내부 및 외부 테스터 추가
199 |
200 | ### 9. App Store Connect에서 앱 정보 설정
201 |
202 | 1. [App Store Connect](https://appstoreconnect.apple.com/)에 로그인
203 | 2. "내 앱" > "+" > "새로운 앱" 선택
204 | 3. 다음 정보 입력:
205 | - 앱 이름, 기본 언어, 번들 ID
206 | - SKU (내부 추적용 고유 ID)
207 | - 사용자 액세스 설정
208 |
209 | ### 10. 앱 정보 등록
210 |
211 | 1. App Store 정보 탭에서 다음 항목 작성:
212 | - 프로모션 텍스트 (최대 170자)
213 | - 설명 (최대 4,000자)
214 | - 키워드 (최대 100자)
215 | - 지원 URL 및 마케팅 URL
216 | - 스크린샷 (다양한 기기 크기별)
217 | - 앱 미리보기 영상 (선택 사항)
218 | - 앱 아이콘 (1024x1024 픽셀)
219 | - 연령 등급
220 | - 개인정보 처리방침 URL
221 | - 가격 및 가용성
222 |
223 | ### 11. 앱 심사 제출
224 |
225 | 1. "앱 버전" 섹션에서 제출할 빌드 선택
226 | 2. 필요한 수출 규정 준수 정보 제공
227 | 3. "심사를 위해 제출" 클릭
228 |
229 | ### 12. 앱 심사 및 출시
230 |
231 | Apple의 앱 심사는 보통 1-3일 소요됩니다. 거부될 경우 이유가 제공되며, 수정 후 재제출할 수 있습니다. 승인되면 "출시 준비됨" 상태가 되고, 수동 또는 자동으로 출시할 수 있습니다.
232 |
233 | ## CI/CD를 활용한 자동화 배포
234 |
235 | 배포 과정을 자동화하기 위해 CI/CD 도구를 활용할 수 있습니다. 대표적인 도구로는 Codemagic, Fastlane, GitHub Actions 등이 있습니다.
236 |
237 | ### Codemagic 활용 예시
238 |
239 | `codemagic.yaml` 파일 구성:
240 |
241 | ```yaml
242 | workflows:
243 | android-workflow:
244 | name: Android Release
245 | environment:
246 | vars:
247 | KEYSTORE_PATH: /tmp/keystore.jks
248 | FCI_KEYSTORE_FILE: Encrypted(...) # 키스토어 파일 암호화
249 | flutter: stable
250 | scripts:
251 | - name: Set up keystore
252 | script: echo $FCI_KEYSTORE_FILE | base64 --decode > $KEYSTORE_PATH
253 | - name: Build AAB
254 | script: flutter build appbundle --release
255 | artifacts:
256 | - build/app/outputs/bundle/release/app-release.aab
257 | publishing:
258 | google_play:
259 | credentials: Encrypted(...) # Google Play API 키
260 | track: internal # 또는 alpha, beta, production
261 |
262 | ios-workflow:
263 | name: iOS Release
264 | environment:
265 | flutter: stable
266 | xcode: latest
267 | cocoapods: default
268 | scripts:
269 | - name: Build iOS
270 | script: flutter build ios --release --no-codesign
271 | - name: Set up code signing
272 | script: |
273 | keychain initialize
274 | app-store-connect fetch-signing-files $(BUNDLE_ID) --type IOS_APP_STORE
275 | keychain add-certificates
276 | xcode-project use-profiles
277 | - name: Build IPA
278 | script: xcode-project build-ipa --workspace ios/Runner.xcworkspace --scheme Runner
279 | artifacts:
280 | - build/ios/ipa/*.ipa
281 | publishing:
282 | app_store_connect:
283 | api_key: Encrypted(...) # App Store Connect API 키
284 | submit_to_testflight: true
285 | ```
286 |
287 | ## 배포 관련 팁
288 |
289 | ### 앱 크기 최적화
290 |
291 | - 사용하지 않는 리소스 제거
292 | - 이미지 최적화 및 압축
293 | - ProGuard/R8 활성화 (Android)
294 |
295 | ### 버전 관리 전략
296 |
297 | Semantic Versioning(SemVer) 규칙을 따르는 것이 좋습니다:
298 |
299 | - `MAJOR.MINOR.PATCH+BUILD_NUMBER`
300 | - MAJOR: 호환되지 않는 API 변경
301 | - MINOR: 호환되는 기능 추가
302 | - PATCH: 버그 수정
303 | - BUILD_NUMBER: 앱 스토어용 빌드 번호 (매 배포마다 증가)
304 |
305 | ### 단계적 출시
306 |
307 | 대규모 업데이트는 단계적으로 출시하는 것이 좋습니다:
308 |
309 | 1. 내부 테스트 → 2. 알파/베타 테스트 → 3. 제한된 사용자 그룹 → 4. 전체 출시
310 |
311 | ## Flutter 배포 관련 자주 묻는 질문
312 |
313 | ### Q: 앱이 너무 큰데 어떻게 크기를 줄일 수 있나요?
314 |
315 | A: `flutter build apk --split-per-abi`로 ABI별 분할, 미사용 리소스 제거, 이미지 최적화, 코드 축소(R8/ProGuard) 활성화 등을 시도해보세요.
316 |
317 | ### Q: 앱이 심사에서 거부됐어요. 어떻게 해야 하나요?
318 |
319 | A: 거부 사유를 주의 깊게 읽고, 해당 문제를 수정한 후 재제출하세요. 명확하지 않은 경우 Apple/Google의 개발자 지원에 문의하세요.
320 |
321 | ### Q: iOS와 Android 배포 중 어떤 것을 먼저 해야 하나요?
322 |
323 | A: 일반적으로 iOS 심사가 더 오래 걸리므로 iOS를 먼저 제출하고, 이후 Android를 제출하는 것이 효율적입니다.
324 |
325 | ## 결론
326 |
327 | 앱 배포는 Flutter 개발 과정의 중요한 마무리 단계입니다. 이 가이드를 통해 Android와 iOS 플랫폼에 앱을 성공적으로 배포하는 전체 과정을 이해할 수 있습니다. 각 플랫폼의 요구사항과 프로세스를 잘 파악하고, CI/CD 도구를 활용하면 효율적이고 안정적인 배포가 가능합니다.
328 |
--------------------------------------------------------------------------------
/src/content/docs/part1/project-structure.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Flutter 프로젝트 구조 이해
3 | ---
4 | import { FileTree } from '@astrojs/starlight/components';
5 |
6 | Flutter 프로젝트는 여러 디렉토리와 파일로 구성되어 있으며, 각각은 프로젝트의 특정 측면을 담당합니다. 이 구조를 이해하면 Flutter 앱을 더 효율적으로 개발하고 관리할 수 있습니다.
7 |
8 | ## Flutter 프로젝트의 기본 구조
9 |
10 | 기본적인 Flutter 프로젝트 구조는 다음과 같습니다:
11 |
12 |
13 | - my_flutter_app/
14 | - .dart_tool/ # Dart 도구 관련 파일
15 | - .idea/ # IDE 설정 (Android Studio)
16 | - android/ # 안드로이드 특화 코드
17 | - build/ # 빌드 출력 파일
18 | - ios/ # iOS 특화 코드
19 | - lib/ # Dart 코드
20 | - main.dart # 앱의 진입점
21 | - linux/ # Linux 특화 코드
22 | - macos/ # macOS 특화 코드
23 | - test/ # 테스트 코드
24 | - web/ # 웹 특화 코드
25 | - windows/ # Windows 특화 코드
26 | - .gitignore # Git 무시 파일
27 | - .metadata # Flutter 메타데이터
28 | - analysis_options.yaml # Dart 분석 설정
29 | - pubspec.lock # 의존성 버전 잠금 파일
30 | - pubspec.yaml # 프로젝트 설정 및 의존성
31 | - README.md # 프로젝트 설명
32 |
33 |
34 | 이제 각 디렉토리와 파일의 역할을 자세히 살펴보겠습니다.
35 |
36 | ## 주요 디렉토리
37 |
38 | ### lib/ 디렉토리
39 |
40 | `lib/` 디렉토리는 Flutter 프로젝트의 핵심으로, 앱의 Dart 소스 코드가 저장되는 위치입니다.
41 |
42 |
43 | - lib/
44 | - main.dart # 앱의 진입점
45 | - models/ # 데이터 모델
46 | - screens/ # 화면 UI
47 | - widgets/ # 재사용 가능한 위젯
48 | - services/ # 비즈니스 로직, API 호출 등
49 | - utils/ # 유틸리티 기능
50 |
51 |
52 | **중요: 기본적으로 생성되는 것은 `main.dart` 파일뿐이며, 나머지 폴더 구조는 개발자가 필요에 따라 생성하고 구성합니다.**
53 |
54 | #### main.dart
55 |
56 | `main.dart` 파일은 앱의 진입점으로, `main()` 함수와 루트 위젯을 포함합니다:
57 |
58 | ```dart
59 | import 'package:flutter/material.dart';
60 |
61 | void main() {
62 | runApp(const MyApp());
63 | }
64 |
65 | class MyApp extends StatelessWidget {
66 | const MyApp({super.key});
67 |
68 | @override
69 | Widget build(BuildContext context) {
70 | return MaterialApp(
71 | title: 'Flutter Demo',
72 | theme: ThemeData(
73 | primarySwatch: Colors.blue,
74 | ),
75 | home: const MyHomePage(title: 'Flutter Demo Home Page'),
76 | );
77 | }
78 | }
79 | ```
80 |
81 | ### test/ 디렉토리
82 |
83 | `test/` 디렉토리는 앱의 자동화된 테스트 코드를 포함합니다. 단위 테스트, 위젯 테스트 등을 이 디렉토리에 작성합니다.
84 |
85 |
86 | - test/
87 | - widget_test.dart # 위젯 테스트
88 | - unit/
89 | - models_test.dart # 단위 테스트
90 |
91 |
92 | 기본적으로 생성되는 `widget_test.dart` 파일은 앱의 메인 위젯을 테스트하는 간단한 예제를 포함합니다:
93 |
94 | ```dart
95 | import 'package:flutter/material.dart';
96 | import 'package:flutter_test/flutter_test.dart';
97 | import 'package:my_flutter_app/main.dart';
98 |
99 | void main() {
100 | testWidgets('Counter increments smoke test', (WidgetTester tester) async {
101 | await tester.pumpWidget(const MyApp());
102 | expect(find.text('0'), findsOneWidget);
103 | expect(find.text('1'), findsNothing);
104 | await tester.tap(find.byIcon(Icons.add));
105 | await tester.pump();
106 | expect(find.text('0'), findsNothing);
107 | expect(find.text('1'), findsOneWidget);
108 | });
109 | }
110 | ```
111 |
112 | ### android/ 디렉토리
113 |
114 | `android/` 디렉토리는 Android 플랫폼 관련 코드와 설정을 포함합니다.
115 |
116 |
117 | - android/
118 | - app/
119 | - src/
120 | - main/
121 | - AndroidManifest.xml # 앱 선언 및 권한
122 | - kotlin/ # Kotlin 소스 코드
123 | - res/ # 리소스 (아이콘, 문자열 등)
124 | - profile/
125 | - build.gradle # 앱 수준 빌드 설정
126 | - ...
127 | - build.gradle # 프로젝트 수준 빌드 설정
128 | - gradle/
129 | - gradle.properties
130 | - ...
131 |
132 |
133 | 특히 중요한 파일들:
134 |
135 | - **AndroidManifest.xml**: 앱의 이름, 아이콘, 필요한 권한 등을 정의합니다.
136 | - **build.gradle**: 앱의 버전, 의존성, 빌드 설정 등을 구성합니다.
137 |
138 | ### ios/ 디렉토리
139 |
140 | `ios/` 디렉토리는 iOS 플랫폼 관련 코드와 설정을 포함합니다.
141 |
142 |
143 | - ios/
144 | - Runner/
145 | - AppDelegate.swift # iOS 앱 진입점
146 | - Info.plist # 앱 구성 및 권한
147 | - Assets.xcassets/ # 이미지 에셋
148 | - ...
149 | - Runner.xcodeproj/ # Xcode 프로젝트 파일
150 | - ...
151 |
152 |
153 | 특히 중요한 파일들:
154 |
155 | - **Info.plist**: 앱 이름, 버전, 권한 등의 메타데이터를 포함합니다.
156 | - **AppDelegate.swift**: iOS 앱의 진입점 및 초기화 로직을 포함합니다.
157 |
158 | ### web/, macos/, linux/, windows/ 디렉토리
159 |
160 | 이 디렉토리들은 각각 웹, macOS, 리눅스, 윈도우 플랫폼을 위한 코드와 설정을 포함합니다. 구조는 플랫폼마다 다르지만, 모두 해당 플랫폼의 네이티브 코드와 구성을 담고 있습니다.
161 |
162 | ## 주요 설정 파일
163 |
164 | ### pubspec.yaml
165 |
166 | `pubspec.yaml`은 Flutter 프로젝트의 핵심 설정 파일로, 앱의 메타데이터, 의존성, 에셋 등을 정의합니다:
167 |
168 | ```yaml
169 | name: my_flutter_app
170 | description: A new Flutter project.
171 |
172 | # The following defines the version and build number for your application.
173 | version: 1.0.0+1
174 |
175 | environment:
176 | sdk: ">=3.0.0 <4.0.0"
177 |
178 | # Dependencies
179 | dependencies:
180 | flutter:
181 | sdk: flutter
182 | http: ^1.0.0
183 | shared_preferences: ^2.1.1
184 |
185 | dev_dependencies:
186 | flutter_test:
187 | sdk: flutter
188 | flutter_lints: ^2.0.0
189 |
190 | # Flutter-specific configurations
191 | flutter:
192 | uses-material-design: true
193 |
194 | # Assets
195 | assets:
196 | - assets/images/
197 | - assets/fonts/
198 |
199 | # Fonts
200 | fonts:
201 | - family: Roboto
202 | fonts:
203 | - asset: assets/fonts/Roboto-Regular.ttf
204 | - asset: assets/fonts/Roboto-Bold.ttf
205 | weight: 700
206 | ```
207 |
208 | 주요 항목들:
209 |
210 | - **name**: 앱의 패키지 이름
211 | - **version**: 앱의 버전(`버전 코드+빌드 번호` 형식)
212 | - **dependencies**: 앱이 사용하는 패키지 의존성
213 | - **dev_dependencies**: 개발 시에만 필요한 패키지 의존성
214 | - **flutter**: Flutter 특화 설정 (에셋, 폰트, 테마 등)
215 |
216 | ### analysis_options.yaml
217 |
218 | `analysis_options.yaml`은 Dart 코드 분석기의 설정을 정의하여 코드 품질과 일관성을 유지하는 데 도움을 줍니다:
219 |
220 | ```yaml
221 | include: package:flutter_lints/flutter.yaml
222 |
223 | linter:
224 | rules:
225 | - avoid_print
226 | - avoid_empty_else
227 | - prefer_const_constructors
228 | - sort_child_properties_last
229 |
230 | analyzer:
231 | errors:
232 | missing_required_param: error
233 | missing_return: error
234 | ```
235 |
236 | ### .gitignore
237 |
238 | `.gitignore` 파일은 Git 버전 관리 시스템에서 무시해야 할 파일들을 지정합니다. Flutter 프로젝트에서는 빌드 결과물, 임시 파일, IDE 파일 등이 여기에 포함됩니다.
239 |
240 | ## 추가 디렉토리와 파일 (선택적)
241 |
242 | 개발자들은 프로젝트의 규모와 복잡성에 따라 추가 디렉토리를 생성할 수 있습니다:
243 |
244 | ### assets/ 디렉토리
245 |
246 |
247 | - assets/
248 | - images/ # 이미지 파일
249 | - fonts/ # 폰트 파일
250 | - data/ # JSON, CSV 등의 데이터 파일
251 |
252 |
253 | 이 디렉토리는 `pubspec.yaml`에 명시적으로 등록해야 합니다:
254 |
255 | ```yaml
256 | flutter:
257 | assets:
258 | - assets/images/
259 | - assets/fonts/
260 | - assets/data/
261 | ```
262 |
263 | ### l10n/ 또는 i18n/ 디렉토리
264 |
265 | 다국어 지원을 위한 디렉토리:
266 |
267 |
268 | - l10n/
269 | - app_en.arb # 영어 번역
270 | - app_ko.arb # 한국어 번역
271 | - app_ja.arb # 일본어 번역
272 |
273 |
274 | ## 프로젝트 구조화 패턴
275 |
276 | Flutter 앱의 구조는 프로젝트의 성격과 팀의 선호도에 따라 달라질 수 있습니다. 일반적으로 많이 사용되는 패턴은 다음과 같습니다:
277 |
278 | ### 기능별 구조 (Feature-First)
279 |
280 | 앱의 기능별로 디렉토리를 구성하는 방식:
281 |
282 |
283 | - lib/
284 | - main.dart
285 | - features/
286 | - auth/
287 | - screens/
288 | - widgets/
289 | - models/
290 | - services/
291 | - home/
292 | - profile/
293 | - settings/
294 | - shared/
295 | - widgets/
296 | - utils/
297 | - constants/
298 |
299 |
300 | 이 구조는 기능이 많은 대규모 앱에 적합합니다.
301 |
302 | ### 계층별 구조 (Layer-First)
303 |
304 | 앱의 아키텍처 계층별로 디렉토리를 구성하는 방식:
305 |
306 |
307 | - lib/
308 | - main.dart
309 | - screens/ # 모든 화면 UI
310 | - widgets/ # 모든 재사용 위젯
311 | - models/ # 모든 데이터 모델
312 | - services/ # 모든 서비스 로직
313 | - repositories/ # 데이터 액세스 로직
314 | - utils/ # 유틸리티 함수
315 |
316 |
317 | 이 구조는 작거나 중간 규모의 앱에 적합합니다.
318 |
319 | ### MVVM 또는 Clean Architecture
320 |
321 | 보다 체계적인 아키텍처 패턴을 적용한 구조:
322 |
323 |
324 | - lib/
325 | - main.dart
326 | - ui/
327 | - screens/
328 | - widgets/
329 | - viewmodels/
330 | - models/
331 | - services/
332 | - repositories/
333 | - core/
334 | - utils/
335 | - constants/
336 |
337 |
338 | 이 구조는 코드의 유지 관리성과 테스트 가능성을 높이는 데 도움이 됩니다.
339 |
340 | ## 모범 사례 및 권장 사항
341 |
342 | ### 1. 명확한 명명 규칙
343 |
344 | - 파일 이름: `snake_case.dart` (예: `user_profile.dart`)
345 | - 클래스 이름: `PascalCase` (예: `UserProfile`)
346 | - 변수 및 함수 이름: `camelCase` (예: `userName`, `getUserInfo()`)
347 |
348 | ### 2. 프로젝트 구조 일관성 유지
349 |
350 | - 처음부터 명확한 구조 계획 수립
351 | - 프로젝트 전체에 동일한 규칙 적용
352 | - 팀 내 구조 합의 및 문서화
353 |
354 | ### 3. 관련 코드 그룹화
355 |
356 | - 관련된 코드는 함께 위치
357 | - 너무 깊은 중첩 디렉토리 피하기 (일반적으로 3-4 수준 이내)
358 | - 디렉토리 이름은 내용을 명확히 반영
359 |
360 | ### 4. 불필요한 분할 피하기
361 |
362 | - 파일이 너무 많아지면 관리가 어려울 수 있음
363 | - 단일 위젯이나 작은 기능을 여러 파일로 나누지 않기
364 | - 너무 큰 파일도 피하기 (일반적으로 300-500줄 이내)
365 |
366 | ## 기타 고려 사항
367 |
368 | ### 환경 구성
369 |
370 | 다양한 환경(개발, 스테이징, 프로덕션)에 대한 구성을 지원하기 위해 다음과 같은 접근 방식을 사용할 수 있습니다:
371 |
372 |
373 | - lib/
374 | - main.dart # 공통 진입점
375 | - main_dev.dart # 개발 환경 진입점
376 | - main_staging.dart # 스테이징 환경 진입점
377 | - main_prod.dart # 프로덕션 환경 진입점
378 | - config/
379 | - app_config.dart # 환경 설정 클래스
380 | - dev_config.dart
381 | - staging_config.dart
382 | - prod_config.dart
383 |
384 |
385 | ### 라우팅 구성
386 |
387 | 앱의 화면 전환을 관리하기 위한 라우팅 구성:
388 |
389 |
390 | - lib/
391 | - router/
392 | - app_router.dart # 라우터 설정
393 | - routes.dart # 라우트 상수
394 |
395 |
396 | ### 상태 관리
397 |
398 | 선택한 상태 관리 솔루션에 따라 구조가 달라질 수 있습니다:
399 |
400 |
401 | - lib/
402 | - providers/ # Riverpod/Provider
403 | - blocs/ # Flutter_bloc
404 | - stores/ # MobX
405 | - state/ # 기타 상태 관리
406 |
407 |
408 | ## 결론
409 |
410 | Flutter 프로젝트 구조는 규모와 복잡성에 따라 다양하게 적용할 수 있습니다. 중요한 것은 팀이 이해하기 쉽고 유지 관리가 용이한 일관된 구조를 선택하는 것입니다. 프로젝트가 성장함에 따라 구조도 진화할 수 있으므로, 리팩토링과 개선에 열린 자세를 유지하는 것이 좋습니다.
411 |
412 | 이제 Flutter 개발 환경 설정과 프로젝트 구조에 대한 이해를 바탕으로, 다음 챕터에서 Dart 언어 기초를 배워보겠습니다.
413 |
--------------------------------------------------------------------------------
/src/content/docs/part5/advanced-routing.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 라우트 가드, ShellRoute, DeepLink
3 | ---
4 |
5 | 이 장에서는 go_router를 활용한 고급 라우팅 기법에 대해 알아보겠습니다. 라우트 가드를 통한 접근 제어, ShellRoute를 이용한 중첩 네비게이션, 그리고 딥 링크를 통한 앱 외부에서의 접근 방법을 살펴보겠습니다.
6 |
7 | ## 라우트 가드 (Route Guards)
8 |
9 | 라우트 가드는 특정 경로에 대한 접근을 제어하는 메커니즘으로, 사용자 인증이나 권한 검사 등에 활용됩니다.
10 |
11 | ### redirect를 활용한 기본 라우트 가드
12 |
13 | go_router의 `redirect` 기능을 사용하여 라우트 가드를 구현할 수 있습니다:
14 |
15 | ```dart
16 | final GoRouter router = GoRouter(
17 | routes: [...],
18 |
19 | // 전역 리다이렉트 (모든 라우트에 적용)
20 | redirect: (context, state) {
21 | // 현재 인증 상태 확인
22 | final isLoggedIn = AuthService.isLoggedIn;
23 |
24 | // 로그인이 필요한 경로 목록
25 | final protectedRoutes = ['/profile', '/settings', '/cart'];
26 | final isProtectedRoute = protectedRoutes.any(
27 | (route) => state.matchedLocation.startsWith(route),
28 | );
29 |
30 | // 로그인 페이지 여부 확인
31 | final isLoginRoute = state.matchedLocation == '/login';
32 |
33 | // 로그인 되지 않았고 보호된 경로로 접근 시도
34 | if (!isLoggedIn && isProtectedRoute) {
35 | return '/login?redirect=${state.matchedLocation}';
36 | }
37 |
38 | // 이미 로그인된 상태에서 로그인 페이지 접근 시도
39 | if (isLoggedIn && isLoginRoute) {
40 | return '/';
41 | }
42 |
43 | // 조건에 해당하지 않으면 리다이렉트 없음 (원래 경로 유지)
44 | return null;
45 | },
46 | );
47 | ```
48 |
49 | ### 특정 라우트에 대한 가드
50 |
51 | 개별 라우트에 대해서도 리다이렉트를 설정할 수 있습니다:
52 |
53 | ```dart
54 | GoRoute(
55 | path: '/admin',
56 | redirect: (context, state) {
57 | final user = AuthService.currentUser;
58 |
59 | // 관리자 권한 확인
60 | if (user == null || !user.hasAdminRole) {
61 | return '/access-denied';
62 | }
63 |
64 | // 권한이 있으면 원래 경로로 진행
65 | return null;
66 | },
67 | builder: (context, state) => AdminDashboard(),
68 | ),
69 | ```
70 |
71 | ### 상태 변화에 따른 리다이렉트 갱신
72 |
73 | 인증 상태가 변경될 때 라우트를 재평가하기 위해 `refreshListenable`을 사용합니다:
74 |
75 | ```dart
76 | // 인증 상태 관리를 위한 ChangeNotifier
77 | class AuthNotifier extends ChangeNotifier {
78 | bool _isLoggedIn = false;
79 |
80 | bool get isLoggedIn => _isLoggedIn;
81 |
82 | Future login() async {
83 | _isLoggedIn = true;
84 | notifyListeners(); // 상태 변경 알림 (라우터가 이를 감지)
85 | }
86 |
87 | Future logout() async {
88 | _isLoggedIn = false;
89 | notifyListeners();
90 | }
91 | }
92 |
93 | // 인증 상태 변경을 감지하는 라우터
94 | final GoRouter router = GoRouter(
95 | refreshListenable: authNotifier, // 인증 상태 변경 감지
96 | redirect: (context, state) {
97 | // 리다이렉트 로직...
98 | },
99 | routes: [...],
100 | );
101 | ```
102 |
103 | ## ShellRoute를 이용한 네비게이션
104 |
105 | ShellRoute는 중첩 네비게이션을 구현하는 데 사용되며, 특히 바텀 네비게이션 바나 탭 바와 같은 영구적인 UI 요소가 있는 앱에 유용합니다.
106 |
107 | ### StatefulShellRoute 기본 개념
108 |
109 | StatefulShellRoute는 여러 브랜치(branch)를 관리하는 라우트로, 각 브랜치는 자체 네비게이션 상태와 히스토리를 가집니다:
110 |
111 |
112 | ### 바텀 네비게이션 바 구현
113 |
114 | StatefulShellRoute를 사용하여 바텀 네비게이션 바를 구현해 보겠습니다:
115 |
116 | ```dart
117 | final GoRouter router = GoRouter(
118 | initialLocation: '/',
119 | routes: [
120 | // StatefulShellRoute 정의 (indexedStack 방식 사용)
121 | StatefulShellRoute.indexedStack(
122 | // 쉘 UI 구성
123 | builder: (context, state, navigationShell) {
124 | return ScaffoldWithNavBar(navigationShell: navigationShell);
125 | },
126 | // 브랜치 정의
127 | branches: [
128 | // 홈 탭
129 | StatefulShellBranch(
130 | routes: [
131 | GoRoute(
132 | path: '/',
133 | builder: (context, state) => HomeScreen(),
134 | routes: [
135 | // 홈 탭 내부의 중첩 라우트
136 | GoRoute(
137 | path: 'details/:id',
138 | builder: (context, state) {
139 | final id = state.pathParameters['id']!;
140 | return DetailsScreen(id: id);
141 | },
142 | ),
143 | ],
144 | ),
145 | ],
146 | ),
147 |
148 | // 검색 탭
149 | StatefulShellBranch(
150 | routes: [
151 | GoRoute(
152 | path: '/search',
153 | builder: (context, state) => SearchScreen(),
154 | ),
155 | ],
156 | ),
157 |
158 | // 프로필 탭
159 | StatefulShellBranch(
160 | routes: [
161 | GoRoute(
162 | path: '/profile',
163 | builder: (context, state) => ProfileScreen(),
164 | ),
165 | ],
166 | ),
167 | ],
168 | ),
169 | ],
170 | );
171 |
172 | // 바텀 네비게이션 바가 있는 스캐폴드
173 | class ScaffoldWithNavBar extends StatelessWidget {
174 | final StatefulNavigationShell navigationShell;
175 |
176 | const ScaffoldWithNavBar({required this.navigationShell});
177 |
178 | @override
179 | Widget build(BuildContext context) {
180 | return Scaffold(
181 | body: navigationShell, // 현재 브랜치의 화면 표시
182 | bottomNavigationBar: BottomNavigationBar(
183 | currentIndex: navigationShell.currentIndex,
184 | items: const [
185 | BottomNavigationBarItem(icon: Icon(Icons.home), label: '홈'),
186 | BottomNavigationBarItem(icon: Icon(Icons.search), label: '검색'),
187 | BottomNavigationBarItem(icon: Icon(Icons.person), label: '프로필'),
188 | ],
189 | onTap: (index) {
190 | // 탭 인덱스에 해당하는 브랜치로 이동
191 | navigationShell.goBranch(index);
192 | },
193 | ),
194 | );
195 | }
196 | }
197 | ```
198 |
199 | ### 브랜치 간 이동과 상태 유지
200 |
201 | StatefulShellRoute의 강점은 각 브랜치 내의 네비게이션 상태가 유지된다는 점입니다:
202 |
203 | ```dart
204 | // 검색 화면에서 사용자가 검색 결과로 이동한 후
205 | // 다른 탭으로 이동했다가 다시 검색 탭으로 돌아오면
206 | // 검색 결과 화면이 그대로 유지됩니다.
207 |
208 | // 홈 탭에서 상세 화면으로 이동
209 | context.go('/details/123');
210 |
211 | // 프로필 탭으로 이동 (홈 탭의 상태는 유지됨)
212 | context.go('/profile');
213 |
214 | // 다시 홈 탭으로 이동하면 상세 화면이 표시됨
215 | context.go('/');
216 | ```
217 |
218 | ## 딥 링크 (Deep Linking)
219 |
220 | 딥 링크는 앱의 특정 화면으로 직접 접근할 수 있는 외부 링크로, 웹 URL, 푸시 알림 등에서 활용됩니다.
221 |
222 | ### 안드로이드 딥 링크 설정
223 |
224 | 1. **AndroidManifest.xml 설정**:
225 |
226 | ```xml
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
246 |
247 |
248 |
249 |
250 | ```
251 |
252 | ### iOS 딥 링크 설정
253 |
254 | 1. **Info.plist 설정**:
255 |
256 | ```xml
257 | CFBundleURLTypes
258 |
259 |
260 | CFBundleTypeRole
261 | Editor
262 | CFBundleURLName
263 | com.example.app
264 | CFBundleURLSchemes
265 |
266 | myapp
267 |
268 |
269 |
270 |
271 |
272 | com.apple.developer.associated-domains
273 |
274 | applinks:example.com
275 |
276 | ```
277 |
278 | ### Flutter에서 딥 링크 처리
279 |
280 | go_router를 사용하면 딥 링크 처리가 매우 간단합니다:
281 |
282 | ```dart
283 | // 딥 링크를 자동으로 처리하는 설정
284 | void main() {
285 | // 앱 초기화
286 | WidgetsFlutterBinding.ensureInitialized();
287 |
288 | // 라우터 설정
289 | final router = GoRouter(
290 | // 라우트 설정
291 | routes: [...],
292 | );
293 |
294 | runApp(MyApp(router: router));
295 | }
296 |
297 | class MyApp extends StatelessWidget {
298 | final GoRouter router;
299 |
300 | const MyApp({required this.router});
301 |
302 | @override
303 | Widget build(BuildContext context) {
304 | return MaterialApp.router(
305 | routerConfig: router,
306 | title: '딥 링크 예제',
307 | // ...
308 | );
309 | }
310 | }
311 | ```
312 |
313 | ### 딥 링크 테스트
314 |
315 | 딥 링크를 테스트하기 위해 터미널에서 다음 명령어를 실행할 수 있습니다:
316 |
317 | **안드로이드**:
318 |
319 | ```bash
320 | adb shell am start -a android.intent.action.VIEW -d "https://example.com/product/123" com.example.app
321 | ```
322 |
323 | **iOS 시뮬레이터**:
324 |
325 | ```bash
326 | xcrun simctl openurl booted "https://example.com/product/123"
327 | ```
328 |
329 | ### 푸시 알림에서 딥 링크 처리
330 |
331 | 푸시 알림을 통한 딥 링크를 처리하는 방법:
332 |
333 | ```dart
334 | // firebase_messaging 패키지 사용 예제
335 | FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
336 | final deepLink = message.data['deepLink'] as String?;
337 | if (deepLink != null) {
338 | // 앱이 실행 중일 때 푸시 알림을 탭하면 해당 경로로 이동
339 | router.go(deepLink);
340 | }
341 | });
342 |
343 | // 앱이 종료된 상태에서 푸시 알림을 탭한 경우
344 | Future setupInteractedMessage() async {
345 | RemoteMessage? initialMessage = await FirebaseMessaging.instance.getInitialMessage();
346 |
347 | if (initialMessage != null) {
348 | final deepLink = initialMessage.data['deepLink'] as String?;
349 | if (deepLink != null) {
350 | // 딥 링크로 이동
351 | router.go(deepLink);
352 | }
353 | }
354 | }
355 | ```
356 |
357 | ## 고급 라우팅 테크닉
358 |
359 | ### 1. 동적 라우트 생성
360 |
361 | API에서 가져온 데이터를 기반으로 동적으로 라우트를 생성할 수 있습니다:
362 |
363 | ```dart
364 | // 동적 라우트 생성 예제
365 | Future> buildDynamicRoutes() async {
366 | // API에서 카테고리 목록 가져오기
367 | final categories = await apiService.getCategories();
368 |
369 | // 각 카테고리에 대한 라우트 생성
370 | return categories.map((category) {
371 | return GoRoute(
372 | path: '/category/${category.slug}',
373 | builder: (context, state) => CategoryScreen(category: category),
374 | );
375 | }).toList();
376 | }
377 | ```
378 |
379 | ### 2. 라우트 전환 애니메이션 커스터마이징
380 |
381 | 라우트 간 전환 애니메이션을 세밀하게 제어할 수 있습니다:
382 |
383 | ```dart
384 | GoRoute(
385 | path: '/details/:id',
386 | pageBuilder: (context, state) {
387 | final id = state.pathParameters['id']!;
388 |
389 | // 히어로 애니메이션을 위한 전환 페이지
390 | return CustomTransitionPage(
391 | key: state.pageKey,
392 | child: ProductDetailsScreen(id: id),
393 | transitionsBuilder: (context, animation, secondaryAnimation, child) {
394 | // 페이드 트랜지션
395 | return FadeTransition(
396 | opacity: CurveTween(curve: Curves.easeInOut).animate(animation),
397 | child: child,
398 | );
399 | },
400 | );
401 | },
402 | ),
403 | ```
404 |
405 | ## 요약
406 |
407 | - **라우트 가드**를 사용하여 인증 상태에 따라 접근을 제어할 수 있습니다.
408 | - **StatefulShellRoute**는 바텀 네비게이션 바와 같은 중첩 네비게이션을 효과적으로 구현할 수 있게 해줍니다.
409 | - **딥 링크**를 통해 앱 외부에서 특정 화면으로 직접 접근할 수 있습니다.
410 | - **안드로이드와 iOS** 모두에서 딥 링크를 설정하는 방법이 다릅니다.
411 | - **푸시 알림**에서 딥 링크를 처리하여 특정 화면으로 이동할 수 있습니다.
412 | - **동적 라우트 생성**, **커스텀 전환 애니메이션** 등 고급 라우팅 기법을 활용할 수 있습니다.
413 |
414 | 다음 섹션에서는 이러한 고급 라우팅 기법을 실제 앱에 적용하는 복수 화면 전환 실습을 진행하겠습니다.
415 |
--------------------------------------------------------------------------------
/src/content/docs/part2/type-system.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 타입 시스템 & 제네릭
3 | ---
4 |
5 | ## Dart 타입 시스템 개요
6 |
7 | Dart는 정적 타입 언어로, 컴파일 시간에 타입 검사를 수행합니다. 그러나 타입 추론을 지원하여 타입 선언을 생략할 수 있는 유연성도 제공합니다. Dart 2부터는 타입 안전성이 강화되었고, Dart 2.12부터는 null 안전성이 도입되었습니다.
8 |
9 | ## 기본 타입
10 |
11 | ### 기본 제공 타입
12 |
13 | Dart에는 다음과 같은 기본 타입이 있습니다:
14 |
15 | ```dart
16 | // 숫자 타입
17 | int integer = 42;
18 | double decimal = 3.14;
19 | num number = 10; // int나 double의 상위 타입
20 |
21 | // 문자열
22 | String text = '안녕하세요';
23 |
24 | // 불리언
25 | bool flag = true;
26 |
27 | // 리스트(배열)
28 | List numbers = [1, 2, 3];
29 |
30 | // 맵(딕셔너리)
31 | Map person = {'name': '홍길동', 'age': 30};
32 |
33 | // 집합
34 | Set uniqueNames = {'홍길동', '김철수', '이영희'};
35 |
36 | // 심볼
37 | Symbol symbol = #symbolName;
38 | ```
39 |
40 | ### 특수 타입
41 |
42 | Dart에는 특수한 용도의 타입도 있습니다:
43 |
44 | ```dart
45 | // void: 값을 반환하지 않는 함수의 반환 타입
46 | void printMessage() {
47 | print('메시지 출력');
48 | }
49 |
50 | // dynamic: 모든 타입을 허용하는 동적 타입
51 | dynamic dynamicValue = '문자열';
52 | dynamicValue = 42; // 타입 변경 가능
53 |
54 | // Object: 모든 객체의 기본 타입
55 | Object objectValue = 'Hello';
56 |
57 | // Null: null 값의 타입 (Dart 2.12 이전)
58 | ```
59 |
60 | ## 타입 추론
61 |
62 | Dart는 변수의 초기값을 기반으로 타입을 추론할 수 있습니다:
63 |
64 | ```dart
65 | // 타입 추론
66 | var name = '홍길동'; // String 타입으로 추론
67 | var age = 30; // int 타입으로 추론
68 | var height = 175.5; // double 타입으로 추론
69 | var active = true; // bool 타입으로 추론
70 | var items = [1, 2, 3]; // List 타입으로 추론
71 |
72 | // 함수에서도 반환 타입 추론
73 | var getName = () {
74 | return '홍길동'; // String 반환 타입으로 추론
75 | };
76 |
77 | // 컬렉션에서도 타입 추론
78 | var people = [ // List