diff --git a/src/content/docs/part1/changelog.md b/src/content/docs/part1/changelog.md index 5c89e01..03508d4 100644 --- a/src/content/docs/part1/changelog.md +++ b/src/content/docs/part1/changelog.md @@ -9,6 +9,7 @@ title: ๋ณ€๊ฒฝ์‚ฌํ•ญ - ๐Ÿ“ฆ 1. ์‹œ์ž‘ํ•˜๊ธฐ์— **๋ณ€๊ฒฝ์‚ฌํ•ญ** ์ถ”๊ฐ€ - ๐Ÿ’ก 2. Dart ์–ธ์–ด ๊ธฐ์ดˆ ์ค‘ ์ปฌ๋ ‰์…˜๊ณผ ๋ฐ˜๋ณต๋ฌธ์— collection ํŒจํ‚ค์ง€ ์•ˆ๋‚ด ์ถ”๊ฐ€ - `flutter pub run build_runner build` ๋ฅผ `dart run build_runner build` ๋กœ ๋ณ€๊ฒฝ +- JSON ์ง๋ ฌํ™” (freezed, json_serializable)์— freezed_annotation ํŒ ์ถ”๊ฐ€ ### 2025๋…„ 05์›” 14์ผ diff --git a/src/content/docs/part6/json-serialization.md b/src/content/docs/part6/json-serialization.mdx similarity index 93% rename from src/content/docs/part6/json-serialization.md rename to src/content/docs/part6/json-serialization.mdx index 14b1ba8..435720e 100644 --- a/src/content/docs/part6/json-serialization.md +++ b/src/content/docs/part6/json-serialization.mdx @@ -1,6 +1,8 @@ --- title: JSON ์ง๋ ฌํ™” (freezed, json_serializable) --- +import { Aside } from '@astrojs/starlight/components'; + REST API์™€ ํ†ต์‹ ํ•  ๋•Œ JSON ํ˜•์‹์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์ฃผ๊ณ ๋ฐ›๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์Šต๋‹ˆ๋‹ค. Flutter/Dart์—์„œ๋Š” ์ด๋Ÿฌํ•œ JSON ๋ฐ์ดํ„ฐ๋ฅผ Dart ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜ํ•˜๊ณ , ๋ฐ˜๋Œ€๋กœ Dart ๊ฐ์ฒด๋ฅผ JSON์œผ๋กœ ์ง๋ ฌํ™”ํ•˜๋Š” ๊ณผ์ •์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ์ด ์žฅ์—์„œ๋Š” `json_serializable`๊ณผ `freezed` ํŒจํ‚ค์ง€๋ฅผ ํ™œ์šฉํ•œ JSON ์ง๋ ฌํ™” ๋ฐฉ๋ฒ•์„ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. @@ -164,6 +166,73 @@ class Product { } ``` + + + +```dart title="color_converter.dart" +import 'package:flutter/material.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +/// Color ๊ฐ์ฒด์™€ String(HEX) ๊ฐ„์˜ ๋ณ€ํ™˜์„ ์ฒ˜๋ฆฌํ•˜๋Š” JSONConverter +class ColorConverter implements JsonConverter { + const ColorConverter(); + + @override + Color fromJson(String json) { + // '#RRGGBB' ํ˜•ํƒœ์˜ HEX ๋ฌธ์ž์—ด์„ Color ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜ + return Color(int.parse(json.substring(1), radix: 16) + 0xFF000000); + } + + @override + String toJson(Color object) { + // Color ๊ฐ์ฒด๋ฅผ '#RRGGBB' ํ˜•ํƒœ์˜ HEX ๋ฌธ์ž์—ด๋กœ ๋ณ€ํ™˜ + return '#${object.value.toRadixString(16).substring(2)}'; + } +} +``` + +```dart title="product.dart" +import 'package:flutter/material.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'product.g.dart'; +part 'product.freezed.dart'; + +/// ๊ฐ์ฒด ์ƒ์„ฑ +/// final Product product = Product( +/// id: 1, +/// name: '๋นจ๊ฐ„ ์ƒํ’ˆ', +/// color: Colors.red, +/// ); +/// +/// JSON์œผ๋กœ ๋ณ€ํ™˜ - color๋Š” ์ž๋™์œผ๋กœ '#ff0000' ๊ฐ™์€ ๋ฌธ์ž์—ด๋กœ ๋ณ€ํ™˜๋จ +/// final Map json = product.toJson(); +/// print(json); // {id: 1, name: ๋นจ๊ฐ„ ์ƒํ’ˆ, color: #ff0000} +/// +/// JSON์—์„œ ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜ - '#ff0000' ๊ฐ™์€ ๋ฌธ์ž์—ด์€ ์ž๋™์œผ๋กœ Color ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜๋จ +/// final Product loadedProduct = Product.fromJson(json); +/// print(loadedProduct.color); // Color(0xFFFF0000) +@freezed +class Product with _$Product { + factory Product({ + required int id, + required String name, + @ColorConverter() required Color color, // JSONConverter ์ ์šฉ + }) = _Product; + + factory Product.fromJson(Map json) => _$ProductFromJson(json); +} +``` + + #### ์ค‘์ฒฉ ๊ฐ์ฒด ์ฒ˜๋ฆฌ ```dart diff --git a/src/content/docs/part9/multi-module.mdx b/src/content/docs/part9/multi-module.mdx index 37a080e..af354b1 100644 --- a/src/content/docs/part9/multi-module.mdx +++ b/src/content/docs/part9/multi-module.mdx @@ -62,9 +62,7 @@ Flutter์—์„œ ๋ฉ€ํ‹ฐ ๋ชจ๋“ˆ ์•„ํ‚คํ…์ฒ˜๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ์—ฌ๋Ÿฌ ๋ฐฉ๋ฒ•์ด ์žˆ์Šต #### ๊ฐ ๋ชจ๋“ˆ์˜ pubspec.yaml ์„ค์ • -**app/pubspec.yaml**: - -```yaml +```yaml title="app/pubspec.yaml" name: my_app description: Main application module version: 1.0.0+1 @@ -87,9 +85,7 @@ dependencies: path: ../packages/feature_profile ``` -**packages/core/pubspec.yaml**: - -```yaml +```yaml title="packages/core/pubspec.yaml" name: core description: Core module with shared functionality version: 0.0.1 @@ -106,9 +102,7 @@ dependencies: shared_preferences: ^2.2.0 ``` -**packages/feature_auth/pubspec.yaml**: - -```yaml +```yaml title="packages/feature_auth/pubspec.yaml" name: feature_auth description: Authentication feature module version: 0.0.1 @@ -141,7 +135,7 @@ dart pub global activate melos 2. ํ”„๋กœ์ ํŠธ ๋ฃจํŠธ์— `melos.yaml` ํŒŒ์ผ ์ƒ์„ฑ: -```yaml +```yaml title="melos.yaml" name: my_flutter_project packages: @@ -179,8 +173,7 @@ melos test ๋ฉ€ํ‹ฐ ๋ชจ๋“ˆ ์•„ํ‚คํ…์ฒ˜๋Š” Flutter Flavors์™€ ๊ฒฐํ•ฉํ•˜์—ฌ ๋‹ค์–‘ํ•œ ์•ฑ ๋ฒ„์ „(๊ฐœ๋ฐœ, ์Šคํ…Œ์ด์ง•, ํ”„๋กœ๋•์…˜ ๋“ฑ)์„ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: -```dart -// app/lib/main_dev.dart +```dart title="app/lib/main_dev.dart" import 'package:core/config.dart'; import 'package:flutter/material.dart'; import 'app.dart'; @@ -240,15 +233,15 @@ void main() { #### ์ธํ„ฐํŽ˜์ด์Šค ๊ธฐ๋ฐ˜ ํ†ต์‹  ์˜ˆ์‹œ -```dart -// core/lib/src/auth/auth_service.dart +```dart title="core/lib/src/auth/auth_service.dart" abstract class AuthService { Future getCurrentUser(); Future signIn(String email, String password); Future signOut(); } +``` -// feature_auth/lib/src/services/firebase_auth_service.dart +```dart title="feature_auth/lib/src/services/firebase_auth_service.dart" class FirebaseAuthService implements AuthService { @override Future getCurrentUser() { @@ -265,8 +258,9 @@ class FirebaseAuthService implements AuthService { // Firebase ๊ตฌํ˜„ } } +``` -// app/lib/di/service_locator.dart +```dart title="app/lib/di/service_locator.dart" void setupServiceLocator() { GetIt.I.registerSingleton(FirebaseAuthService()); } @@ -308,8 +302,7 @@ void setupServiceLocator() { ๊ฐ ๋ชจ๋“ˆ์€ ๋ช…ํ™•ํ•œ ๊ณต๊ฐœ API๋ฅผ ์ •์˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋ชจ๋“ˆ ๋‚ด๋ถ€ ๊ตฌํ˜„์€ ์ˆจ๊ธฐ๊ณ  ํ•„์š”ํ•œ ๊ธฐ๋Šฅ๋งŒ ๋…ธ์ถœํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. -```dart -// feature_auth/lib/feature_auth.dart +```dart title="feature_auth/lib/feature_auth.dart" library feature_auth; // ๊ณต๊ฐœ API @@ -319,8 +312,7 @@ export 'src/domain/entities/user.dart'; export 'src/di/auth_module.dart'; ``` -```dart -// feature_auth/lib/src/di/auth_module.dart +```dart title="feature_auth/lib/src/di/auth_module.dart" import 'package:get_it/get_it.dart'; import '../data/repositories/auth_repository_impl.dart'; import '../data/datasources/auth_remote_datasource.dart'; @@ -375,8 +367,7 @@ class AuthModule { ์ฝ”์–ด ๋ชจ๋“ˆ์€ ๋‹ค๋ฅธ ๋ชจ๋“  ๋ชจ๋“ˆ์—์„œ ์‚ฌ์šฉํ•˜๋Š” ๊ณตํ†ต ๊ธฐ๋Šฅ์„ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค: -```dart -// core/lib/core.dart +```dart title="core/lib/core.dart" library core; export 'src/config/app_config.dart'; @@ -387,8 +378,7 @@ export 'src/di/service_locator.dart'; export 'src/navigation/router.dart'; ``` -```dart -// core/lib/src/config/app_config.dart +```dart title="core/lib/src/config/app_config.dart" enum Environment { dev, staging, prod } class AppConfig { @@ -416,8 +406,7 @@ class AppConfig { UI ํ‚คํŠธ ๋ชจ๋“ˆ์€ ์•ฑ ์ „์ฒด์—์„œ ์‚ฌ์šฉ๋˜๋Š” ๊ณตํ†ต UI ์ปดํฌ๋„ŒํŠธ๋ฅผ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค: -```dart -// ui_kit/lib/ui_kit.dart +```dart title="ui_kit/lib/ui_kit.dart" library ui_kit; export 'src/buttons/primary_button.dart'; @@ -426,8 +415,7 @@ export 'src/theme/app_theme.dart'; export 'src/inputs/text_field.dart'; ``` -```dart -// ui_kit/lib/src/buttons/primary_button.dart +```dart title="ui_kit/lib/src/buttons/primary_button.dart" import 'package:flutter/material.dart'; class PrimaryButton extends StatelessWidget { @@ -458,8 +446,7 @@ class PrimaryButton extends StatelessWidget { ์ƒํ’ˆ ๊ธฐ๋Šฅ ๋ชจ๋“ˆ์€ ์ƒํ’ˆ ๋ชฉ๋ก, ์ƒ์„ธ ์ •๋ณด, ๊ฒ€์ƒ‰ ๋“ฑ์˜ ๊ธฐ๋Šฅ์„ ๋‹ด๋‹นํ•ฉ๋‹ˆ๋‹ค: -```dart -// feature_products/lib/feature_products.dart +```dart title="feature_products/lib/feature_products.dart" library feature_products; export 'src/presentation/pages/product_list_page.dart'; @@ -468,8 +455,7 @@ export 'src/domain/entities/product.dart'; export 'src/di/products_module.dart'; ``` -```dart -// feature_products/lib/src/presentation/pages/product_list_page.dart +```dart title="feature_products/lib/src/presentation/pages/product_list_page.dart" import 'package:core/core.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -516,8 +502,7 @@ class ProductListPage extends ConsumerWidget { ์•ฑ ๋ชจ๋“ˆ์€ ๋ชจ๋“  ๊ธฐ๋Šฅ ๋ชจ๋“ˆ์„ ํ†ตํ•ฉํ•˜๊ณ  ์•ฑ์˜ ์ง„์ž…์  ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค: -```dart -// app/lib/main_dev.dart +```dart title="app/lib/main_dev.dart" import 'package:core/core.dart'; import 'package:feature_auth/feature_auth.dart'; import 'package:feature_products/feature_products.dart'; @@ -548,8 +533,7 @@ void main() { } ``` -```dart -// app/lib/app.dart +```dart title="app/lib/app.dart" import 'package:core/core.dart'; import 'package:ui_kit/ui_kit.dart'; import 'package:flutter/material.dart'; @@ -587,7 +571,7 @@ class MyApp extends StatelessWidget { - ํ•„์š”ํ•œ ๊ฒฝ์šฐ ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ํ†ต์‹ ์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. - ๊ณตํ†ต ์ฝ”๋“œ๋ฅผ ์ฝ”์–ด ๋ชจ๋“ˆ๋กœ ์ด๋™์‹œํ‚ต๋‹ˆ๋‹ค. -```dart +```dart title="core/lib/core.dart" // ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ํ†ต์‹  ์˜ˆ์‹œ (core ๋ชจ๋“ˆ) class AppEvent { // ์ด๋ฒคํŠธ ์ •์˜