agent-flutter 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +57 -0
- package/bin/agent-flutter.js +8 -0
- package/package.json +36 -0
- package/src/cli.js +494 -0
- package/templates/shared/.ignore +24 -0
- package/templates/shared/TEMPLATES.md +54 -0
- package/templates/shared/rules/document-workflow-function.md +98 -0
- package/templates/shared/rules/integration-api.md +3 -0
- package/templates/shared/rules/new-template-project.md +148 -0
- package/templates/shared/rules/ui.md +268 -0
- package/templates/shared/rules/unit-test.md +3 -0
- package/templates/shared/rules/widget-test.md +3 -0
- package/templates/shared/skills/dart-best-practices/SKILL.md +20 -0
- package/templates/shared/skills/dart-language-patterns/SKILL.md +47 -0
- package/templates/shared/skills/dart-model-reuse/SKILL.md +38 -0
- package/templates/shared/skills/dart-tooling-ci/SKILL.md +38 -0
- package/templates/shared/skills/flutter-assets-management/SKILL.md +78 -0
- package/templates/shared/skills/flutter-bloc-state-management/SKILL.md +48 -0
- package/templates/shared/skills/flutter-bloc-state-management/references/REFERENCE.md +19 -0
- package/templates/shared/skills/flutter-bloc-state-management/references/auth-bloc-example.md +96 -0
- package/templates/shared/skills/flutter-bloc-state-management/references/property-based-state.md +103 -0
- package/templates/shared/skills/flutter-dependency-injection-injectable/SKILL.md +64 -0
- package/templates/shared/skills/flutter-dependency-injection-injectable/references/REFERENCE.md +15 -0
- package/templates/shared/skills/flutter-dependency-injection-injectable/references/modules.md +42 -0
- package/templates/shared/skills/flutter-error-handling/SKILL.md +25 -0
- package/templates/shared/skills/flutter-error-handling/references/REFERENCE.md +38 -0
- package/templates/shared/skills/flutter-error-handling/references/error-mapping.md +44 -0
- package/templates/shared/skills/flutter-navigation-manager/SKILL.md +81 -0
- package/templates/shared/skills/flutter-navigation-manager/references/REFERENCE.md +71 -0
- package/templates/shared/skills/flutter-navigation-manager/references/router-config.md +57 -0
- package/templates/shared/skills/flutter-standard-lib-src-architecture/SKILL.md +89 -0
- package/templates/shared/skills/flutter-standard-lib-src-architecture/references/REFERENCE.md +19 -0
- package/templates/shared/skills/flutter-standard-lib-src-architecture/references/folder-structure.md +35 -0
- package/templates/shared/skills/flutter-standard-lib-src-architecture/references/modular-injection.md +27 -0
- package/templates/shared/skills/flutter-standard-lib-src-architecture/references/shared-core.md +29 -0
- package/templates/shared/skills/flutter-standard-lib-src-architecture-dependency-rules/SKILL.md +58 -0
- package/templates/shared/skills/flutter-standard-lib-src-architecture-dependency-rules/references/REFERENCE.md +113 -0
- package/templates/shared/skills/flutter-standard-lib-src-architecture-dependency-rules/references/repository-mapping.md +48 -0
- package/templates/shared/skills/flutter-ui-widgets/SKILL.md +152 -0
- package/templates/shared/skills/getx-localization-standard/SKILL.md +101 -0
- package/templates/shared/skills/getx-localization-standard/references/new-page-localization.example.md +61 -0
- package/templates/shared/skills/ui-documentation-workflow/SKILL.md +55 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Flutter Assets Management
|
|
3
|
+
description: Standards for asset naming, organization, and synchronization with design tools.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Assets Management
|
|
7
|
+
|
|
8
|
+
## **Priority: P1 (HIGH)**
|
|
9
|
+
|
|
10
|
+
Strict conventions for asset filenames to ensure traceability between Design (Figma) and Code.
|
|
11
|
+
|
|
12
|
+
## Naming Convention
|
|
13
|
+
|
|
14
|
+
### **CRITICAL RULE: Figma Matching**
|
|
15
|
+
- **Filenames MUST match the layer/export name in Figma.**
|
|
16
|
+
- **Format**: `snake_case` (lowercase with underscores).
|
|
17
|
+
- **Prohibited**: Random strings, UUIDs, or generic names (e.g., `image_1.png`, `552ecefb...png`).
|
|
18
|
+
|
|
19
|
+
### **Temporary Naming for Per-Screen Icons**
|
|
20
|
+
- **Rule**: If a name already exists, append an incrementing numeric suffix: `_2`, `_3`, ...
|
|
21
|
+
- **Examples**:
|
|
22
|
+
- `visit_record_calendar.svg` → `visit_record_calendar_2.svg` (if already present)
|
|
23
|
+
- `customer_detail_back.svg` → `customer_detail_back_2.svg` (if already present)
|
|
24
|
+
- **AppAssets**: Constant should match the filename; e.g. `iconsVisitRecordCalendar2Svg` → `'assets/images/icons/visit_record_calendar_2.svg'`.
|
|
25
|
+
- **Note**: Do not overwrite existing files; always create the next numeric suffix.
|
|
26
|
+
|
|
27
|
+
### **Examples**
|
|
28
|
+
| Bad (Reject) | Good (Accept) |
|
|
29
|
+
| :--- | :--- |
|
|
30
|
+
| `552ecefb-d09c.png` | `user_avatar_placeholder.png` |
|
|
31
|
+
| `Group 123.svg` | `ic_notification_badge.svg` |
|
|
32
|
+
| `IMG_2024.jpg` | `onboarding_background.jpg` |
|
|
33
|
+
|
|
34
|
+
## Organization
|
|
35
|
+
|
|
36
|
+
```text
|
|
37
|
+
assets/
|
|
38
|
+
├── images/ # Illustrations, photos, backgrounds
|
|
39
|
+
├── icons/ # Vector icons (SVG)
|
|
40
|
+
└── fonts/ # Custom font files
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Workflow
|
|
44
|
+
1. **Rename in Figma**: Before exporting, rename the layer in Figma to a valid `snake_case` name.
|
|
45
|
+
2. **Export**: Export the asset with the correct name.
|
|
46
|
+
3. **Import**: Place in the corresponding `assets/` subdirectory.
|
|
47
|
+
4. **Verification**: Do not commit random filenames.
|
|
48
|
+
|
|
49
|
+
## Icon Consistency & Deduplication
|
|
50
|
+
- **Goal**: Avoid cross-screen reuse during the current phase; keep icons per-screen only.
|
|
51
|
+
- **Rule**:
|
|
52
|
+
- **Single Source**: Each icon has one SVG file and one constant in `AppAssets`.
|
|
53
|
+
- **No Duplicates**: Do not add different filenames with identical graphics/content.
|
|
54
|
+
- **Dedup Workflow**:
|
|
55
|
+
1. Normalize SVG: remove metadata, pretty-print, sort attributes, flatten stroke→fill, remove masks/filters if unnecessary.
|
|
56
|
+
2. Compute a content hash (SHA-256) after normalization; compare within `assets/images/icons/`.
|
|
57
|
+
3. If hashes match, consolidate to a single file and update all code references to the shared `AppAssets` constant.
|
|
58
|
+
4. Variants (color/size/outline vs solid) must be clearly named and treated as different when geometry or semantics differ (e.g., `icons_chevron_solid.svg` vs `icons_chevron_outline.svg`).
|
|
59
|
+
- **CI/Pre-commit (Recommended)**:
|
|
60
|
+
- Add a duplicate check step: normalize + hash for `assets/images/icons/`.
|
|
61
|
+
- Fail build on duplicates; print a list of files to consolidate.
|
|
62
|
+
- **Usage Policy**:
|
|
63
|
+
- Always use `SvgPicture.asset(AppAssets.some_icon)`; do not reference raw paths.
|
|
64
|
+
- For recolor, use a fill/tint-friendly version (stroke flattened to fill).
|
|
65
|
+
|
|
66
|
+
## Temporary Policy: Per-Screen Icons Only
|
|
67
|
+
- **Scope**: Current phase.
|
|
68
|
+
- **Rule**: Each screen adds its own icons only; do not reuse icons across other screens.
|
|
69
|
+
- **Naming Suggestion**: Name by screen context (e.g., `customer_detail_back.svg`, `visit_record_calendar.svg`) to avoid confusion.
|
|
70
|
+
- **Note**: When moving to project-wide reuse, we will consolidate using the dedup policy above.
|
|
71
|
+
|
|
72
|
+
## SVG Color Handling
|
|
73
|
+
- **Default**: Do not apply `color`/`colorFilter` if the SVG already has the correct Figma colors.
|
|
74
|
+
- **Allowed**: Only use `color`/`colorFilter` when explicit recolor is required (e.g., active/inactive states) and the icon is prepared with fill (no stroke).
|
|
75
|
+
- **Prohibited**: Avoid applying `colorFilter` broadly on multi-color SVGs; this causes color deviations unless explicitly specified by design.
|
|
76
|
+
## Related Topics
|
|
77
|
+
|
|
78
|
+
idiomatic-flutter | feature-based-clean-architecture
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Flutter BLoC State Management
|
|
3
|
+
description: Standards for predictable state management using flutter_bloc and equatable. Invoke when implementing BLoCs/Cubits, Events, States, or refactoring page widgets into components.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# BLoC State Management
|
|
7
|
+
|
|
8
|
+
## **Priority: P0 (CRITICAL)**
|
|
9
|
+
|
|
10
|
+
Predictable state management separating business logic from UI using `flutter_bloc` and `equatable`.
|
|
11
|
+
|
|
12
|
+
## Structure
|
|
13
|
+
|
|
14
|
+
```text
|
|
15
|
+
presentation/blocs/
|
|
16
|
+
├── auth/
|
|
17
|
+
│ ├── auth_bloc.dart
|
|
18
|
+
│ ├── auth_event.dart # (Equatable)
|
|
19
|
+
│ └── auth_state.dart # (Equatable)
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Implementation Guidelines
|
|
23
|
+
|
|
24
|
+
- **States & Events**: Use `equatable` for value equality (stable rebuilds, reliable tests).
|
|
25
|
+
- **State Strategy**:
|
|
26
|
+
- **Phase-based State**: Use a `Status` enum (idle/loading/success/failure) for exclusive UI phases.
|
|
27
|
+
- **Property-based State**: Use explicit properties (e.g. `isSubmitting`, `errorMessage`, `data`) for forms and complex screens.
|
|
28
|
+
- **Copy/Update**: Provide `copyWith` manually when needed (keep it small and explicit).
|
|
29
|
+
- **Error Handling**: Use `Failure` objects; avoid throwing exceptions.
|
|
30
|
+
- **Async Data**: Use `emit.forEach` or `emit.onEach` for streams.
|
|
31
|
+
- **Concurrency**: Use `transformer` (restartable, droppable) for event debouncing.
|
|
32
|
+
- **Testing**: Use `blocTest` for state transition verification.
|
|
33
|
+
- **Injection**: Register BLoCs as `@injectable` (Factory).
|
|
34
|
+
|
|
35
|
+
## Anti-Patterns
|
|
36
|
+
|
|
37
|
+
- **No Manual Emit**: Do not call `emit()` inside `Future.then`; always use `await` or `emit.forEach`.
|
|
38
|
+
- **No UI Logic**: Do not perform calculations or data formatting inside `BlocBuilder`.
|
|
39
|
+
- **No Cross-Bloc Reference**: Do not pass a BLoC instance into another BLoC; use streams or the UI layer to coordinate.
|
|
40
|
+
|
|
41
|
+
## Reference & Examples
|
|
42
|
+
|
|
43
|
+
For full BLoC/Cubit implementations and concurrency patterns:
|
|
44
|
+
See [references/REFERENCE.md](references/REFERENCE.md).
|
|
45
|
+
|
|
46
|
+
## Related Topics
|
|
47
|
+
|
|
48
|
+
feature-based-clean-architecture | dependency-injection
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# BLoC State Management Reference
|
|
2
|
+
|
|
3
|
+
Detailed patterns for implementing BLoC/Cubit in production.
|
|
4
|
+
|
|
5
|
+
## References
|
|
6
|
+
|
|
7
|
+
- [**Full Auth BLoC (Union State)**](auth-bloc-example.md) - Best for distinct app phases.
|
|
8
|
+
- [**Property-Based State (Forms)**](property-based-state.md) - Best for persistent data and forms.
|
|
9
|
+
- [**Cubit Minimal**](cubit-minimal.md) - Simple state management without events.
|
|
10
|
+
|
|
11
|
+
## **Concurrency Transformer**
|
|
12
|
+
|
|
13
|
+
```dart
|
|
14
|
+
// Restartable prevents multiple simultaneous requests
|
|
15
|
+
on<SearchEvent>(
|
|
16
|
+
_onSearch,
|
|
17
|
+
transformer: restartable(),
|
|
18
|
+
);
|
|
19
|
+
```
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# Auth BLoC Full Implementation
|
|
2
|
+
|
|
3
|
+
## **Events (Equatable)**
|
|
4
|
+
|
|
5
|
+
```dart
|
|
6
|
+
abstract class AuthEvent extends Equatable {
|
|
7
|
+
const AuthEvent();
|
|
8
|
+
@override
|
|
9
|
+
List<Object?> get props => [];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
class AuthStarted extends AuthEvent {
|
|
13
|
+
const AuthStarted();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
class AuthLoginSubmitted extends AuthEvent {
|
|
17
|
+
final String email;
|
|
18
|
+
final String password;
|
|
19
|
+
const AuthLoginSubmitted(this.email, this.password);
|
|
20
|
+
|
|
21
|
+
@override
|
|
22
|
+
List<Object?> get props => [email, password];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
class AuthLogoutPressed extends AuthEvent {
|
|
26
|
+
const AuthLogoutPressed();
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## **States (Equatable)**
|
|
31
|
+
|
|
32
|
+
```dart
|
|
33
|
+
enum AuthStatus { initial, loading, authenticated, unauthenticated, failure }
|
|
34
|
+
|
|
35
|
+
class AuthState extends Equatable {
|
|
36
|
+
final AuthStatus status;
|
|
37
|
+
final User? user;
|
|
38
|
+
final String? message;
|
|
39
|
+
|
|
40
|
+
const AuthState({
|
|
41
|
+
required this.status,
|
|
42
|
+
this.user,
|
|
43
|
+
this.message,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const AuthState.initial() : this(status: AuthStatus.initial);
|
|
47
|
+
|
|
48
|
+
AuthState copyWith({
|
|
49
|
+
AuthStatus? status,
|
|
50
|
+
User? user,
|
|
51
|
+
String? message,
|
|
52
|
+
}) {
|
|
53
|
+
return AuthState(
|
|
54
|
+
status: status ?? this.status,
|
|
55
|
+
user: user ?? this.user,
|
|
56
|
+
message: message ?? this.message,
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
@override
|
|
61
|
+
List<Object?> get props => [status, user, message];
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## **BLoC Implementation**
|
|
66
|
+
|
|
67
|
+
```dart
|
|
68
|
+
class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|
69
|
+
final IAuthRepository _repository;
|
|
70
|
+
|
|
71
|
+
AuthBloc(this._repository) : super(const AuthState.initial()) {
|
|
72
|
+
on<AuthLoginSubmitted>(_onLogin);
|
|
73
|
+
on<AuthLogoutPressed>(_onLogout);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
Future<void> _onLogin(
|
|
77
|
+
AuthLoginSubmitted event,
|
|
78
|
+
Emitter<AuthState> emit,
|
|
79
|
+
) async {
|
|
80
|
+
emit(state.copyWith(status: AuthStatus.loading));
|
|
81
|
+
final result = await _repository.login(event.email, event.password);
|
|
82
|
+
|
|
83
|
+
switch (result) {
|
|
84
|
+
case Success(value: final user):
|
|
85
|
+
emit(state.copyWith(status: AuthStatus.authenticated, user: user));
|
|
86
|
+
case FailureResult(failure: final failure):
|
|
87
|
+
emit(state.copyWith(status: AuthStatus.failure, message: failure.message));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
Future<void> _onLogout(AuthLogoutPressed event, Emitter<AuthState> emit) async {
|
|
92
|
+
await _repository.logout();
|
|
93
|
+
emit(state.copyWith(status: AuthStatus.unauthenticated, user: null));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
```
|
package/templates/shared/skills/flutter-bloc-state-management/references/property-based-state.md
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# Property-Based State Pattern (Forms & Complex Data)
|
|
2
|
+
|
|
3
|
+
This pattern uses a single data class to maintain the entire state of a feature. It is the preferred approach for forms, multi-step wizards, and screens with persistent filtering.
|
|
4
|
+
|
|
5
|
+
## **Pattern Implementation**
|
|
6
|
+
|
|
7
|
+
```dart
|
|
8
|
+
class NewRequestState extends Equatable {
|
|
9
|
+
final List<ReturnMaterial> selectedItems;
|
|
10
|
+
final String returnReference;
|
|
11
|
+
final SalesOrg salesOrg;
|
|
12
|
+
|
|
13
|
+
final bool showErrorMessages;
|
|
14
|
+
final bool isSubmitting;
|
|
15
|
+
|
|
16
|
+
final String? submitSuccessMessage;
|
|
17
|
+
final ApiFailure? submitFailure;
|
|
18
|
+
|
|
19
|
+
const NewRequestState({
|
|
20
|
+
required this.selectedItems,
|
|
21
|
+
required this.returnReference,
|
|
22
|
+
required this.salesOrg,
|
|
23
|
+
required this.showErrorMessages,
|
|
24
|
+
required this.isSubmitting,
|
|
25
|
+
this.submitSuccessMessage,
|
|
26
|
+
this.submitFailure,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
factory NewRequestState.initial() => NewRequestState(
|
|
30
|
+
selectedItems: const [],
|
|
31
|
+
returnReference: '',
|
|
32
|
+
salesOrg: SalesOrg.empty(),
|
|
33
|
+
showErrorMessages: false,
|
|
34
|
+
isSubmitting: false,
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
NewRequestState copyWith({
|
|
38
|
+
List<ReturnMaterial>? selectedItems,
|
|
39
|
+
String? returnReference,
|
|
40
|
+
SalesOrg? salesOrg,
|
|
41
|
+
bool? showErrorMessages,
|
|
42
|
+
bool? isSubmitting,
|
|
43
|
+
String? submitSuccessMessage,
|
|
44
|
+
ApiFailure? submitFailure,
|
|
45
|
+
}) {
|
|
46
|
+
return NewRequestState(
|
|
47
|
+
selectedItems: selectedItems ?? this.selectedItems,
|
|
48
|
+
returnReference: returnReference ?? this.returnReference,
|
|
49
|
+
salesOrg: salesOrg ?? this.salesOrg,
|
|
50
|
+
showErrorMessages: showErrorMessages ?? this.showErrorMessages,
|
|
51
|
+
isSubmitting: isSubmitting ?? this.isSubmitting,
|
|
52
|
+
submitSuccessMessage: submitSuccessMessage,
|
|
53
|
+
submitFailure: submitFailure,
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
@override
|
|
58
|
+
List<Object?> get props => [
|
|
59
|
+
selectedItems,
|
|
60
|
+
returnReference,
|
|
61
|
+
salesOrg,
|
|
62
|
+
showErrorMessages,
|
|
63
|
+
isSubmitting,
|
|
64
|
+
submitSuccessMessage,
|
|
65
|
+
submitFailure,
|
|
66
|
+
];
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## **When to use this vs. Union States?**
|
|
71
|
+
|
|
72
|
+
### **1. Use Property-Based (Flat) State when:**
|
|
73
|
+
|
|
74
|
+
- **Preservation is key**: You are building a **Form** or a **wizard** where users enter data. If you used a Union `Loading` state, the user's current input would be lost unless passed forward manually.
|
|
75
|
+
- **Overlapping UI**: You need to show a loading indicator *on top* of existing data (e.g., a "loading" overlay on a list).
|
|
76
|
+
- **Complex Filtering**: Multiple filters (search, date, category) that all need to persist.
|
|
77
|
+
|
|
78
|
+
### **2. Use Union States (Sealed Classes) when:**
|
|
79
|
+
|
|
80
|
+
- **Exclusive Phases**: The screen looks completely different in each state (e.g., Login Screen -> Loading Spinner -> Dashboard).
|
|
81
|
+
- **Simple Lifecycle**: Fetch data once -> Display it. No complex user input involved.
|
|
82
|
+
- **Type Safety**: The UI *must* have data in the `Success` state and *must not* have it in `Initial`.
|
|
83
|
+
|
|
84
|
+
## **UI Consumption (Success/Failure fields)**
|
|
85
|
+
|
|
86
|
+
```dart
|
|
87
|
+
BlocListener<NewRequestBloc, NewRequestState>(
|
|
88
|
+
listenWhen: (p, c) =>
|
|
89
|
+
p.submitSuccessMessage != c.submitSuccessMessage ||
|
|
90
|
+
p.submitFailure != c.submitFailure,
|
|
91
|
+
listener: (context, state) {
|
|
92
|
+
if (state.submitFailure != null) {
|
|
93
|
+
showErrorSnackbar(state.submitFailure!.message);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (state.submitSuccessMessage != null) {
|
|
97
|
+
navigateToSummary();
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
child: ...,
|
|
102
|
+
)
|
|
103
|
+
```
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Flutter Dependency Injection (GetX)
|
|
3
|
+
description: Standards for dependency injection using GetX Bindings and Service Locator.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Dependency Injection (GetX)
|
|
7
|
+
|
|
8
|
+
## **Priority: P1 (HIGH)**
|
|
9
|
+
|
|
10
|
+
This project uses **GetX** for dependency injection.
|
|
11
|
+
Dependencies are managed via `Bindings` and the global `Get` service locator.
|
|
12
|
+
|
|
13
|
+
## Structure
|
|
14
|
+
|
|
15
|
+
```text
|
|
16
|
+
lib/src/
|
|
17
|
+
├── di/
|
|
18
|
+
│ ├── di_graph_setup.dart # Global/Core dependencies (Singletons)
|
|
19
|
+
│ ├── register_core_module.dart
|
|
20
|
+
│ └── register_manager_module.dart
|
|
21
|
+
└── ui/
|
|
22
|
+
└── <feature>/
|
|
23
|
+
└── binding/
|
|
24
|
+
└── <feature>_binding.dart # Feature-specific dependencies
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Implementation Guidelines
|
|
28
|
+
|
|
29
|
+
### 1. Global Dependencies (Singletons)
|
|
30
|
+
Register core services (API clients, storage, managers) in `lib/src/di/di_graph_setup.dart`.
|
|
31
|
+
|
|
32
|
+
```dart
|
|
33
|
+
// register_core_module.dart
|
|
34
|
+
Future<void> _registerCoreModule() async {
|
|
35
|
+
Get.put(SharedPreferenceHelper(), permanent: true);
|
|
36
|
+
Get.lazyPut(() => AuthRepository(Get.find(), Get.find()), fenix: true);
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### 2. Feature Dependencies (Bindings)
|
|
41
|
+
Use `Bindings` to lazy-load controllers/BLoCs when a page is entered.
|
|
42
|
+
|
|
43
|
+
```dart
|
|
44
|
+
// lib/src/ui/auth/login/binding/login_binding.dart
|
|
45
|
+
class LoginBinding extends Bindings {
|
|
46
|
+
@override
|
|
47
|
+
void dependencies() {
|
|
48
|
+
// Register BLoC/Controller
|
|
49
|
+
Get.lazyPut<LoginBloc>(() => LoginBloc());
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### 3. Injection Types
|
|
55
|
+
- **`Get.put()`**: Immediate initialization. Use for critical core services.
|
|
56
|
+
- **`Get.lazyPut()`**: Lazy initialization. Created only when used. Best for Repositories/BLoCs.
|
|
57
|
+
- **`Get.find<T>()`**: Inject a dependency.
|
|
58
|
+
|
|
59
|
+
### 4. Scoping
|
|
60
|
+
- Bindings attached to `GetPage` in `AppPages` are automatically disposed when the route is removed from the stack (unless `fenix: true` is used).
|
|
61
|
+
|
|
62
|
+
## Reference & Examples
|
|
63
|
+
|
|
64
|
+
See [references/modules.md](references/modules.md).
|
package/templates/shared/skills/flutter-dependency-injection-injectable/references/REFERENCE.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Dependency Injection Reference
|
|
2
|
+
|
|
3
|
+
Implementation patterns for `injectable` and `get_it` in massive Flutter projects.
|
|
4
|
+
|
|
5
|
+
## References
|
|
6
|
+
|
|
7
|
+
- [**Injection Modules**](modules.md) - Registering third-party libraries (Dio, Hive).
|
|
8
|
+
- [**Production Initialization**](initialization.md) - Wiring everything in `main.dart`.
|
|
9
|
+
- [**Testing Mocks**](testing-mocks.md) - How to swap services during unit tests.
|
|
10
|
+
|
|
11
|
+
## **Quick Registration Guide**
|
|
12
|
+
|
|
13
|
+
- **@injectable**: Use for BLoCs (New instance every time).
|
|
14
|
+
- **@LazySingleton**: Use for Repositories and DataSources (Global, lazy-init).
|
|
15
|
+
- **@singleton**: Use only for truly shared resources (init on startup).
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# DI Modules (GetX)
|
|
2
|
+
|
|
3
|
+
## Global Setup
|
|
4
|
+
`lib/src/di/di_graph_setup.dart`
|
|
5
|
+
|
|
6
|
+
```dart
|
|
7
|
+
Future<void> setupDependenciesGraph() async {
|
|
8
|
+
await _initializeEnvironment();
|
|
9
|
+
await _registerCoreModule(); // Repositories, APIs
|
|
10
|
+
_registerManagersModule(); // Managers
|
|
11
|
+
}
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Feature Binding Example
|
|
15
|
+
`lib/src/ui/auth/login/binding/login_binding.dart`
|
|
16
|
+
|
|
17
|
+
```dart
|
|
18
|
+
import 'package:get/get.dart';
|
|
19
|
+
import 'package:link_home/src/ui/auth/login/bloc/login_bloc.dart';
|
|
20
|
+
|
|
21
|
+
class LoginBinding extends Bindings {
|
|
22
|
+
@override
|
|
23
|
+
void dependencies() {
|
|
24
|
+
// Lazy load the Bloc/Controller for this feature
|
|
25
|
+
Get.lazyPut<LoginBloc>(() => LoginBloc());
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Repository Registration
|
|
31
|
+
Repositories are typically registered globally or in a core module.
|
|
32
|
+
|
|
33
|
+
```dart
|
|
34
|
+
// register_core_module.dart
|
|
35
|
+
Get.lazyPut<AuthRepository>(
|
|
36
|
+
() => AuthRepository(
|
|
37
|
+
Get.find<AppShared>(),
|
|
38
|
+
Get.find<IAuthApiUrl>(),
|
|
39
|
+
),
|
|
40
|
+
fenix: true, // Recreate if disposed but needed again
|
|
41
|
+
);
|
|
42
|
+
```
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Error Handling
|
|
2
|
+
|
|
3
|
+
## **Priority: P1 (HIGH)**
|
|
4
|
+
|
|
5
|
+
Standardized typed error handling using `Result<T>` and `Failure` models. The project uses **Dio** for networking, not `http`.
|
|
6
|
+
|
|
7
|
+
## Implementation Guidelines
|
|
8
|
+
|
|
9
|
+
- **Networking**: Use `Dio` (configured in `lib/src/api/api.dart`).
|
|
10
|
+
- **Result Pattern**: Return `Result<T>` from repositories. No exceptions in UI/BLoC.
|
|
11
|
+
- **Failures**: Define domain-specific failures as plain immutable classes.
|
|
12
|
+
- **Error Mapping**:
|
|
13
|
+
- `Api` class (in `lib/src/api/api.dart`) handles low-level `DioException`.
|
|
14
|
+
- It extracts error messages from server response (e.g., `response.data['errors']` or `response.data['message']`).
|
|
15
|
+
- It handles UI feedback (e.g., `showErrorToast`) automatically for API errors.
|
|
16
|
+
- Repositories should catch exceptions rethrown by `Api` and map them to `Failure`.
|
|
17
|
+
|
|
18
|
+
## Reference & Examples
|
|
19
|
+
|
|
20
|
+
For Failure definitions and API error mapping:
|
|
21
|
+
See [references/REFERENCE.md](references/REFERENCE.md).
|
|
22
|
+
|
|
23
|
+
## Related Topics
|
|
24
|
+
|
|
25
|
+
layer-based-clean-architecture | bloc-state-management
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Error Handling Reference
|
|
2
|
+
|
|
3
|
+
## **Dio Error Mapping**
|
|
4
|
+
|
|
5
|
+
Since the project uses `Dio` and a centralized `Api` wrapper (`lib/src/api/api.dart`), errors are processed as follows:
|
|
6
|
+
|
|
7
|
+
1. **Low-Level Catching (`Api.request`)**:
|
|
8
|
+
- Catches `DioException`.
|
|
9
|
+
- Parses `response.data['errors']` (Map) or `response.data['message']` (String).
|
|
10
|
+
- Shows Toast automatically via `showErrorToast`.
|
|
11
|
+
- Rethrows the exception for the Repository to handle logic flow.
|
|
12
|
+
|
|
13
|
+
2. **Repository Layer Mapping**:
|
|
14
|
+
Repositories should wrap API calls in try-catch blocks to return `Result<T>` (Success/Failure).
|
|
15
|
+
|
|
16
|
+
```dart
|
|
17
|
+
// Example Repository
|
|
18
|
+
Future<Result<LoginResponse>> login(LoginRequest request) async {
|
|
19
|
+
try {
|
|
20
|
+
final response = await Api.request(
|
|
21
|
+
url: ApiUrl.login,
|
|
22
|
+
method: 'POST',
|
|
23
|
+
body: request.toJson(),
|
|
24
|
+
);
|
|
25
|
+
return Result.success(LoginResponse.fromJson(response));
|
|
26
|
+
} catch (e) {
|
|
27
|
+
// Dio errors are already toasted by Api class,
|
|
28
|
+
// but we return Failure so BLoC knows to stop loading.
|
|
29
|
+
return Result.failure(ServerFailure(e.toString()));
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## **Failure Types**
|
|
35
|
+
|
|
36
|
+
- **ServerFailure**: API returned 4xx/5xx or connection failed.
|
|
37
|
+
- **CacheFailure**: Local storage error.
|
|
38
|
+
- **ValidationFailure**: Invalid input (caught before API call).
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Functional Failure Patterns
|
|
2
|
+
|
|
3
|
+
## **Global Failures (No Code-gen)**
|
|
4
|
+
|
|
5
|
+
```dart
|
|
6
|
+
abstract class ApiFailure implements Exception {
|
|
7
|
+
const ApiFailure();
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
class ServerFailure extends ApiFailure {
|
|
11
|
+
const ServerFailure();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
class NetworkFailure extends ApiFailure {
|
|
15
|
+
const NetworkFailure();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
class UnauthenticatedFailure extends ApiFailure {
|
|
19
|
+
const UnauthenticatedFailure();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
class BadRequestFailure extends ApiFailure {
|
|
23
|
+
final String message;
|
|
24
|
+
const BadRequestFailure(this.message);
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## **Infrastructure Mapper**
|
|
29
|
+
|
|
30
|
+
```dart
|
|
31
|
+
extension DioErrorX on DioException {
|
|
32
|
+
ApiFailure toFailure() {
|
|
33
|
+
switch (type) {
|
|
34
|
+
case DioExceptionType.connectionTimeout:
|
|
35
|
+
return const NetworkFailure();
|
|
36
|
+
case DioExceptionType.badResponse:
|
|
37
|
+
if (response?.statusCode == 401) return const UnauthenticatedFailure();
|
|
38
|
+
return const ServerFailure();
|
|
39
|
+
default:
|
|
40
|
+
return const ServerFailure();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
```
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Flutter Navigation Manager
|
|
3
|
+
description: Routing strategy management (GetX is the Project Standard).
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Flutter Navigation Strategy
|
|
7
|
+
|
|
8
|
+
## **Active Strategy: GetX**
|
|
9
|
+
|
|
10
|
+
This project currently uses **GetX** for all navigation requirements.
|
|
11
|
+
|
|
12
|
+
**Do NOT use:**
|
|
13
|
+
- AutoRoute
|
|
14
|
+
- GoRouter
|
|
15
|
+
- Navigator 2.0 (Raw)
|
|
16
|
+
|
|
17
|
+
## Guidelines
|
|
18
|
+
|
|
19
|
+
### GetX Routing
|
|
20
|
+
- **Configuration**: `lib/src/utils/app_pages.dart`
|
|
21
|
+
- **Navigation**: `Get.toNamed()`, `Get.offNamed()`
|
|
22
|
+
- **Bindings**: Attach dependencies via `binding` property in `GetPage`.
|
|
23
|
+
|
|
24
|
+
### Prototype → Route Mapping Rules
|
|
25
|
+
- **Full Screen (Hides BottomBar)**: Register the route in `AppPages` and use `Get.toNamed(AppPages.some)`. Back: `Navigator.pop(context)`.
|
|
26
|
+
- **Nested Under BottomBar (Keeps Visible)**:
|
|
27
|
+
- Register routes in `lib/src/ui/routing/<tab>_router.dart` (or `common_router.dart` if shared).
|
|
28
|
+
- Use `Navigator.of(context).push(...)` to push onto the tab’s internal stack, avoiding root replacement.
|
|
29
|
+
- Back: `Navigator.pop(context)` (do not use `Get.back()` to avoid popping the wrong stack).
|
|
30
|
+
- **Shared Page (Keeps BottomBar)**: Register in `common_router.dart` and use `Navigator.of(context)` within the current tab context.
|
|
31
|
+
|
|
32
|
+
### Decision Heuristics (Prototype Mapping)
|
|
33
|
+
- Destination keeps BottomBar visible → Nested route.
|
|
34
|
+
- Destination covers entire screen (modal/full-screen) → Full screen GetX route.
|
|
35
|
+
- Detail flow originating from the current tab (e.g., Customer → Customer Detail) → Nested.
|
|
36
|
+
- Flow leaving tab context (e.g., Login, standalone Privacy Policy) → Full screen.
|
|
37
|
+
|
|
38
|
+
### Arguments (GetX vs Navigator)
|
|
39
|
+
- **GetX (Full Screen)**:
|
|
40
|
+
- Send: `Get.toNamed(AppPages.detail, arguments: {'id': 123, 'mode': 'edit'})`
|
|
41
|
+
- Receive:
|
|
42
|
+
```dart
|
|
43
|
+
final args = Get.arguments as Map<String, dynamic>;
|
|
44
|
+
final id = args['id'] as int;
|
|
45
|
+
final mode = args['mode'] as String?;
|
|
46
|
+
```
|
|
47
|
+
- **Navigator (Nested)**:
|
|
48
|
+
- Prefer constructor parameters for strong typing:
|
|
49
|
+
```dart
|
|
50
|
+
Navigator.of(context).push(MaterialPageRoute(
|
|
51
|
+
builder: (_) => DetailPage(id: 123, mode: 'edit'),
|
|
52
|
+
));
|
|
53
|
+
```
|
|
54
|
+
- If using `settings.arguments`:
|
|
55
|
+
```dart
|
|
56
|
+
Navigator.of(context).push(MaterialPageRoute(
|
|
57
|
+
settings: const RouteSettings(arguments: {'id': 123}),
|
|
58
|
+
builder: (_) => const DetailPage(),
|
|
59
|
+
));
|
|
60
|
+
// In DetailPage (build):
|
|
61
|
+
final args = ModalRoute.of(context)?.settings.arguments as Map?;
|
|
62
|
+
```
|
|
63
|
+
- Rule: Constructor > settings.arguments > Get.arguments in nested flows to avoid stack/context confusion.
|
|
64
|
+
|
|
65
|
+
### Examples
|
|
66
|
+
```dart
|
|
67
|
+
// Full screen: hides BottomBar
|
|
68
|
+
Get.toNamed(AppPages.visitRecord);
|
|
69
|
+
// Back
|
|
70
|
+
Navigator.pop(context);
|
|
71
|
+
|
|
72
|
+
// Nested: keeps BottomBar
|
|
73
|
+
Navigator.of(context).push(
|
|
74
|
+
MaterialPageRoute(builder: (_) => const CustomerDetailPage()),
|
|
75
|
+
);
|
|
76
|
+
// Back
|
|
77
|
+
Navigator.pop(context);
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Related Topics
|
|
81
|
+
getx-state-management | dependency-injection
|