{"id":"architecture-feature-first","name":"architecture-feature-first","summary":"機能の作成、フォルダ構造の設計、リポジトリ/サービス/ビューモデルの追加、依存関係注入の配線、またはどのレイヤーがロジックを所有するかの判断時に使用します。","body":"# Flutter Architecture — Feature-First Skill\n\nThis skill defines how to design, structure, and implement Flutter applications using the recommended **layered architecture** with **feature-first** file organization.\n\nIt is **state management agnostic**: the business logic holder in the UI layer may be named ViewModel, Controller, Cubit, Bloc, Provider, or Notifier — depending on the chosen state management approach. The architectural rules apply equally to all of them.\n\n## When to Use\n\nUse this skill when:\n\n* Designing the folder/file structure of a new Flutter app or feature.\n* Creating a new View, ViewModel, Repository, or Service.\n* Deciding which layer owns a piece of logic.\n* Wiring dependency injection between components.\n* Adding a domain (logic) layer for complex business logic.\n* Refactoring an existing app from type-first to feature-first organization.\n\n---\n\n## 1. Layers\n\nSeparate every app into a **UI Layer** and a **Data Layer**. Add a **Logic (Domain) Layer** between them only for complex apps.\n\n```\n┌──────────────────────────────────────────────────────────────┐\n│   UI Layer    │  Views + business logic holders              │\n│               │  (ViewModel / Cubit / Controller / Provider) │\n├──────────────────────────────────────────────────────────────┤\n│  Logic Layer  │  Use Cases / Interactors  (optional)         │\n├──────────────────────────────────────────────────────────────┤\n│   Data Layer  │  Repositories + Services                     │\n└──────────────────────────────────────────────────────────────┘\n```\n\n**Rules:**\n- Only adjacent layers may communicate. The UI layer must never access a Service directly.\n- The Logic layer is added **only** when business logic is too complex for the business logic holder or is reused across multiple screens.\n- Data changes always happen in the Data layer (SSOT = Repository). No mutation in UI or Logic layers.\n- Follow unidirectional data flow: state flows **down** (Data → UI), events flow **up** (UI → Data).\n\n---\n\n## 2. Feature-First File Structure\n\nOrganize code by **feature**, not by type. Group all layers belonging to one feature together in a single directory.\n\n### Sample directory structure\n\n```\nlib/\n├── app.dart\n├── main.dart\n├── core/                          # Shared utilities, theme, DI setup\n│   ├── di/\n│   │   └── service_locator.dart\n│   ├── theme/\n│   │   └── app_theme.dart\n│   └── network/\n│       └── api_client.dart\n├── features/\n│   ├── auth/\n│   │   ├── data/\n│   │   │   ├── auth_repository.dart\n│   │   │   └── auth_api_service.dart\n│   │   ├── domain/                # Optional — only for complex logic\n│   │   │   └── login_usecase.dart\n│   │   └── ui/\n│   │       ├── auth_viewmodel.dart\n│   │       ├── login_screen.dart\n│   │       └── widgets/\n│   │           └── login_form.dart\n│   └── profile/\n│       ├── data/\n│       │   ├── profile_repository.dart\n│       │   └── profile_api_service.dart\n│       └── ui/\n│           ├── profile_viewmodel.dart\n│           └── profile_screen.dart\n└── shared/                        # Shared widgets, models, extensions\n    ├── models/\n    │   └── user.dart\n    └── widgets/\n        └── loading_indicator.dart\n```\n\nEach feature directory contains the files needed for that feature, named according to the chosen state management approach:\n\n| Approach | Business logic holder file |\n|---|---|\n| MVVM / ChangeNotifier | `*_viewmodel.dart` / `*_controller.dart` |\n| BLoC | `*_cubit.dart` / `*_bloc.dart` |\n| Provider / Riverpod | `*_provider.dart` / `*_notifier.dart` |\n\n---\n\n## 3. Component Responsibilities\n\n### View\n- Describes how to present data to the user; keep logic minimal and only UI-related.\n- Passes events to the business logic holder in response to user interactions.\n- Extract reusable widgets into separate components within a `widgets/` subdirectory.\n- Use `StatelessWidget` when possible; keep build methods simple.\n\n### Business Logic Holder (ViewModel / Cubit / Controller / Provider)\n- Contains logic to convert app data into UI state and maintains current state needed by the view.\n- Exposes callbacks (commands) to the View and retrieves/transforms data from repositories.\n\n```dart\nclass AuthViewModel extends ChangeNotifier {\n  final AuthRepository _authRepo;\n  AuthViewModel(this._authRepo);\n\n  bool _isLoading = false;\n  bool get isLoading => _isLoading;\n\n  String? _error;\n  String? get error => _error;\n\n  Future<bool> login(String email, String password) async {\n    _isLoading = true;\n    _error = null;\n    notifyListeners();\n    try {\n      await _authRepo.login(email, password);\n      return true;\n    } catch (e) {\n      _error = e.toString();\n      return false;\n    } finally {\n      _isLoading = false;\n      notifyListeners();\n    }\n  }\n}\n```\n\n### Repository\n- Single Source of Truth (SSOT) for a given type of model data.\n- The only class allowed to mutate its data; all other classes read from it.\n- Handles caching, error handling, and data refresh logic.\n- Transforms raw data from services into domain models.\n\n### Service\n- Wraps API endpoints and exposes asynchronous response objects.\n- Isolates data-loading and holds no state.\n\n---\n\n## 4. Domain Layer (Use Cases)\n\nIntroduce use cases/interactors **only** when:\n- Logic is complex or does not fit cleanly in the UI or Data layers.\n- Logic is reused across multiple business logic holders or merges data from multiple repositories.\n\nDo not add a domain layer for simple CRUD apps.\n\n---\n\n## 5. Dependency Injection\n\nUse dependency injection to provide components with their dependencies, enabling testability and flexibility.\n\n- Supply repositories to business logic holders via constructors.\n- Supply services to repositories via constructors.\n- Define abstract interfaces so implementations can be swapped without changing consumers.\n\n```dart\n// In service_locator.dart — register dependencies at startup\nvoid setupDependencies() {\n  final apiClient = ApiClient();\n\n  // Services\n  final authService = AuthApiService(apiClient);\n  final profileService = ProfileApiService(apiClient);\n\n  // Repositories\n  final authRepo = AuthRepository(authService);\n  final profileRepo = ProfileRepository(profileService);\n\n  // Register with your DI framework (get_it, provider, riverpod, etc.)\n  getIt.registerSingleton<AuthRepository>(authRepo);\n  getIt.registerSingleton<ProfileRepository>(profileRepo);\n}\n```\n\n---\n\n## 6. Workflow: Add a New Feature\n\n1. **Create the `features/<name>/` directory** with `data/`, `ui/`, and optionally `domain/` subdirectories.\n2. **Implement the Service** — wrap the API endpoints in `data/<name>_api_service.dart`.\n3. **Implement the Repository** — inject the Service, add caching/error handling in `data/<name>_repository.dart`.\n4. **Implement the ViewModel** — inject the Repository, expose UI state and commands in `ui/<name>_viewmodel.dart`.\n5. **Implement the View** — bind to the ViewModel, render state, dispatch events in `ui/<name>_screen.dart`.\n6. **Register in DI** — add the new Service, Repository, and ViewModel to the service locator.\n7. **Verify** — confirm the View never accesses the Service directly and data flows unidirectionally.\n\n---\n\n## References\n\n- [Flutter app architecture guide](https://docs.flutter.dev/app-architecture/guide)\n- [Architecture case study (Compass app)](https://docs.flutter.dev/app-architecture/case-study)\n- [Architecture recommendations](https://docs.flutter.dev/app-architecture/recommendations)","author":"@evanca","ownerProfile":null,"authorContacts":null,"sourceUrl":"https://github.com/evanca/flutter-ai-rules/tree/main/skills/architecture-feature-first","license":"MIT","category":"design","lang":"en","tokens":1733,"stars":0,"calls30d":1,"claimed":false,"visibility":"public","origin":"crawler","version":"0.1.0","createdAt":"2026-08-22","updatedAt":"2026-08-22","files":[],"requires":{"mcp":[],"tools":[]},"safety":{"flags":[],"scannedAt":"2026-08-22","hasScripts":false,"networkEndpoints":["docs.flutter.dev"]}}