yutrutasu

๐Ÿ—๏ธ ใ‚ขใƒผใ‚ญใƒ†ใ‚ฏใƒใƒฃ่จญ่จˆๆ›ธ

๐Ÿ“‹ ๆฆ‚่ฆ

ใ‚†ใ‚‹ใŸใ™ใ‚ขใƒ—ใƒชใ‚ฑใƒผใ‚ทใƒงใƒณใฏใ€Flutter MVCใ‚ขใƒผใ‚ญใƒ†ใ‚ฏใƒใƒฃใƒ‘ใ‚ฟใƒผใƒณใ‚’ๆŽก็”จใ—ใ€ไฟๅฎˆๆ€งใจๆ‹กๅผตๆ€งใ‚’้‡่ฆ–ใ—ใŸ่จญ่จˆใจใชใฃใฆใ„ใพใ™ใ€‚

๐ŸŽฏ ใ‚ขใƒผใ‚ญใƒ†ใ‚ฏใƒใƒฃๅŽŸๅ‰‡

1. ้–ขๅฟƒใฎๅˆ†้›ข๏ผˆSeparation of Concerns๏ผ‰

2. ๅ˜ไธ€่ฒฌไปปใฎๅŽŸๅ‰‡๏ผˆSingle Responsibility Principle๏ผ‰

3. ไพๅญ˜ๆ€ง้€†่ปขใฎๅŽŸๅ‰‡๏ผˆDependency Inversion Principle๏ผ‰

๐Ÿ›๏ธ ใƒฌใ‚คใƒคใƒผใ‚ขใƒผใ‚ญใƒ†ใ‚ฏใƒใƒฃ

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚           Presentation Layer        โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”   โ”‚
โ”‚  โ”‚    Views    โ”‚  โ”‚   Dialogs   โ”‚   โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜   โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                    โ”‚
                    โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚          Application Layer          โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”   โ”‚
โ”‚  โ”‚ Controllers โ”‚  โ”‚   Services  โ”‚   โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜   โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                    โ”‚
                    โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚            Domain Layer             โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”   โ”‚
โ”‚  โ”‚   Models    โ”‚  โ”‚   Entities  โ”‚   โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜   โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                    โ”‚
                    โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚       Infrastructure Layer          โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”   โ”‚
โ”‚  โ”‚   Storage   โ”‚  โ”‚   External  โ”‚   โ”‚
โ”‚  โ”‚             โ”‚  โ”‚   Services  โ”‚   โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜   โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

๐Ÿ“ฆ ใ‚ณใƒณใƒใƒผใƒใƒณใƒˆ่ฉณ็ดฐ

1. Presentation Layer๏ผˆใƒ—ใƒฌใ‚ผใƒณใƒ†ใƒผใ‚ทใƒงใƒณๅฑค๏ผ‰

Views

// ็”ป้ขใ‚ณใƒณใƒใƒผใƒใƒณใƒˆ
- YaruKotoListView         // ใƒ—ใƒญใ‚ธใ‚งใ‚ฏใƒˆไธ€่ฆง็”ป้ข
- YaruKotoDetailView       // ใƒ—ใƒญใ‚ธใ‚งใ‚ฏใƒˆ่ฉณ็ดฐ็”ป้ข๏ผˆ้ …็›ฎไธ€่ฆง๏ผ‰
- TaskItemDetailView       // ้ …็›ฎ่ฉณ็ดฐ็”ป้ข๏ผˆใ‚ฟใ‚นใ‚ฏไธ€่ฆง๏ผ‰
- SettingsView             // ่จญๅฎš็”ป้ข

Dialogs

// ใƒ€ใ‚คใ‚ขใƒญใ‚ฐใ‚ณใƒณใƒใƒผใƒใƒณใƒˆ
- AddYaruKotoDialog        // ใƒ—ใƒญใ‚ธใ‚งใ‚ฏใƒˆ่ฟฝๅŠ ใƒ€ใ‚คใ‚ขใƒญใ‚ฐ
- EditYaruKotoDialog       // ใƒ—ใƒญใ‚ธใ‚งใ‚ฏใƒˆ็ทจ้›†ใƒ€ใ‚คใ‚ขใƒญใ‚ฐ
- AddTaskItemDialog        // ้ …็›ฎ่ฟฝๅŠ ใƒ€ใ‚คใ‚ขใƒญใ‚ฐ
- EditTaskItemDialog       // ้ …็›ฎ็ทจ้›†ใƒ€ใ‚คใ‚ขใƒญใ‚ฐ
- AddTaskDialog            // ใ‚ฟใ‚นใ‚ฏ่ฟฝๅŠ ใƒ€ใ‚คใ‚ขใƒญใ‚ฐ
- EditTaskDialog           // ใ‚ฟใ‚นใ‚ฏ็ทจ้›†ใƒ€ใ‚คใ‚ขใƒญใ‚ฐ

2. Application Layer๏ผˆใ‚ขใƒ—ใƒชใ‚ฑใƒผใ‚ทใƒงใƒณๅฑค๏ผ‰

Controllers

// ็Šถๆ…‹็ฎก็†ใƒปใƒ“ใ‚ธใƒใ‚นใƒญใ‚ธใƒƒใ‚ฏๅˆถๅพก
YaruKotoController extends ChangeNotifier {
  - List<YaruKoto> _yaruKotoList
  - bool _isLoading
  
  // ใƒ—ใƒญใ‚ธใ‚งใ‚ฏใƒˆๆ“ไฝœ
  + addYaruKoto(String title, String? description)
  + updateYaruKoto(String id, String? title, String? description)
  + deleteYaruKoto(String id)
  + reorderYaruKoto(int oldIndex, int newIndex)
  
  // ้ …็›ฎๆ“ไฝœ๏ผˆไธญ้–“้šŽๅฑค๏ผ‰
  + addTaskItem(String yaruKotoId, String itemTitle, String? description)
  + updateTaskItem(String yaruKotoId, String taskItemId, String? title, String? description)
  + deleteTaskItem(String yaruKotoId, String taskItemId)
  + reorderTaskItems(String yaruKotoId, int oldIndex, int newIndex)
  
  // ใ‚ฟใ‚นใ‚ฏๆ“ไฝœ๏ผˆๆœ€ๅฐๅ˜ไฝ๏ผ‰
  + addTask(String yaruKotoId, String taskItemId, String taskTitle)
  + updateTask(String yaruKotoId, String taskItemId, String taskId, String newTitle)
  + updateTaskProgress(String yaruKotoId, String taskItemId, String taskId, TaskProgress progress)
  + nextTaskProgress(String yaruKotoId, String taskItemId, String taskId)
  + deleteTask(String yaruKotoId, String taskItemId, String taskId)
  + reorderTasks(String yaruKotoId, String taskItemId, int oldIndex, int newIndex)
}

Services

// ๅค–้ƒจใ‚ตใƒผใƒ“ใ‚นใจใฎ้€ฃๆบ
YaruKotoService {
  + getAllYaruKoto(): Future<List<YaruKoto>>
  + saveYaruKoto(List<YaruKoto> list): Future<void>
  + addYaruKoto(YaruKoto yaruKoto): Future<void>
  + updateYaruKoto(YaruKoto yaruKoto): Future<void>
  + deleteYaruKoto(String id): Future<void>
}

AdMobService {
  + initialize(): Future<void>
  + loadBannerAd(): BannerAd
  + loadInterstitialAd(): InterstitialAd
}

3. Domain Layer๏ผˆใƒ‰ใƒกใ‚คใƒณๅฑค๏ผ‰

Models

// ใƒ“ใ‚ธใƒใ‚นใƒ‰ใƒกใ‚คใƒณใฎใ‚จใƒณใƒ†ใ‚ฃใƒ†ใ‚ฃ
class YaruKoto {
  final String id;
  final String title;
  final String? description;
  final List<TaskItem> items;
  final DateTime createdAt;
  
  double get progressPercentage; // ้ …็›ฎใฎๅนณๅ‡ๅ€ค
  String get progressLabel;
}

class TaskItem {
  final String id;
  final String title;
  final String? description;
  final List<Task> tasks;
  final DateTime createdAt;
  
  double get progressPercentage; // ใ‚ฟใ‚นใ‚ฏใฎๅนณๅ‡ๅ€ค
  String get progressLabel;
}

class Task {
  final String id;
  final String title;
  final TaskProgress progress;
  final DateTime createdAt;
}

enum TaskProgress {
  notStarted(0, 'ๆœช็€ๆ‰‹'),
  inProgress(50, 'ใกใ‚‡ใฃใจใ‚„ใฃใŸ'),
  completed(100, 'ๅฎŒไบ†');
}

4. Infrastructure Layer๏ผˆใ‚คใƒณใƒ•ใƒฉใ‚นใƒˆใƒฉใ‚ฏใƒใƒฃๅฑค๏ผ‰

Storage

// ใƒ‡ใƒผใ‚ฟๆฐธ็ถšๅŒ–
SharedPreferences {
  + getString(String key): String?
  + setString(String key, String value): Future<bool>
}

External Services

// ๅค–้ƒจใ‚ตใƒผใƒ“ใ‚น
GoogleMobileAds {
  + initialize(): Future<void>
  + BannerAd
  + InterstitialAd
  + NativeAd
}

๐Ÿ”„ ใƒ‡ใƒผใ‚ฟใƒ•ใƒญใƒผ

1. ใƒฆใƒผใ‚ถใƒผใ‚ขใ‚ฏใ‚ทใƒงใƒณ โ†’ UIๆ›ดๆ–ฐ

User Action โ†’ View โ†’ Controller โ†’ Service โ†’ Model โ†’ Controller โ†’ View

2. ใƒ‡ใƒผใ‚ฟๆฐธ็ถšๅŒ–ใƒ•ใƒญใƒผ

Controller โ†’ Service โ†’ SharedPreferences โ†’ JSON Storage

3. ใ‚ขใƒ—ใƒช่ตทๅ‹•ๆ™‚ใฎใƒ‡ใƒผใ‚ฟ่ชญใฟ่พผใฟ

App Start โ†’ Controller.loadData() โ†’ Service.getAllYaruKoto() โ†’ SharedPreferences โ†’ JSON Parse โ†’ Model List

๐ŸŽจ ็Šถๆ…‹็ฎก็†ใƒ‘ใ‚ฟใƒผใƒณ

Provider Pattern

// main.dart
ChangeNotifierProvider(
  create: (context) => YaruKotoController(),
  child: MyApp(),
)

// View
Consumer<YaruKotoController>(
  builder: (context, controller, child) {
    return ListView.builder(
      itemCount: controller.yaruKotoList.length,
      itemBuilder: (context, index) {
        return YaruKotoCard(controller.yaruKotoList[index]);
      },
    );
  },
)

๐Ÿš€ ๆ‹กๅผตๆ€งใฎ่€ƒๆ…ฎ

1. ๆ–ฐๆฉŸ่ƒฝ่ฟฝๅŠ ๆ™‚

// ๆ–ฐใ—ใ„ใ‚ตใƒผใƒ“ใ‚นใฎ่ฟฝๅŠ 
abstract class StatisticsService {
  Future<Statistics> getStatistics();
}

class StatisticsServiceImpl implements StatisticsService {
  @override
  Future<Statistics> getStatistics() async {
    // ๅฎŸ่ฃ…
  }
}

2. ใƒ‡ใƒผใ‚ฟใ‚นใƒˆใƒฌใƒผใ‚ธใฎๅค‰ๆ›ด

// ๆŠฝ่ฑกๅŒ–ใซใ‚ˆใ‚Šใ‚นใƒˆใƒฌใƒผใ‚ธๅค‰ๆ›ดใŒๅฎนๆ˜“
abstract class StorageService {
  Future<List<YaruKoto>> getAllYaruKoto();
  Future<void> saveYaruKoto(List<YaruKoto> list);
}

class SharedPreferencesStorage implements StorageService {
  // SharedPreferencesๅฎŸ่ฃ…
}

class SqliteStorage implements StorageService {
  // SQLiteๅฎŸ่ฃ…
}

๐Ÿ”ง ใƒ†ใ‚นใƒˆๆˆฆ็•ฅ

1. ๅ˜ไฝ“ใƒ†ใ‚นใƒˆ

// ใƒขใƒ‡ใƒซใฎใƒ†ใ‚นใƒˆ
test('YaruKoto progress calculation', () {
  final yaruKoto = YaruKoto(
    id: '1',
    title: 'Test Project',
    items: [
      TaskItem(id: '1', title: 'Task 1', progress: TaskProgress.completed),
      TaskItem(id: '2', title: 'Task 2', progress: TaskProgress.inProgress),
    ],
    createdAt: DateTime.now(),
  );
  
  expect(yaruKoto.progressPercentage, 75.0);
});

2. ็ตฑๅˆใƒ†ใ‚นใƒˆ

// Controller + Service ใฎใƒ†ใ‚นใƒˆ
testWidgets('Add YaruKoto integration test', (tester) async {
  final controller = YaruKotoController();
  await controller.addYaruKoto('Test Project');
  
  expect(controller.yaruKotoList.length, 1);
  expect(controller.yaruKotoList.first.title, 'Test Project');
});

๐Ÿ“ˆ ใƒ‘ใƒ•ใ‚ฉใƒผใƒžใƒณใ‚นๆœ€้ฉๅŒ–

1. ListViewๆœ€้ฉๅŒ–

ListView.builder(
  itemCount: items.length,
  itemBuilder: (context, index) {
    return YaruKotoCard(key: ValueKey(items[index].id), item: items[index]);
  },
)

2. ็Šถๆ…‹็ฎก็†ๆœ€้ฉๅŒ–

// ๅฟ…่ฆใช้ƒจๅˆ†ใฎใฟๆ›ดๆ–ฐ
Consumer<YaruKotoController>(
  builder: (context, controller, child) {
    return Text(controller.yaruKotoList.length.toString());
  },
)

ใ“ใฎ่จญ่จˆใซใ‚ˆใ‚Šใ€ไฟๅฎˆๆ€งใŒ้ซ˜ใใ€ๆ‹กๅผตใ—ใ‚„ใ™ใ„ใ‚ขใƒ—ใƒชใ‚ฑใƒผใ‚ทใƒงใƒณใ‚’ๅฎŸ็พใ—ใฆใ„ใพใ™ใ€‚