diff --git a/sidebar.config.mjs b/sidebar.config.mjs
index 3960d24..9796872 100644
--- a/sidebar.config.mjs
+++ b/sidebar.config.mjs
@@ -94,7 +94,6 @@ export const sidebars = [
{
label: "π§ Part 9. νλ‘μ νΈ κ΅¬μ‘° & μν€ν
μ²",
items: [
- { label: "ν΄λ¦° μν€ν
μ²", slug: "part9/clean-architecture" },
{ label: "κΈ°λ₯λ³ vs κ³μΈ΅λ³ ν΄λ ꡬ쑰", slug: "part9/folder-structure" },
{ label: "λ©ν° λͺ¨λ μν€ν
μ²", slug: "part9/multi-module" },
],
@@ -107,20 +106,20 @@ export const sidebars = [
{ label: "μ λλ©μ΄μ
", slug: "part10/animations" },
{ label: "μ κ·Όμ±", slug: "part10/accessibility" },
{ label: "λ€κ΅μ΄ μ²λ¦¬", slug: "part10/internationalization" },
- { label: "νΌν¬λ¨Όμ€ νλ", slug: "part10/performance" },
- { label: "μΆμ² ν¨ν€μ§", slug: "part10/recommended-packages" },
+ { label: "μ±λ₯ μ΅μ ν", slug: "part10/performance" },
+ // { label: "μΆμ² ν¨ν€μ§", slug: "part10/recommended-packages" },
],
},
{
label: "π λΆλ‘",
items: [
- { label: "κ°λ° λꡬμ λ§ν¬", slug: "appendix/tools" },
+ // { label: "κ°λ° λꡬμ λ§ν¬", slug: "appendix/tools" },
{ label: "Flutter μ€λ₯ λμλ²", slug: "appendix/error-handling" },
{ label: "μ½λ ν
νλ¦Ώ", slug: "appendix/code-templates" },
- { label: "FAQ", slug: "appendix/faq" },
{ label: "μμ
λ‘κ·ΈμΈ", slug: "appendix/social-login" },
{ label: "iOS λΌμ΄λΈ μ‘ν°λΉν°", slug: "appendix/live-activities" },
{ label: "WidgetBook", slug: "appendix/widgetbook" },
+ { label: "FAQ", slug: "appendix/faq" },
],
},
];
diff --git a/src/content/docs/appendix/social-login.md b/src/content/docs/appendix/social-login.mdx
similarity index 83%
rename from src/content/docs/appendix/social-login.md
rename to src/content/docs/appendix/social-login.mdx
index 243fe53..b83fb71 100644
--- a/src/content/docs/appendix/social-login.md
+++ b/src/content/docs/appendix/social-login.mdx
@@ -75,7 +75,7 @@ abstract class SocialLoginProvider {
```yaml
dependencies:
- kakao_flutter_sdk_user: ^1.6.0
+ kakao_flutter_sdk_user: ^1.9.7+3
```
### 2. νλ«νΌλ³ μ€μ
@@ -222,149 +222,11 @@ void main() {
```yaml
dependencies:
- flutter_naver_login: ^1.8.0
+ naver_login_sdk: ^3.0.0
```
-### 2. νλ«νΌλ³ μ€μ
+> μ€λΉμ€μ
λλ€.
-#### Android μ€μ
-
-1. `android/app/src/main/res/values/strings.xml` νμΌ μμ± λ° μ€μ :
-
-```xml
-
-
- ${YOUR_CLIENT_ID}
- ${YOUR_CLIENT_SECRET}
- ${YOUR_APP_NAME}
-
-```
-
-2. `android/app/build.gradle` νμΌμ `defaultConfig` μΉμ
μ λ§€λνμ€νΈ νλ μ΄μ€νλ μΆκ°:
-
-```gradle
-defaultConfig {
- // ...
- manifestPlaceholders += [
- 'naverClientId': '${YOUR_CLIENT_ID}',
- 'naverClientSecret': '${YOUR_CLIENT_SECRET}',
- 'naverClientName': '${YOUR_APP_NAME}'
- ]
-}
-```
-
-3. `android/app/src/main/AndroidManifest.xml` νμΌμ λ€μ΄λ² λ‘κ·ΈμΈ μ€μ μΆκ°:
-
-```xml
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-```
-
-#### iOS μ€μ
-
-1. `ios/Runner/Info.plist` νμΌμ λ€μ΄λ² μ€μ μΆκ°:
-
-```xml
-CFBundleURLTypes
-
-
-
- CFBundleTypeRole
- Editor
- CFBundleURLSchemes
-
- naver${YOUR_CLIENT_ID}
-
-
-
-
-LSApplicationQueriesSchemes
-
-
- naversearchapp
- naversearchthirdlogin
-
-
-naverClientId
-${YOUR_CLIENT_ID}
-naverClientSecret
-${YOUR_CLIENT_SECRET}
-naverServiceAppName
-${YOUR_APP_NAME}
-```
-
-### 3. λ€μ΄λ² λ‘κ·ΈμΈ κ΅¬ν
-
-```dart
-import 'package:flutter_naver_login/flutter_naver_login.dart';
-
-class NaverLoginProvider implements SocialLoginProvider {
- @override
- Future login() async {
- try {
- // λ€μ΄λ² λ‘κ·ΈμΈ μ€ν
- NaverLoginResult result = await FlutterNaverLogin.logIn();
-
- // λ‘κ·ΈμΈ κ²°κ³Ό νμΈ
- if (result.status == NaverLoginStatus.success) {
- // μ¬μ©μ μ 보 κ°μ Έμ€κΈ°
- NaverAccessToken token = await FlutterNaverLogin.currentAccessToken;
- NaverAccountResult account = await FlutterNaverLogin.currentAccount();
-
- return SocialLoginResult.success(
- accessToken: token.accessToken,
- provider: 'naver',
- email: account.email,
- name: account.name,
- profileImage: account.profileImage,
- );
- } else if (result.status == NaverLoginStatus.cancelledByUser) {
- return const SocialLoginResult.cancelled(provider: 'naver');
- } else {
- return SocialLoginResult.error(
- message: 'λ€μ΄λ² λ‘κ·ΈμΈ μ€ν¨: ${result.errorMessage}',
- provider: 'naver',
- );
- }
- } catch (error) {
- return SocialLoginResult.error(
- message: error.toString(),
- provider: 'naver',
- );
- }
- }
-
- @override
- Future logout() async {
- await FlutterNaverLogin.logOut();
- }
-}
-```
## μ ν λ‘κ·ΈμΈ κ΅¬ν
diff --git a/src/content/docs/appendix/tools.md b/src/content/docs/appendix/tools.md
index 99e6b4c..53c400e 100644
--- a/src/content/docs/appendix/tools.md
+++ b/src/content/docs/appendix/tools.md
@@ -22,7 +22,6 @@ Flutter κ°λ°μ ν¨κ³Όμ μΌλ‘ μννκΈ° μν΄μλ μ μ ν λꡬλ₯Ό
| **Awesome Flutter Snippets** | μμ£Ό μ¬μ©λλ Flutter μ½λ μ‘°κ° μ 곡 |
| **Flutter Widget Snippets** | μμ ― μ½λ μ€λν« μ 곡 |
| **Pubspec Assist** | pubspec.yaml νμΌ κ΄λ¦¬ λμ°λ―Έ |
-| **bloc** | Bloc ν¨ν΄ κ°λ° μ§μ |
| **Git History** | Git μ΄λ ₯ κ΄λ¦¬ μκ°ν |
| **Error Lens** | μΈλΌμΈ μ€λ₯ νμ΄λΌμ΄ν
|
@@ -35,14 +34,6 @@ Flutter κ°λ°μ ν¨κ³Όμ μΌλ‘ μννκΈ° μν΄μλ μ μ ν λꡬλ₯Ό
| **Flipper** | Facebookμ λͺ¨λ°μΌ μ± λλ²κΉ
νλ«νΌ | [μΉμ¬μ΄νΈ](https://fbflipper.com/) |
| **Sentry** | μ€μκ° μλ¬ μΆμ | [μΉμ¬μ΄νΈ](https://sentry.io/) |
-### UI λμμΈ λꡬ
-
-| λꡬ | μ€λͺ
| λ§ν¬ |
-| ------------------------- | ------------------------ | ---------------------------------------------------------------- |
-| **Figma** | νμ
κΈ°λ° UI λμμΈ λꡬ | [μΉμ¬μ΄νΈ](https://www.figma.com/) |
-| **Adobe XD** | UI/UX λμμΈ λꡬ | [μΉμ¬μ΄νΈ](https://www.adobe.com/products/xd.html) |
-| **Flutter UI Challenges** | UI ꡬν μ°μ΅ νλ‘μ νΈ | [GitHub](https://github.com/lohanidamodar/flutter_ui_challenges) |
-
### CI/CD λꡬ
| λꡬ | μ€λͺ
| λ§ν¬ |
diff --git a/src/content/docs/part10/performance.md b/src/content/docs/part10/performance.md
index baf406b..14d73ab 100644
--- a/src/content/docs/part10/performance.md
+++ b/src/content/docs/part10/performance.md
@@ -1,5 +1,5 @@
---
-title: νΌν¬λ¨Όμ€ νλ
+title: μ±λ₯ μ΅μ ν
---
Flutter μ±μ μ±λ₯μ μ¬μ©μ κ²½νμ μ§μ μ μΈ μν₯μ λ―ΈμΉλ μ€μν μμμ
λλ€. μ±μ΄ λΆλλ½κ² μλνκ³ , λ°μμ΄ λΉ λ₯΄λ©°, μμμ ν¨μ¨μ μΌλ‘ μ¬μ©ν λ μ¬μ©μ λ§μ‘±λκ° λμμ§λλ€. μ΄ μ₯μμλ Flutter μ±μ μ±λ₯μ μ΅μ ννκΈ° μν λ€μν μ λ΅κ³Ό κΈ°λ²μ μ΄ν΄λ³΄κ² μ΅λλ€.
@@ -319,7 +319,7 @@ class ImageCache {
}
```
-### 2. λμ€ν¬μ¦ ν¨ν΄
+### 2. ν΄μ ν¨ν΄
`StatefulWidget`μμ 리μμ€λ₯Ό μ μ ν ν΄μ ν©λλ€:
diff --git a/src/content/docs/part5/go-router.md b/src/content/docs/part5/go-router.md
index d05f351..f60aeff 100644
--- a/src/content/docs/part5/go-router.md
+++ b/src/content/docs/part5/go-router.md
@@ -775,11 +775,6 @@ go_routerλ λ€λ₯Έ Flutter λΌμ°ν
λΌμ΄λΈλ¬λ¦¬μ λΉν΄ λͺ κ°μ§ μ₯
- **go_router**: 곡μ μ§μ, κ°λ¨ν μ€μ , μ½λ μμ± λΆνμ
- **auto_route**: μ½λ μμ± κΈ°λ°, νμ
μμ μ±, λ λ§μ μ€μ νμ
-### 3. go_router vs get
-
-- **go_router**: 곡μ μ§μ, Navigator 2.0 κΈ°λ°, URL λκΈ°ν μ§μ κ°λ ₯
-- **get**: λ λμ κΈ°λ₯ μΈνΈ (μν κ΄λ¦¬, μ’
μμ± μ£Όμ
λ±), λ λ¨μν API
-
## μμ½
- **go_router**λ Flutter νμ΄ κ³΅μ μ§μνλ λ€λΉκ²μ΄μ
λΌμ΄λΈλ¬λ¦¬λ‘, Navigator 2.0μ κΈ°λ₯μ λ μ½κ² μ¬μ©ν μ μκ² ν΄μ€λλ€.
diff --git a/src/content/docs/part8/error-tracking.md b/src/content/docs/part8/error-tracking.md
index 4f6b4cc..98962ce 100644
--- a/src/content/docs/part8/error-tracking.md
+++ b/src/content/docs/part8/error-tracking.md
@@ -55,55 +55,7 @@ dependencies:
flutter pub get
```
-#### 3. Android μ€μ
-
-Androidμμ Crashlyticsλ₯Ό μ€μ νλ €λ©΄ `android/app/build.gradle` νμΌμ λ€μ νλ¬κ·ΈμΈμ μΆκ°ν΄μΌ ν©λλ€:
-
-```gradle
-// app/build.gradle νμΌ
-dependencies {
- // ... κΈ°μ‘΄ μ’
μμ±
- implementation 'com.google.firebase:firebase-crashlytics:18.4.0'
- implementation 'com.google.firebase:firebase-analytics:21.3.0'
-}
-
-apply plugin: 'com.google.firebase.crashlytics'
-```
-
-κ·Έλ¦¬κ³ νλ‘μ νΈ μμ€μ `android/build.gradle` νμΌμ λ€μ λ΄μ©μ μΆκ°ν©λλ€:
-
-```gradle
-// project/build.gradle νμΌ
-buildscript {
- repositories {
- // ... κΈ°μ‘΄ μ μ₯μ
- google()
- }
- dependencies {
- // ... κΈ°μ‘΄ μ’
μμ±
- classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.9'
- }
-}
-```
-
-#### 4. iOS μ€μ
-
-iOSμ κ²½μ° `ios/Podfile`μ λ€μ λ΄μ©μ μΆκ°ν©λλ€:
-
-```ruby
-target 'Runner' do
- // ... κΈ°μ‘΄ λ΄μ©
- pod 'FirebaseCrashlytics'
-end
-```
-
-κ·Έλ¦¬κ³ λ€μ λͺ
λ Ήμ΄λ₯Ό μ€νν©λλ€:
-
-```bash
-cd ios && pod install --repo-update
-```
-
-#### 5. Flutter μ±μμ Crashlytics μ΄κΈ°ν
+#### 3. Flutter μ±μμ Crashlytics μ΄κΈ°ν
μ±μ λ©μΈ νμΌμμ Crashlyticsλ₯Ό μ΄κΈ°νν©λλ€:
@@ -259,7 +211,7 @@ pubspec.yamlμ λ€μ ν¨ν€μ§λ₯Ό μΆκ°ν©λλ€:
```yaml
dependencies:
- sentry_flutter: ^7.9.0
+ sentry_flutter: ^8.14.2
```
ν¨ν€μ§λ₯Ό μ€μΉν©λλ€:
diff --git a/src/content/docs/part9/clean-architecture.md b/src/content/docs/part9/clean-architecture.md
deleted file mode 100644
index a8138dc..0000000
--- a/src/content/docs/part9/clean-architecture.md
+++ /dev/null
@@ -1,813 +0,0 @@
----
-title: ν΄λ¦° μν€ν
μ² λμ
νκΈ°
----
-
-
-Flutter μ±μ κ·λͺ¨κ° 컀μ§κ³ 볡μ‘ν΄μ§μλ‘ μ½λμ μ μ§λ³΄μμ±, νμ₯μ±, ν
μ€νΈ μ©μ΄μ±μ ν보νλ κ²μ΄ μ€μν΄μ§λλ€. ν΄λ¦° μν€ν
μ²λ μ΄λ¬ν λ¬Έμ λ₯Ό ν΄κ²°νκΈ° μν μννΈμ¨μ΄ μ€κ³ λ°©λ²λ‘ μΌλ‘, Flutter μ±μλ ν¨κ³Όμ μΌλ‘ μ μ©ν μ μμ΅λλ€. μ΄ λ¬Έμμμλ Flutterμ ν΄λ¦° μν€ν
μ²λ₯Ό λμ
νλ λ°©λ²μ μ€μ©μ μΈ κ΄μ μμ μ΄ν΄λ³΄κ² μ΅λλ€.
-
-## ν΄λ¦° μν€ν
μ²λ?
-
-ν΄λ¦° μν€ν
μ²λ λ‘λ²νΈ C. λ§ν΄(Robert C. Martin, μΌλͺ
Uncle Bob)μ΄ μ μν μννΈμ¨μ΄ μν€ν
μ² ν¨ν΄μΌλ‘, λ€μκ³Ό κ°μ ν΅μ¬ μμΉμ λ°λ¦
λλ€:
-
-### ν΄λ¦° μν€ν
μ²μ μ£Όμ μμΉ
-
-1. **κ΄μ¬μ¬ λΆλ¦¬(Separation of Concerns)**: μλ‘ λ€λ₯Έ μ±
μμ κ°μ§ μ½λλ₯Ό λΆλ¦¬ν©λλ€.
-2. **μμ‘΄μ± κ·μΉ(Dependency Rule)**: λͺ¨λ μμ‘΄μ±μ μΈλΆ κ³μΈ΅μμ λ΄λΆ κ³μΈ΅μΌλ‘ ν₯ν΄μΌ ν©λλ€. λ΄λΆ κ³μΈ΅μ μΈλΆ κ³μΈ΅μ λν΄ μμ§ λͺ»ν©λλ€.
-3. **λΉμ¦λμ€ κ·μΉ λ
립μ±**: λΉμ¦λμ€ κ·μΉμ UI, λ°μ΄ν°λ² μ΄μ€, νλ μμν¬, μΈλΆ λΌμ΄λΈλ¬λ¦¬μ λ
립μ μ΄μ΄μΌ ν©λλ€.
-
-### ν΄λ¦° μν€ν
μ²μ κ³μΈ΅
-
-ν΄λ¦° μν€ν
μ²λ μΌλ°μ μΌλ‘ λ€μ μΈ κ°μ§ μ£Όμ κ³μΈ΅μΌλ‘ ꡬμ±λ©λλ€:
-
-1. **λλ©μΈ κ³μΈ΅(Domain Layer)**: λΉμ¦λμ€ λ‘μ§κ³Ό μν°ν°λ₯Ό ν¬ν¨νλ ν΅μ¬ κ³μΈ΅μ
λλ€.
-2. **λ°μ΄ν° κ³μΈ΅(Data Layer)**: λ°μ΄ν° μμ€μμ ν΅μ μ λ΄λΉνλ κ³μΈ΅μ
λλ€.
-3. **νλ μ ν
μ΄μ
κ³μΈ΅(Presentation Layer)**: UIμ μ¬μ©μ μνΈμμ©μ μ²λ¦¬νλ κ³μΈ΅μ
λλ€.
-
-## Flutterμμμ ν΄λ¦° μν€ν
μ² κ΅¬ν
-
-Flutterμμ ν΄λ¦° μν€ν
μ²λ₯Ό ꡬννκΈ° μν ꡬ체μ μΈ μ κ·Ό λ°©μμ μ΄ν΄λ³΄κ² μ΅λλ€.
-
-### 1. νλ‘μ νΈ κ΅¬μ‘° μ€μ
-
-ν΄λ¦° μν€ν
μ²λ₯Ό μ μ©ν Flutter νλ‘μ νΈμ κΈ°λ³Έ ꡬ쑰λ λ€μκ³Ό κ°μ΅λλ€:
-
-```
-lib/
-βββ core/ # κ³΅ν΅ κΈ°λ₯ λ° μ νΈλ¦¬ν°
-β βββ error/ # μ€λ₯ μ²λ¦¬
-β βββ network/ # λ€νΈμν¬ κ΄λ ¨
-β βββ utils/ # μ νΈλ¦¬ν° ν¨μ
-β
-βββ data/ # λ°μ΄ν° κ³μΈ΅
-β βββ datasources/ # λ°μ΄ν° μμ€ κ΅¬ν
-β β βββ local/ # λ‘컬 λ°μ΄ν° μμ€
-β β βββ remote/ # μ격 λ°μ΄ν° μμ€
-β βββ models/ # λ°μ΄ν° λͺ¨λΈ(DTO)
-β βββ repositories/ # 리ν¬μ§ν 리 ꡬν
-β
-βββ domain/ # λλ©μΈ κ³μΈ΅
-β βββ entities/ # λΉμ¦λμ€ μν°ν°
-β βββ repositories/ # 리ν¬μ§ν 리 μΈν°νμ΄μ€
-β βββ usecases/ # μ μ€μΌμ΄μ€(λΉμ¦λμ€ λ‘μ§)
-β
-βββ presentation/ # νλ μ ν
μ΄μ
κ³μΈ΅
-β βββ pages/ # νλ©΄ μμ ―
-β βββ providers/ # μν κ΄λ¦¬
-β βββ widgets/ # μ¬μ¬μ© μμ ―
-β
-βββ main.dart # μ± μ§μ
μ
-```
-
-μ΄ κ΅¬μ‘°λ κΈ°λ₯λ³ κ΅¬μ‘°μ κ²°ν©νμ¬ μ¬μ©ν μλ μμ΅λλ€:
-
-```
-lib/
-βββ core/ # κ³΅ν΅ κΈ°λ₯ λ° μ νΈλ¦¬ν°
-β
-βββ features/ # κΈ°λ₯λ³ κ΅¬μ±
-β βββ auth/ # μΈμ¦ κΈ°λ₯
-β β βββ data/
-β β βββ domain/
-β β βββ presentation/
-β β
-β βββ products/ # μν κΈ°λ₯
-β β βββ data/
-β β βββ domain/
-β β βββ presentation/
-β β
-β βββ cart/ # μ₯λ°κ΅¬λ κΈ°λ₯
-β βββ data/
-β βββ domain/
-β βββ presentation/
-β
-βββ main.dart # μ± μ§μ
μ
-```
-
-### 2. κ³μΈ΅λ³ ꡬν λ°©λ²
-
-#### λλ©μΈ κ³μΈ΅(Domain Layer)
-
-λλ©μΈ κ³μΈ΅μ λΉμ¦λμ€ λ‘μ§μ λ΄λΉνλ ν΅μ¬ κ³μΈ΅μΌλ‘, λ€μκ³Ό κ°μ μμλ‘ κ΅¬μ±λ©λλ€:
-
-1. **μν°ν°(Entities)**: λΉμ¦λμ€ κ°μ²΄ λͺ¨λΈ
-2. **리ν¬μ§ν 리 μΈν°νμ΄μ€(Repository Interfaces)**: λ°μ΄ν° μ‘μΈμ€ μΆμν
-3. **μ μ€μΌμ΄μ€(Use Cases)**: λΉμ¦λμ€ λ‘μ§μ μΊ‘μν
-
-```dart
-// 1. μν°ν° μ μ
-// domain/entities/product.dart
-class Product {
- final String id;
- final String name;
- final double price;
- final String description;
-
- const Product({
- required this.id,
- required this.name,
- required this.price,
- required this.description,
- });
-}
-
-// 2. 리ν¬μ§ν 리 μΈν°νμ΄μ€
-// domain/repositories/product_repository.dart
-abstract class ProductRepository {
- Future> getProducts();
- Future getProductById(String id);
- Future saveProduct(Product product);
- Future deleteProduct(String id);
-}
-
-// 3. μ μ€μΌμ΄μ€
-// domain/usecases/get_products.dart
-class GetProducts {
- final ProductRepository repository;
-
- GetProducts(this.repository);
-
- Future> execute() {
- return repository.getProducts();
- }
-}
-
-// domain/usecases/get_product_by_id.dart
-class GetProductById {
- final ProductRepository repository;
-
- GetProductById(this.repository);
-
- Future execute(String id) {
- return repository.getProductById(id);
- }
-}
-```
-
-#### λ°μ΄ν° κ³μΈ΅(Data Layer)
-
-λ°μ΄ν° κ³μΈ΅μ λ°μ΄ν° μ μ₯μ(API, λ‘컬 DB λ±)μμ ν΅μ μ λ΄λΉνκ³ , λλ©μΈ κ³μΈ΅μ μ μλ 리ν¬μ§ν 리 μΈν°νμ΄μ€λ₯Ό ꡬνν©λλ€:
-
-1. **λ°μ΄ν° λͺ¨λΈ(Data Models)**: APIλ DBμ ν΅μ νκΈ° μν DTO(Data Transfer Object)
-2. **λ°μ΄ν° μμ€(Data Sources)**: μ€μ λ°μ΄ν° μ‘μΈμ€ λ‘μ§
-3. **리ν¬μ§ν 리 ꡬν(Repository Implementations)**: λλ©μΈ κ³μΈ΅μ μ μλ μΈν°νμ΄μ€ ꡬν
-
-```dart
-// 1. λ°μ΄ν° λͺ¨λΈ
-// data/models/product_model.dart
-class ProductModel {
- final String id;
- final String name;
- final double price;
- final String description;
-
- const ProductModel({
- required this.id,
- required this.name,
- required this.price,
- required this.description,
- });
-
- // JSON μ§λ ¬ν/μμ§λ ¬ν
- factory ProductModel.fromJson(Map json) {
- return ProductModel(
- id: json['id'],
- name: json['name'],
- price: json['price'].toDouble(),
- description: json['description'],
- );
- }
-
- Map toJson() {
- return {
- 'id': id,
- 'name': name,
- 'price': price,
- 'description': description,
- };
- }
-
- // λλ©μΈ μν°ν°λ‘ λ³ν
- Product toEntity() {
- return Product(
- id: id,
- name: name,
- price: price,
- description: description,
- );
- }
-
- // λλ©μΈ μν°ν°μμ λ³ν
- factory ProductModel.fromEntity(Product product) {
- return ProductModel(
- id: product.id,
- name: product.name,
- price: product.price,
- description: product.description,
- );
- }
-}
-
-// 2. λ°μ΄ν° μμ€
-// data/datasources/remote/product_remote_data_source.dart
-abstract class ProductRemoteDataSource {
- Future> getProducts();
- Future getProductById(String id);
- Future saveProduct(ProductModel product);
- Future deleteProduct(String id);
-}
-
-// data/datasources/remote/product_remote_data_source_impl.dart
-class ProductRemoteDataSourceImpl implements ProductRemoteDataSource {
- final http.Client client;
- final String baseUrl;
-
- ProductRemoteDataSourceImpl({
- required this.client,
- required this.baseUrl,
- });
-
- @override
- Future> getProducts() async {
- try {
- final response = await client.get(
- Uri.parse('$baseUrl/products'),
- headers: {'Content-Type': 'application/json'},
- );
-
- if (response.statusCode == 200) {
- final List jsonList = json.decode(response.body);
- return jsonList.map((json) => ProductModel.fromJson(json)).toList();
- } else {
- throw ServerException();
- }
- } catch (e) {
- throw ServerException();
- }
- }
-
- // λ€λ₯Έ λ©μλ ꡬν...
-}
-
-// 3. 리ν¬μ§ν 리 ꡬν
-// data/repositories/product_repository_impl.dart
-class ProductRepositoryImpl implements ProductRepository {
- final ProductRemoteDataSource remoteDataSource;
- final NetworkInfo networkInfo;
-
- ProductRepositoryImpl({
- required this.remoteDataSource,
- required this.networkInfo,
- });
-
- @override
- Future> getProducts() async {
- if (await networkInfo.isConnected) {
- try {
- final remoteProducts = await remoteDataSource.getProducts();
- return remoteProducts.map((model) => model.toEntity()).toList();
- } on ServerException {
- throw ServerFailure();
- }
- } else {
- throw NetworkFailure();
- }
- }
-
- // λ€λ₯Έ λ©μλ ꡬν...
-}
-```
-
-#### νλ μ ν
μ΄μ
κ³μΈ΅(Presentation Layer)
-
-νλ μ ν
μ΄μ
κ³μΈ΅μ UIμ μ¬μ©μ μνΈμμ©μ λ΄λΉνλ©°, Riverpodμ μ¬μ©νμ¬ μνλ₯Ό κ΄λ¦¬ν©λλ€:
-
-1. **μν κ΄λ¦¬(State Management)**: Riverpod νλ‘λ°μ΄λ
-2. **UI μμ ―(UI Widgets)**: νλ©΄ λ° μ»΄ν¬λνΈ
-
-```dart
-// 1. μν κ΄λ¦¬ (Riverpod)
-// presentation/providers/product_providers.dart
-import 'package:flutter_riverpod/flutter_riverpod.dart';
-
-// μμ‘΄μ± μ£Όμ
μ μν νλ‘λ°μ΄λ
-final productRepositoryProvider = Provider((ref) {
- final remoteDataSource = ref.read(productRemoteDataSourceProvider);
- final networkInfo = ref.read(networkInfoProvider);
-
- return ProductRepositoryImpl(
- remoteDataSource: remoteDataSource,
- networkInfo: networkInfo,
- );
-});
-
-final getProductsUseCaseProvider = Provider((ref) {
- final repository = ref.read(productRepositoryProvider);
- return GetProducts(repository);
-});
-
-// μν νλ‘λ°μ΄λ
-final productsProvider = FutureProvider>((ref) async {
- final getProductsUseCase = ref.read(getProductsUseCaseProvider);
- return getProductsUseCase.execute();
-});
-
-final productDetailsProvider = FutureProvider.family((ref, id) async {
- final getProductByIdUseCase = ref.read(getProductByIdUseCaseProvider);
- return getProductByIdUseCase.execute(id);
-});
-
-// 2. UI μμ ―
-// presentation/pages/product_list_page.dart
-class ProductListPage extends ConsumerWidget {
- @override
- Widget build(BuildContext context, WidgetRef ref) {
- final productsAsync = ref.watch(productsProvider);
-
- return Scaffold(
- appBar: AppBar(title: Text('μν λͺ©λ‘')),
- body: productsAsync.when(
- data: (products) => ListView.builder(
- itemCount: products.length,
- itemBuilder: (context, index) {
- final product = products[index];
- return ListTile(
- title: Text(product.name),
- subtitle: Text('\$${product.price}'),
- onTap: () => Navigator.of(context).pushNamed(
- '/product/${product.id}',
- ),
- );
- },
- ),
- loading: () => Center(child: CircularProgressIndicator()),
- error: (error, stackTrace) => Center(
- child: Text('μ€λ₯κ° λ°μνμ΅λλ€: $error'),
- ),
- ),
- );
- }
-}
-```
-
-### 3. μμ‘΄μ± μ£Όμ
(Dependency Injection)
-
-ν΄λ¦° μν€ν
μ²μμ μμ‘΄μ± μ£Όμ
μ λ§€μ° μ€μν©λλ€. Riverpodμ μ¬μ©νλ©΄ μμ‘΄μ± μ£Όμ
μ ν¨κ³Όμ μΌλ‘ κ΄λ¦¬ν μ μμ΅λλ€:
-
-```dart
-// core/di/injection_container.dart
-import 'package:flutter_riverpod/flutter_riverpod.dart';
-import 'package:http/http.dart' as http;
-
-// μΈνλΌ κ³μΈ΅ νλ‘λ°μ΄λ
-final httpClientProvider = Provider((ref) {
- return http.Client();
-});
-
-final networkInfoProvider = Provider((ref) {
- final connectivity = ref.read(connectivityProvider);
- return NetworkInfoImpl(connectivity);
-});
-
-// λ°μ΄ν° μμ€ νλ‘λ°μ΄λ
-final productRemoteDataSourceProvider = Provider((ref) {
- final client = ref.read(httpClientProvider);
- return ProductRemoteDataSourceImpl(
- client: client,
- baseUrl: 'https://api.example.com',
- );
-});
-
-// 리ν¬μ§ν 리 νλ‘λ°μ΄λ
-final productRepositoryProvider = Provider((ref) {
- final remoteDataSource = ref.read(productRemoteDataSourceProvider);
- final networkInfo = ref.read(networkInfoProvider);
-
- return ProductRepositoryImpl(
- remoteDataSource: remoteDataSource,
- networkInfo: networkInfo,
- );
-});
-
-// μ μ€μΌμ΄μ€ νλ‘λ°μ΄λ
-final getProductsUseCaseProvider = Provider((ref) {
- final repository = ref.read(productRepositoryProvider);
- return GetProducts(repository);
-});
-
-final getProductByIdUseCaseProvider = Provider((ref) {
- final repository = ref.read(productRepositoryProvider);
- return GetProductById(repository);
-});
-```
-
-### 4. μλ¬ μ²λ¦¬
-
-ν΄λ¦° μν€ν
μ²μμ μλ¬ μ²λ¦¬λ μΌκ΄λκ³ μ²΄κ³μ μΌλ‘ μ΄λ£¨μ΄μ ΈμΌ ν©λλ€:
-
-```dart
-// core/error/exceptions.dart
-class ServerException implements Exception {}
-class CacheException implements Exception {}
-class NetworkException implements Exception {}
-
-// core/error/failures.dart
-abstract class Failure {}
-
-class ServerFailure extends Failure {}
-class CacheFailure extends Failure {}
-class NetworkFailure extends Failure {}
-
-// μλ¬ μ²λ¦¬ μμ
-// data/repositories/product_repository_impl.dart
-@override
-Future> getProducts() async {
- if (await networkInfo.isConnected) {
- try {
- final remoteProducts = await remoteDataSource.getProducts();
- return remoteProducts.map((model) => model.toEntity()).toList();
- } on ServerException {
- throw ServerFailure();
- }
- } else {
- throw NetworkFailure();
- }
-}
-```
-
-## μ€μ Flutter μμ : Todo μ±
-
-μ€μ Todo μ± μμ λ₯Ό ν΅ν΄ ν΄λ¦° μν€ν
μ²λ₯Ό μ μ©νλ λ°©λ²μ μ΄ν΄λ³΄κ² μ΅λλ€.
-
-### λλ©μΈ κ³μΈ΅ ꡬν
-
-```dart
-// domain/entities/todo.dart
-class Todo {
- final String id;
- final String title;
- final String description;
- final bool completed;
-
- const Todo({
- required this.id,
- required this.title,
- required this.description,
- required this.completed,
- });
-}
-
-// domain/repositories/todo_repository.dart
-abstract class TodoRepository {
- Future> getTodos();
- Future getTodoById(String id);
- Future addTodo(Todo todo);
- Future updateTodo(Todo todo);
- Future deleteTodo(String id);
-}
-
-// domain/usecases/get_todos.dart
-class GetTodos {
- final TodoRepository repository;
-
- GetTodos(this.repository);
-
- Future> execute() {
- return repository.getTodos();
- }
-}
-
-// domain/usecases/add_todo.dart
-class AddTodo {
- final TodoRepository repository;
-
- AddTodo(this.repository);
-
- Future execute(Todo todo) {
- return repository.addTodo(todo);
- }
-}
-```
-
-### λ°μ΄ν° κ³μΈ΅ ꡬν
-
-```dart
-// data/models/todo_model.dart
-import 'package:json_annotation/json_annotation.dart';
-
-part 'todo_model.g.dart';
-
-@JsonSerializable()
-class TodoModel {
- final String id;
- final String title;
- final String description;
- final bool completed;
-
- const TodoModel({
- required this.id,
- required this.title,
- required this.description,
- required this.completed,
- });
-
- factory TodoModel.fromJson(Map json) =>
- _$TodoModelFromJson(json);
-
- Map toJson() => _$TodoModelToJson(this);
-
- // λ³ν λ©μλ
- Todo toEntity() => Todo(
- id: id,
- title: title,
- description: description,
- completed: completed,
- );
-
- factory TodoModel.fromEntity(Todo todo) => TodoModel(
- id: todo.id,
- title: todo.title,
- description: todo.description,
- completed: todo.completed,
- );
-}
-
-// data/datasources/todo_remote_data_source.dart
-abstract class TodoRemoteDataSource {
- Future> getTodos();
- Future getTodoById(String id);
- Future addTodo(TodoModel todo);
- Future updateTodo(TodoModel todo);
- Future deleteTodo(String id);
-}
-
-// data/datasources/todo_remote_data_source_impl.dart
-class TodoRemoteDataSourceImpl implements TodoRemoteDataSource {
- final http.Client client;
- final String baseUrl;
-
- TodoRemoteDataSourceImpl({
- required this.client,
- required this.baseUrl,
- });
-
- @override
- Future> getTodos() async {
- try {
- final response = await client.get(
- Uri.parse('$baseUrl/todos'),
- headers: {'Content-Type': 'application/json'},
- );
-
- if (response.statusCode == 200) {
- final List jsonList = json.decode(response.body);
- return jsonList.map((json) => TodoModel.fromJson(json)).toList();
- } else {
- throw ServerException();
- }
- } catch (e) {
- throw ServerException();
- }
- }
-
- // λ€λ₯Έ λ©μλ ꡬν...
-}
-
-// data/repositories/todo_repository_impl.dart
-class TodoRepositoryImpl implements TodoRepository {
- final TodoRemoteDataSource remoteDataSource;
- final NetworkInfo networkInfo;
-
- TodoRepositoryImpl({
- required this.remoteDataSource,
- required this.networkInfo,
- });
-
- @override
- Future> getTodos() async {
- if (await networkInfo.isConnected) {
- try {
- final remoteTodos = await remoteDataSource.getTodos();
- return remoteTodos.map((model) => model.toEntity()).toList();
- } on ServerException {
- throw ServerFailure();
- }
- } else {
- throw NetworkFailure();
- }
- }
-
- // λ€λ₯Έ λ©μλ ꡬν...
-}
-```
-
-### νλ μ ν
μ΄μ
κ³μΈ΅ ꡬν
-
-```dart
-// presentation/providers/todo_providers.dart
-import 'package:flutter_riverpod/flutter_riverpod.dart';
-
-// 리ν¬μ§ν 리 νλ‘λ°μ΄λ
-final todoRepositoryProvider = Provider((ref) {
- final remoteDataSource = ref.read(todoRemoteDataSourceProvider);
- final networkInfo = ref.read(networkInfoProvider);
-
- return TodoRepositoryImpl(
- remoteDataSource: remoteDataSource,
- networkInfo: networkInfo,
- );
-});
-
-// μ μ€μΌμ΄μ€ νλ‘λ°μ΄λ
-final getTodosUseCaseProvider = Provider((ref) {
- final repository = ref.read(todoRepositoryProvider);
- return GetTodos(repository);
-});
-
-final addTodoUseCaseProvider = Provider((ref) {
- final repository = ref.read(todoRepositoryProvider);
- return AddTodo(repository);
-});
-
-// μν νλ‘λ°μ΄λ
-final todosProvider = FutureProvider>((ref) async {
- final getTodosUseCase = ref.read(getTodosUseCaseProvider);
- return getTodosUseCase.execute();
-});
-
-// μν κ΄λ¦¬ λ
Έν°νμ΄μ΄
-final todoListNotifierProvider = StateNotifierProvider>>((ref) {
- final getTodosUseCase = ref.read(getTodosUseCaseProvider);
- final addTodoUseCase = ref.read(addTodoUseCaseProvider);
-
- return TodoListNotifier(
- getTodosUseCase: getTodosUseCase,
- addTodoUseCase: addTodoUseCase,
- );
-});
-
-class TodoListNotifier extends StateNotifier>> {
- final GetTodos getTodosUseCase;
- final AddTodo addTodoUseCase;
-
- TodoListNotifier({
- required this.getTodosUseCase,
- required this.addTodoUseCase,
- }) : super(const AsyncValue.loading()) {
- _loadTodos();
- }
-
- Future _loadTodos() async {
- state = const AsyncValue.loading();
- try {
- final todos = await getTodosUseCase.execute();
- state = AsyncValue.data(todos);
- } catch (e, stackTrace) {
- state = AsyncValue.error(e, stackTrace);
- }
- }
-
- Future addTodo(Todo todo) async {
- try {
- await addTodoUseCase.execute(todo);
- _loadTodos(); // λͺ©λ‘ λ€μ λ‘λ
- } catch (e) {
- // μ€λ₯ μ²λ¦¬
- }
- }
-}
-
-// presentation/pages/todo_list_page.dart
-class TodoListPage extends ConsumerWidget {
- @override
- Widget build(BuildContext context, WidgetRef ref) {
- final todosAsync = ref.watch(todoListNotifierProvider);
-
- return Scaffold(
- appBar: AppBar(title: Text('Todo λͺ©λ‘')),
- body: todosAsync.when(
- data: (todos) => ListView.builder(
- itemCount: todos.length,
- itemBuilder: (context, index) {
- final todo = todos[index];
- return ListTile(
- title: Text(todo.title),
- subtitle: Text(todo.description),
- trailing: Checkbox(
- value: todo.completed,
- onChanged: (value) {
- // 체ν¬λ°μ€ μν λ³κ²½ μ²λ¦¬
- },
- ),
- );
- },
- ),
- loading: () => Center(child: CircularProgressIndicator()),
- error: (error, stackTrace) => Center(
- child: Text('μ€λ₯κ° λ°μνμ΅λλ€: $error'),
- ),
- ),
- floatingActionButton: FloatingActionButton(
- onPressed: () {
- // μ Todo μΆκ° νλ©΄μΌλ‘ μ΄λ
- Navigator.of(context).pushNamed('/add-todo');
- },
- child: Icon(Icons.add),
- ),
- );
- }
-}
-```
-
-## ν΄λ¦° μν€ν
μ²μ μ₯μ κ³Ό λ¨μ
-
-### μ₯μ
-
-1. **κ΄μ¬μ¬ λΆλ¦¬**: λΉμ¦λμ€ λ‘μ§, λ°μ΄ν° μ‘μΈμ€, UIκ° λͺ
νν λΆλ¦¬λ©λλ€.
-2. **ν
μ€νΈ μ©μ΄μ±**: κ° κ³μΈ΅μ΄ λ
립μ μ΄λ―λ‘ λ¨μ ν
μ€νΈκ° μ©μ΄ν©λλ€.
-3. **μ μ§λ³΄μμ±**: μ½λκ° κ΅¬μ‘°νλμ΄ μμ΄ λ³κ²½μ΄ νμν λ μν₯ λ²μκ° μ νμ μ
λλ€.
-4. **νμ₯μ±**: μλ‘μ΄ κΈ°λ₯μ μΆκ°νκ±°λ κΈ°μ‘΄ ꡬνμ κ΅μ²΄νκΈ° μ½μ΅λλ€.
-5. **νλ μμν¬ λ
립μ±**: ν΅μ¬ λΉμ¦λμ€ λ‘μ§μ΄ Flutterλ μΈλΆ λΌμ΄λΈλ¬λ¦¬μ μμ‘΄νμ§ μμ΅λλ€.
-
-### λ¨μ
-
-1. **μ΄κΈ° μ€μ 볡μ‘μ±**: μμ νλ‘μ νΈμμλ κ³Όλν 보μΌλ¬νλ μ΄νΈ μ½λκ° μκΈΈ μ μμ΅λλ€.
-2. **νμ΅ κ³‘μ **: νμλ€μ΄ μν€ν
μ²λ₯Ό μ΄ν΄νκ³ μ μ©νλ λ° μκ°μ΄ νμν©λλ€.
-3. **κ°λ° μλ**: μ΄κΈ°μλ κ°λ° μλκ° λλ €μ§ μ μμ΅λλ€.
-4. **μ½λλ μ¦κ°**: μΈν°νμ΄μ€, ꡬν체, λͺ¨λΈ λ³ν λ±μΌλ‘ μΈν΄ μ½λλμ΄ μ¦κ°ν©λλ€.
-
-## μ€μ©μ μΈ μ κ·Ό λ°©μ
-
-λͺ¨λ νλ‘μ νΈμ μμ ν ν΄λ¦° μν€ν
μ²κ° νμν κ²μ μλλλ€. νλ‘μ νΈ κ·λͺ¨μ ν νΉμ±μ
-λ°λΌ λ€μκ³Ό κ°μ΄ μ κ·Όν μ μμ΅λλ€:
-
-### μκ·λͺ¨ νλ‘μ νΈ
-
-μκ·λͺ¨ νλ‘μ νΈμμλ κ°μνλ μν€ν
μ²λ₯Ό μ μ©ν μ μμ΅λλ€:
-
-```
-lib/
-βββ data/ # λ°μ΄ν° μ‘μΈμ€
-β βββ models/
-β βββ repositories/
-βββ screens/ # UI νλ©΄
-β βββ home/
-β βββ details/
-βββ providers/ # μν κ΄λ¦¬
-βββ main.dart
-```
-
-### μ€κ·λͺ¨ νλ‘μ νΈ
-
-μ€κ·λͺ¨ νλ‘μ νΈμμλ λλ©μΈ κ³μΈ΅μ μΌλΆ κ°λ
μ λμ
ν μ μμ΅λλ€:
-
-```
-lib/
-βββ data/ # λ°μ΄ν° κ³μΈ΅
-β βββ models/
-β βββ repositories/
-βββ domain/ # λλ©μΈ κ³μΈ΅ (κ°μν)
-β βββ usecases/
-βββ presentation/ # νλ μ ν
μ΄μ
κ³μΈ΅
-β βββ pages/
-β βββ providers/
-βββ core/ # μ νΈλ¦¬ν°
-βββ main.dart
-```
-
-### λκ·λͺ¨ νλ‘μ νΈ
-
-λκ·λͺ¨ νλ‘μ νΈμμλ μμ ν ν΄λ¦° μν€ν
μ²μ κΈ°λ₯λ³ κ΅¬μ‘°λ₯Ό κ²°ν©ν μ μμ΅λλ€:
-
-```
-lib/
-βββ core/
-βββ features/
-β βββ auth/
-β β βββ data/
-β β βββ domain/
-β β βββ presentation/
-β βββ home/
-β β βββ data/
-β β βββ domain/
-β β βββ presentation/
-β βββ settings/
-β βββ data/
-β βββ domain/
-β βββ presentation/
-βββ main.dart
-```
-
-## μ μ§μ λμ
μ λ΅
-
-κΈ°μ‘΄ νλ‘μ νΈμ ν΄λ¦° μν€ν
μ²λ₯Ό λμ
ν λλ μ μ§μ μΈ μ κ·Όμ΄ νμν©λλ€:
-
-1. **κ³μΈ΅ λΆλ¦¬λΆν° μμ**: λ¨Όμ UI, λΉμ¦λμ€ λ‘μ§, λ°μ΄ν° μ‘μΈμ€ μ½λλ₯Ό λΆλ¦¬ν©λλ€.
-2. **ν κΈ°λ₯μ© λ¦¬ν©ν λ§**: μλ‘μ΄ κΈ°λ₯μ΄λ μ€μν κΈ°λ₯λΆν° ν΄λ¦° μν€ν
μ²λ‘ 리ν©ν λ§ν©λλ€.
-3. **ν
μ€νΈ μμ±**: 리ν©ν λ§κ³Ό ν¨κ» λ¨μ ν
μ€νΈλ₯Ό μμ±νμ¬ μμ μ±μ ν보ν©λλ€.
-4. **μΈν°νμ΄μ€ λμ
**: μ μ§μ μΌλ‘ μΈν°νμ΄μ€μ μμ‘΄μ± μ£Όμ
μ λμ
ν©λλ€.
-
-## κ²°λ‘
-
-ν΄λ¦° μν€ν
μ²λ Flutter μ±μ νμ₯μ±, μ μ§λ³΄μμ±, ν
μ€νΈ μ©μ΄μ±μ ν¬κ² ν₯μμν¬ μ μμ΅λλ€. νμ§λ§ λͺ¨λ νλ‘μ νΈμ λμΌν μμ€μΌλ‘ μ μ©ν νμλ μμΌλ©°, νλ‘μ νΈμ κ·λͺ¨μ νΉμ±μ λ§κ² μ μ ν μ‘°μ νλ κ²μ΄ μ€μν©λλ€.
-
-μ²μμλ 볡μ‘ν΄ λ³΄μΌ μ μμ§λ§, ν΅μ¬ μμΉμ μ΄ν΄νκ³ μ μ§μ μΌλ‘ λμ
νλ€λ©΄ μ₯κΈ°μ μΌλ‘ λ μμ μ μ΄κ³ μ μ§λ³΄μνκΈ° μ¬μ΄ μ½λλ² μ΄μ€λ₯Ό ꡬμΆν μ μμ΅λλ€. νΉν ν κ·λͺ¨κ° ν¬κ±°λ μ±μ΄ κ³μ μ±μ₯ν κ²μΌλ‘ μμλλ κ²½μ° ν΄λ¦° μν€ν
μ²μ λμ
μ κ³ λ €ν΄λ³Ό κ°μΉκ° μμ΅λλ€.