@rashidee/co2 1.2.5
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/dist/index.js +25449 -0
- package/drizzle/0000_remarkable_charles_xavier.sql +210 -0
- package/drizzle/0001_wise_wolf_cub.sql +13 -0
- package/drizzle/0002_secret_spyke.sql +16 -0
- package/drizzle/0003_workspace_config_singleton.sql +16 -0
- package/drizzle/0004_terminal_claim_v120.sql +11 -0
- package/drizzle/0005_brainstorm_v124.sql +10 -0
- package/drizzle/0006_project_v140.sql +6 -0
- package/drizzle/0007_prd_wizard_v124.sql +14 -0
- package/drizzle/0008_workflow_artifact_v126.sql +24 -0
- package/drizzle/0009_project_import_v150.sql +7 -0
- package/drizzle/0010_quality_v151.sql +3 -0
- package/drizzle/meta/0000_snapshot.json +1335 -0
- package/drizzle/meta/0001_snapshot.json +1416 -0
- package/drizzle/meta/0002_snapshot.json +1510 -0
- package/drizzle/meta/_journal.json +83 -0
- package/package.json +41 -0
- package/plugin/.claude-plugin/marketplace.json +36 -0
- package/plugin/.claude-plugin/plugin.json +22 -0
- package/plugin/README.md +625 -0
- package/plugin/SKILLS.md +350 -0
- package/plugin/co2.svg +5 -0
- package/plugin/commands/brainstorm-loop.md +129 -0
- package/plugin/skills/conductor-defect/SKILL.md +898 -0
- package/plugin/skills/conductor-feature-develop/SKILL.md +1383 -0
- package/plugin/skills/conductor-feature-develop/references/implementation-domain-template.md +131 -0
- package/plugin/skills/conductor-feature-develop/references/implementation-master-template.md +89 -0
- package/plugin/skills/conductor-feature-develop/references/playwright-setup.md +224 -0
- package/plugin/skills/conductor-feature-prepare/SKILL.md +604 -0
- package/plugin/skills/depgen-k8s/SKILL.md +468 -0
- package/plugin/skills/depgen-k8s/references/dockerfile-laravel.md +197 -0
- package/plugin/skills/depgen-k8s/references/dockerfile-nodejs.md +163 -0
- package/plugin/skills/depgen-k8s/references/dockerfile-spring-boot.md +174 -0
- package/plugin/skills/depgen-k8s/references/k8s-patterns.md +869 -0
- package/plugin/skills/mockgen-shadcn/SKILL.md +1067 -0
- package/plugin/skills/mockgen-shadcn/references/admin-layout-template.md +1467 -0
- package/plugin/skills/mockgen-shadcn/references/mockup-hub-template.md +498 -0
- package/plugin/skills/mockgen-shadcn/references/mockup-index-template.md +216 -0
- package/plugin/skills/mockgen-tailwind/SKILL.md +904 -0
- package/plugin/skills/mockgen-tailwind/references/admin-layout-template.md +720 -0
- package/plugin/skills/mockgen-tailwind/references/mockup-hub-template.md +498 -0
- package/plugin/skills/mockgen-tailwind/references/mockup-index-template.md +190 -0
- package/plugin/skills/modelgen-nosql/SKILL.md +617 -0
- package/plugin/skills/modelgen-nosql/references/model-extraction-methodology.md +497 -0
- package/plugin/skills/modelgen-nosql/references/nosql-design-guide.md +258 -0
- package/plugin/skills/modelgen-relational/SKILL.md +707 -0
- package/plugin/skills/modelgen-relational/references/model-extraction-methodology.md +499 -0
- package/plugin/skills/specgen-flutter-riverpod/SKILL.md +898 -0
- package/plugin/skills/specgen-flutter-riverpod/references/component-patterns.md +971 -0
- package/plugin/skills/specgen-flutter-riverpod/references/notification-patterns.md +457 -0
- package/plugin/skills/specgen-flutter-riverpod/references/routing-patterns.md +362 -0
- package/plugin/skills/specgen-flutter-riverpod/references/security-patterns.md +709 -0
- package/plugin/skills/specgen-flutter-riverpod/references/spec-template.md +1566 -0
- package/plugin/skills/specgen-flutter-riverpod/references/state-patterns.md +518 -0
- package/plugin/skills/specgen-flutter-riverpod/references/storage-patterns.md +370 -0
- package/plugin/skills/specgen-laravel-eloquent-bladehtmx/SKILL.md +984 -0
- package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/batch-patterns.md +359 -0
- package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/messaging-patterns.md +468 -0
- package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/modulith-patterns.md +461 -0
- package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/reporting-patterns.md +803 -0
- package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/security-patterns.md +501 -0
- package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/spec-template.md +1268 -0
- package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/web-patterns.md +301 -0
- package/plugin/skills/specgen-node-cli-web/SKILL.md +561 -0
- package/plugin/skills/specgen-node-cli-web/references/auth-patterns.md +293 -0
- package/plugin/skills/specgen-node-cli-web/references/cli-packaging-patterns.md +250 -0
- package/plugin/skills/specgen-node-cli-web/references/server-patterns.md +247 -0
- package/plugin/skills/specgen-node-cli-web/references/spec-template.md +401 -0
- package/plugin/skills/specgen-node-cli-web/references/testing-patterns.md +174 -0
- package/plugin/skills/specgen-react-mui/SKILL.md +789 -0
- package/plugin/skills/specgen-react-mui/references/component-patterns.md +1077 -0
- package/plugin/skills/specgen-react-mui/references/reporting-patterns.md +1998 -0
- package/plugin/skills/specgen-react-mui/references/routing-patterns.md +461 -0
- package/plugin/skills/specgen-react-mui/references/security-patterns.md +599 -0
- package/plugin/skills/specgen-react-mui/references/spec-template.md +1030 -0
- package/plugin/skills/specgen-react-mui/references/state-patterns.md +327 -0
- package/plugin/skills/specgen-react-tailwind/SKILL.md +859 -0
- package/plugin/skills/specgen-react-tailwind/references/component-patterns.md +1212 -0
- package/plugin/skills/specgen-react-tailwind/references/reporting-patterns.md +1529 -0
- package/plugin/skills/specgen-react-tailwind/references/routing-patterns.md +669 -0
- package/plugin/skills/specgen-react-tailwind/references/security-patterns.md +656 -0
- package/plugin/skills/specgen-react-tailwind/references/spec-template.md +1237 -0
- package/plugin/skills/specgen-react-tailwind/references/state-patterns.md +327 -0
- package/plugin/skills/specgen-sdk-java/SKILL.md +933 -0
- package/plugin/skills/specgen-sdk-java/references/api-extraction-patterns.md +247 -0
- package/plugin/skills/specgen-sdk-java/references/http-client-patterns.md +495 -0
- package/plugin/skills/specgen-sdk-java/references/packaging-patterns.md +366 -0
- package/plugin/skills/specgen-sdk-java/references/spec-template.md +956 -0
- package/plugin/skills/specgen-spring-jpa-jtehtmx/SKILL.md +1284 -0
- package/plugin/skills/specgen-spring-jpa-jtehtmx/references/batch-patterns.md +1224 -0
- package/plugin/skills/specgen-spring-jpa-jtehtmx/references/jasper-patterns.md +1638 -0
- package/plugin/skills/specgen-spring-jpa-jtehtmx/references/messaging-patterns.md +512 -0
- package/plugin/skills/specgen-spring-jpa-jtehtmx/references/modulith-patterns.md +354 -0
- package/plugin/skills/specgen-spring-jpa-jtehtmx/references/security-patterns.md +551 -0
- package/plugin/skills/specgen-spring-jpa-jtehtmx/references/spec-template.md +2502 -0
- package/plugin/skills/specgen-spring-jpa-jtehtmx/references/web-patterns.md +503 -0
- package/plugin/skills/specgen-spring-jpa-restapi/SKILL.md +990 -0
- package/plugin/skills/specgen-spring-jpa-restapi/references/batch-patterns.md +1224 -0
- package/plugin/skills/specgen-spring-jpa-restapi/references/messaging-patterns.md +512 -0
- package/plugin/skills/specgen-spring-jpa-restapi/references/modulith-patterns.md +279 -0
- package/plugin/skills/specgen-spring-jpa-restapi/references/reporting-patterns.md +1536 -0
- package/plugin/skills/specgen-spring-jpa-restapi/references/restapi-patterns.md +722 -0
- package/plugin/skills/specgen-spring-jpa-restapi/references/security-patterns.md +351 -0
- package/plugin/skills/specgen-spring-jpa-restapi/references/spec-template.md +553 -0
- package/plugin/skills/specgen-ts-cli/SKILL.md +861 -0
- package/plugin/skills/specgen-ts-cli/references/async-patterns.md +1101 -0
- package/plugin/skills/specgen-ts-cli/references/command-patterns.md +383 -0
- package/plugin/skills/specgen-ts-cli/references/config-patterns.md +430 -0
- package/plugin/skills/specgen-ts-cli/references/database-patterns.md +750 -0
- package/plugin/skills/specgen-ts-cli/references/packaging-patterns.md +426 -0
- package/plugin/skills/specgen-ts-cli/references/spec-template.md +1391 -0
- package/plugin/skills/specgen-ts-cli/references/testing-patterns.md +909 -0
- package/plugin/skills/testgen-functional/SKILL.md +1381 -0
- package/plugin/skills/tracegen-matrix/SKILL.md +243 -0
- package/plugin/skills/util-preparek8senv/SKILL.md +422 -0
- package/plugin/skills/util-projectinit/SKILL.md +307 -0
- package/plugin/skills/util-projectinit/reference/brainstorm-loop.md +129 -0
- package/plugin/skills/util-projectinit/reference/brainstorm-protocol.md +144 -0
- package/plugin/skills/util-projectinit/reference/brainstorm-setup-guide.md +153 -0
- package/plugin/skills/util-projectinit/reference/brainstorm-template-appdev.md +87 -0
- package/plugin/skills/util-projectinit/reference/co2-context-generation-guide.md +761 -0
- package/plugin/skills/util-projectsync/SKILL.md +501 -0
- package/plugin/skills/util-updprd/SKILL.md +324 -0
- package/plugin/skills/util-usanalyzer/SKILL.md +307 -0
- package/plugin/skills/util-ustagger/SKILL.md +154 -0
- package/static/assets/abnfDiagram-VRR7QNED-DvPuwyCV.js +1 -0
- package/static/assets/arc-DRKw7sjs.js +1 -0
- package/static/assets/architectureDiagram-ZJ3FMSHR-CaNcxUbC.js +36 -0
- package/static/assets/blockDiagram-677ZJIJ3-Demd4AOK.js +132 -0
- package/static/assets/c4Diagram-LMCZKHZV-PVKUfc2x.js +10 -0
- package/static/assets/channel-C2nVbuwv.js +1 -0
- package/static/assets/chunk-2Q5K7J3B-CExf8ww_.js +1 -0
- package/static/assets/chunk-32BRIVSS-CFPaoONF.js +1 -0
- package/static/assets/chunk-5VM5RSS4-DE2esM67.js +15 -0
- package/static/assets/chunk-EX3LRPZG-SON_YdDG.js +231 -0
- package/static/assets/chunk-JWPE2WC7-Qxah8LNr.js +1 -0
- package/static/assets/chunk-MOJQB5TN-BcWGQV3j.js +88 -0
- package/static/assets/chunk-RYQCIY6F-DkDj-OR5.js +1 -0
- package/static/assets/chunk-V7JOEXUC-BlI1Mrah.js +206 -0
- package/static/assets/chunk-VR4S4FIN-Agf__duj.js +1 -0
- package/static/assets/chunk-XXDRQBXY-B5CfAh85.js +1 -0
- package/static/assets/classDiagram-OUVF2IWQ-DXJIljsR.js +1 -0
- package/static/assets/classDiagram-v2-EOCWNBFH-DXJIljsR.js +1 -0
- package/static/assets/cose-bilkent-JH36ORCC-DO0cQkcy.js +1 -0
- package/static/assets/cynefin-VYW2F7L2-CfOnsnXc.js +178 -0
- package/static/assets/cynefinDiagram-TSTJHNR4-BJ8tULAv.js +62 -0
- package/static/assets/cytoscape.esm-DTSO7Bv0.js +331 -0
- package/static/assets/dagre-VKFMJZFB-DfjwzHqP.js +4 -0
- package/static/assets/defaultLocale-DX6XiGOO.js +1 -0
- package/static/assets/diagram-FQU43EPY-CndSOIIM.js +3 -0
- package/static/assets/diagram-G47NLZAW-Czf5hN0Z.js +24 -0
- package/static/assets/diagram-NH7WQ7WH-COAyojGh.js +24 -0
- package/static/assets/diagram-OA4YK3LP-H8PoiN2m.js +30 -0
- package/static/assets/diagram-WEI45ONY-DzcaVzjp.js +41 -0
- package/static/assets/ebnfDiagram-CCIWWBDH-DPazGYLJ.js +1 -0
- package/static/assets/erDiagram-Q63AITRT-Bo-fubgF.js +85 -0
- package/static/assets/flowDiagram-23GEKE2U-CSvxUDwh.js +156 -0
- package/static/assets/ganttDiagram-NO4QXBWP-DnBveWX6.js +292 -0
- package/static/assets/gitGraphDiagram-IHSO6WYX-B3rVL8c-.js +106 -0
- package/static/assets/graph-C9eacEi8.js +1 -0
- package/static/assets/index-LZSQkLE2.css +41 -0
- package/static/assets/index-y7XDSgMG.js +495 -0
- package/static/assets/infoDiagram-FWYZ7A6U-CvWV9yy2.js +2 -0
- package/static/assets/init-Gi6I4Gst.js +1 -0
- package/static/assets/ishikawaDiagram-FXEZZL3T-DF28a1N6.js +70 -0
- package/static/assets/journeyDiagram-5HDEW3XC-CB6CugYk.js +139 -0
- package/static/assets/kanban-definition-HUTT4EX6-ResSLF6d.js +89 -0
- package/static/assets/katex-C5jXJg4s.js +257 -0
- package/static/assets/layout-DEXfKzaS.js +1 -0
- package/static/assets/linear-CLO--ryk.js +1 -0
- package/static/assets/map-Czzmt4hB.js +1 -0
- package/static/assets/mindmap-definition-LN4V7U3C-BvW3EbxX.js +96 -0
- package/static/assets/ordinal-Cboi1Yqb.js +1 -0
- package/static/assets/pegDiagram-2B236MQR-BHCgqrrM.js +1 -0
- package/static/assets/pieDiagram-ENE6RG2P-DVmCQ5FA.js +39 -0
- package/static/assets/quadrantDiagram-ABIIQ3AL-B3e5KR52.js +7 -0
- package/static/assets/railroadDiagram-RFXS5EU6-Csv5ysLc.js +1 -0
- package/static/assets/requirementDiagram-TGXJPOKE-DOMVzvnB.js +84 -0
- package/static/assets/sankeyDiagram-HTMAVEWB-B1dcTOvy.js +40 -0
- package/static/assets/sequenceDiagram-DBY2YBRQ-BY414yeO.js +162 -0
- package/static/assets/sizeCapture-X5ZJPWSS-CuJfrfBN.js +1 -0
- package/static/assets/stateDiagram-2N3HPSRC-FX4rJkzV.js +1 -0
- package/static/assets/stateDiagram-v2-6OUMAXLB-BVufbO_H.js +1 -0
- package/static/assets/swimlanes-5IMT3BWC-Sfy58mmv.js +2 -0
- package/static/assets/swimlanesDiagram-G3AALYLV-wDCDw08H.js +8 -0
- package/static/assets/timeline-definition-FHXFAJF6-CmV7by8p.js +120 -0
- package/static/assets/vennDiagram-L72KCM5P-FqIyFZFt.js +34 -0
- package/static/assets/wardleyDiagram-EHGQE667-BZ06O45i.js +78 -0
- package/static/assets/xychartDiagram-FW5EYKEG-BOkffR06.js +7 -0
- package/static/index.html +24 -0
|
@@ -0,0 +1,518 @@
|
|
|
1
|
+
# State Management Patterns — Riverpod 2 with `riverpod_generator`
|
|
2
|
+
|
|
3
|
+
This reference describes the canonical Riverpod patterns for the spec. Include this
|
|
4
|
+
content in Section 10 of the generated specification and in any module SPEC.md that
|
|
5
|
+
defines providers.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## State Separation Principle
|
|
10
|
+
|
|
11
|
+
The golden rule for Flutter app state management:
|
|
12
|
+
|
|
13
|
+
| State Type | Tool | Examples |
|
|
14
|
+
|---|---|---|
|
|
15
|
+
| **Server state** (data from API) | `AsyncNotifier` / `FutureProvider` | Task list, profile, catalogue |
|
|
16
|
+
| **Cached server state** (offline) | Hive box + Riverpod provider | Last-seen tasks, profile snapshot |
|
|
17
|
+
| **UI state** (per-screen, transient) | `Notifier` / `useState` (flutter_hooks) | Selected filter, active tab |
|
|
18
|
+
| **Global UI state** | `Notifier` persisted to Hive | Theme mode, locale, sidebar |
|
|
19
|
+
| **Auth state** | `AsyncNotifier`; tokens in `flutter_secure_storage` | JWT, user info, roles |
|
|
20
|
+
| **Form state** | `Form` + controllers (+ optional `Notifier` for wizards) | Field values, validation errors |
|
|
21
|
+
|
|
22
|
+
**Do NOT cache API responses in plain `Notifier`/`StateProvider`.** Use `AsyncNotifier`
|
|
23
|
+
so that loading/error/data states are first-class. For offline persistence, write the
|
|
24
|
+
fetched data to Hive *inside* the provider's `build()` after a successful fetch, and
|
|
25
|
+
seed `build()` with the cached value when offline.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Generated Providers — `build.yaml`
|
|
30
|
+
|
|
31
|
+
```yaml
|
|
32
|
+
# build.yaml at the project root
|
|
33
|
+
targets:
|
|
34
|
+
$default:
|
|
35
|
+
builders:
|
|
36
|
+
riverpod_generator:
|
|
37
|
+
options:
|
|
38
|
+
# Provide a project-wide naming convention if desired
|
|
39
|
+
provider_family_name: family
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Every provider file imports the generator part:
|
|
43
|
+
|
|
44
|
+
```dart
|
|
45
|
+
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
|
46
|
+
|
|
47
|
+
part 'task_providers.g.dart';
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Run codegen with:
|
|
51
|
+
```bash
|
|
52
|
+
dart run build_runner watch --delete-conflicting-outputs
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Repository Provider Pattern
|
|
58
|
+
|
|
59
|
+
```dart
|
|
60
|
+
// lib/features/task/data/task_repository.dart
|
|
61
|
+
import 'package:dio/dio.dart';
|
|
62
|
+
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
|
63
|
+
|
|
64
|
+
import '../../../core/api/api_failure.dart';
|
|
65
|
+
import '../../../core/api/dio_provider.dart';
|
|
66
|
+
import '../../../core/api/paginated_response.dart';
|
|
67
|
+
import '../domain/task.dart';
|
|
68
|
+
|
|
69
|
+
part 'task_repository.g.dart';
|
|
70
|
+
|
|
71
|
+
class TaskRepository {
|
|
72
|
+
TaskRepository(this._dio);
|
|
73
|
+
final Dio _dio;
|
|
74
|
+
|
|
75
|
+
Future<PaginatedResponse<Task>> list(TaskListParams params) async {
|
|
76
|
+
try {
|
|
77
|
+
final response = await _dio.get<Map<String, dynamic>>(
|
|
78
|
+
'/tasks',
|
|
79
|
+
queryParameters: params.toJson(),
|
|
80
|
+
);
|
|
81
|
+
return PaginatedResponse.fromJson(
|
|
82
|
+
response.data!,
|
|
83
|
+
(item) => Task.fromJson(item as Map<String, dynamic>),
|
|
84
|
+
);
|
|
85
|
+
} on DioException catch (e) {
|
|
86
|
+
throw ApiFailure.fromDioException(e);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
Future<Task> getById(String id) async {
|
|
91
|
+
try {
|
|
92
|
+
final response = await _dio.get<Map<String, dynamic>>('/tasks/$id');
|
|
93
|
+
return Task.fromJson(response.data!);
|
|
94
|
+
} on DioException catch (e) {
|
|
95
|
+
throw ApiFailure.fromDioException(e);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
Future<Task> create(TaskCreate payload) async {
|
|
100
|
+
try {
|
|
101
|
+
final response = await _dio.post<Map<String, dynamic>>(
|
|
102
|
+
'/tasks',
|
|
103
|
+
data: payload.toJson(),
|
|
104
|
+
);
|
|
105
|
+
return Task.fromJson(response.data!);
|
|
106
|
+
} on DioException catch (e) {
|
|
107
|
+
throw ApiFailure.fromDioException(e);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
Future<Task> update(String id, TaskUpdate payload) async {
|
|
112
|
+
try {
|
|
113
|
+
final response = await _dio.put<Map<String, dynamic>>(
|
|
114
|
+
'/tasks/$id',
|
|
115
|
+
data: payload.toJson(),
|
|
116
|
+
);
|
|
117
|
+
return Task.fromJson(response.data!);
|
|
118
|
+
} on DioException catch (e) {
|
|
119
|
+
throw ApiFailure.fromDioException(e);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
Future<void> delete(String id) async {
|
|
124
|
+
try {
|
|
125
|
+
await _dio.delete<void>('/tasks/$id');
|
|
126
|
+
} on DioException catch (e) {
|
|
127
|
+
throw ApiFailure.fromDioException(e);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
@Riverpod(keepAlive: true)
|
|
133
|
+
TaskRepository taskRepository(TaskRepositoryRef ref) {
|
|
134
|
+
return TaskRepository(ref.watch(dioProvider));
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## AsyncNotifier (List) Pattern
|
|
141
|
+
|
|
142
|
+
```dart
|
|
143
|
+
// lib/features/task/application/task_providers.dart
|
|
144
|
+
import 'package:flutter/foundation.dart';
|
|
145
|
+
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
|
146
|
+
|
|
147
|
+
import '../data/task_repository.dart';
|
|
148
|
+
import '../domain/task.dart';
|
|
149
|
+
|
|
150
|
+
part 'task_providers.g.dart';
|
|
151
|
+
|
|
152
|
+
@immutable
|
|
153
|
+
class TaskListParams {
|
|
154
|
+
const TaskListParams({
|
|
155
|
+
this.page = 0,
|
|
156
|
+
this.size = 20,
|
|
157
|
+
this.status,
|
|
158
|
+
this.search,
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
final int page;
|
|
162
|
+
final int size;
|
|
163
|
+
final TaskStatus? status;
|
|
164
|
+
final String? search;
|
|
165
|
+
|
|
166
|
+
Map<String, dynamic> toJson() => {
|
|
167
|
+
'page': page,
|
|
168
|
+
'size': size,
|
|
169
|
+
if (status != null) 'status': status!.name.toUpperCase(),
|
|
170
|
+
if (search != null && search!.isNotEmpty) 'search': search,
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
TaskListParams copyWith({int? page, int? size, TaskStatus? status, String? search}) {
|
|
174
|
+
return TaskListParams(
|
|
175
|
+
page: page ?? this.page,
|
|
176
|
+
size: size ?? this.size,
|
|
177
|
+
status: status ?? this.status,
|
|
178
|
+
search: search ?? this.search,
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
@override
|
|
183
|
+
bool operator ==(Object other) =>
|
|
184
|
+
other is TaskListParams &&
|
|
185
|
+
other.page == page &&
|
|
186
|
+
other.size == size &&
|
|
187
|
+
other.status == status &&
|
|
188
|
+
other.search == search;
|
|
189
|
+
|
|
190
|
+
@override
|
|
191
|
+
int get hashCode => Object.hash(page, size, status, search);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
@riverpod
|
|
195
|
+
class TaskList extends _$TaskList {
|
|
196
|
+
@override
|
|
197
|
+
Future<PaginatedResponse<Task>> build(TaskListParams params) async {
|
|
198
|
+
final repo = ref.watch(taskRepositoryProvider);
|
|
199
|
+
return repo.list(params);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
Future<void> refresh() async {
|
|
203
|
+
ref.invalidateSelf();
|
|
204
|
+
await future;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
@riverpod
|
|
209
|
+
Future<Task> taskById(TaskByIdRef ref, String id) async {
|
|
210
|
+
final repo = ref.watch(taskRepositoryProvider);
|
|
211
|
+
return repo.getById(id);
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## Mutation Notifier Pattern
|
|
218
|
+
|
|
219
|
+
```dart
|
|
220
|
+
@riverpod
|
|
221
|
+
class TaskFormController extends _$TaskFormController {
|
|
222
|
+
@override
|
|
223
|
+
AsyncValue<void> build() => const AsyncData(null);
|
|
224
|
+
|
|
225
|
+
Future<Task> create(TaskCreate payload) async {
|
|
226
|
+
state = const AsyncLoading();
|
|
227
|
+
try {
|
|
228
|
+
final repo = ref.read(taskRepositoryProvider);
|
|
229
|
+
final created = await repo.create(payload);
|
|
230
|
+
ref.invalidate(taskListProvider);
|
|
231
|
+
state = const AsyncData(null);
|
|
232
|
+
return created;
|
|
233
|
+
} catch (error, stack) {
|
|
234
|
+
state = AsyncError(error, stack);
|
|
235
|
+
rethrow;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
Future<Task> update(String id, TaskUpdate payload) async {
|
|
240
|
+
state = const AsyncLoading();
|
|
241
|
+
try {
|
|
242
|
+
final repo = ref.read(taskRepositoryProvider);
|
|
243
|
+
final updated = await repo.update(id, payload);
|
|
244
|
+
ref.invalidate(taskListProvider);
|
|
245
|
+
ref.invalidate(taskByIdProvider(id));
|
|
246
|
+
state = const AsyncData(null);
|
|
247
|
+
return updated;
|
|
248
|
+
} catch (error, stack) {
|
|
249
|
+
state = AsyncError(error, stack);
|
|
250
|
+
rethrow;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
Future<void> delete(String id) async {
|
|
255
|
+
state = const AsyncLoading();
|
|
256
|
+
try {
|
|
257
|
+
final repo = ref.read(taskRepositoryProvider);
|
|
258
|
+
await repo.delete(id);
|
|
259
|
+
ref.invalidate(taskListProvider);
|
|
260
|
+
state = const AsyncData(null);
|
|
261
|
+
} catch (error, stack) {
|
|
262
|
+
state = AsyncError(error, stack);
|
|
263
|
+
rethrow;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
---
|
|
270
|
+
|
|
271
|
+
## Consuming Providers in Widgets
|
|
272
|
+
|
|
273
|
+
```dart
|
|
274
|
+
import 'package:flutter/material.dart';
|
|
275
|
+
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
276
|
+
|
|
277
|
+
import '../application/task_providers.dart';
|
|
278
|
+
import '../../../shared/widgets/empty_state.dart';
|
|
279
|
+
import '../../../shared/widgets/error_state.dart';
|
|
280
|
+
import '../../../shared/widgets/loading_indicator.dart';
|
|
281
|
+
|
|
282
|
+
class TaskListScreen extends ConsumerStatefulWidget {
|
|
283
|
+
const TaskListScreen({super.key});
|
|
284
|
+
|
|
285
|
+
@override
|
|
286
|
+
ConsumerState<TaskListScreen> createState() => _TaskListScreenState();
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
class _TaskListScreenState extends ConsumerState<TaskListScreen> {
|
|
290
|
+
TaskListParams _params = const TaskListParams();
|
|
291
|
+
|
|
292
|
+
@override
|
|
293
|
+
Widget build(BuildContext context) {
|
|
294
|
+
final taskListAsync = ref.watch(taskListProvider(_params));
|
|
295
|
+
|
|
296
|
+
return Scaffold(
|
|
297
|
+
appBar: AppBar(title: const Text('Tasks')),
|
|
298
|
+
body: taskListAsync.when(
|
|
299
|
+
loading: () => const LoadingIndicator(),
|
|
300
|
+
error: (error, _) => ErrorState(
|
|
301
|
+
error: error,
|
|
302
|
+
onRetry: () => ref.invalidate(taskListProvider(_params)),
|
|
303
|
+
),
|
|
304
|
+
data: (page) {
|
|
305
|
+
if (page.content.isEmpty) {
|
|
306
|
+
return const EmptyState(message: 'No tasks found.');
|
|
307
|
+
}
|
|
308
|
+
return ListView.builder(
|
|
309
|
+
itemCount: page.content.length,
|
|
310
|
+
itemBuilder: (context, index) {
|
|
311
|
+
final task = page.content[index];
|
|
312
|
+
return ListTile(
|
|
313
|
+
title: Text(task.title),
|
|
314
|
+
subtitle: Text(task.status.name),
|
|
315
|
+
onTap: () => context.push('/task/${task.id}'),
|
|
316
|
+
);
|
|
317
|
+
},
|
|
318
|
+
);
|
|
319
|
+
},
|
|
320
|
+
),
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
---
|
|
327
|
+
|
|
328
|
+
## Offline-First Pattern (Hive cache + remote)
|
|
329
|
+
|
|
330
|
+
```dart
|
|
331
|
+
@riverpod
|
|
332
|
+
class TaskList extends _$TaskList {
|
|
333
|
+
@override
|
|
334
|
+
Future<List<Task>> build(TaskListParams params) async {
|
|
335
|
+
final box = ref.watch(taskCacheBoxProvider);
|
|
336
|
+
final repo = ref.watch(taskRepositoryProvider);
|
|
337
|
+
|
|
338
|
+
// Seed with cached data if present (sync)
|
|
339
|
+
final cached = box.values.toList();
|
|
340
|
+
|
|
341
|
+
try {
|
|
342
|
+
final remote = await repo.list(params);
|
|
343
|
+
await box.clear();
|
|
344
|
+
await box.addAll(remote.content);
|
|
345
|
+
return remote.content;
|
|
346
|
+
} catch (e) {
|
|
347
|
+
// Surface fresh data when possible, but fall back to cache
|
|
348
|
+
if (cached.isNotEmpty) {
|
|
349
|
+
return cached;
|
|
350
|
+
}
|
|
351
|
+
rethrow;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
---
|
|
358
|
+
|
|
359
|
+
## Family Providers for Detail / Parametrised Queries
|
|
360
|
+
|
|
361
|
+
```dart
|
|
362
|
+
@riverpod
|
|
363
|
+
Future<Task> taskById(TaskByIdRef ref, String id) async {
|
|
364
|
+
final repo = ref.watch(taskRepositoryProvider);
|
|
365
|
+
return repo.getById(id);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Usage in widget:
|
|
369
|
+
// final taskAsync = ref.watch(taskByIdProvider('abc-123'));
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
---
|
|
373
|
+
|
|
374
|
+
## Listening for Side Effects (`ref.listen`)
|
|
375
|
+
|
|
376
|
+
Use `ref.listen` to react to provider state changes without rebuilding the widget —
|
|
377
|
+
useful for navigation, SnackBars, and dialogs:
|
|
378
|
+
|
|
379
|
+
```dart
|
|
380
|
+
@override
|
|
381
|
+
Widget build(BuildContext context, WidgetRef ref) {
|
|
382
|
+
ref.listen<AsyncValue<void>>(taskFormControllerProvider, (previous, next) {
|
|
383
|
+
next.whenOrNull(
|
|
384
|
+
error: (error, _) {
|
|
385
|
+
ScaffoldMessenger.of(context).showSnackBar(
|
|
386
|
+
SnackBar(content: Text(error.toString())),
|
|
387
|
+
);
|
|
388
|
+
},
|
|
389
|
+
);
|
|
390
|
+
});
|
|
391
|
+
// ...
|
|
392
|
+
}
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
---
|
|
396
|
+
|
|
397
|
+
## Multi-Step Form / Wizard Pattern
|
|
398
|
+
|
|
399
|
+
```dart
|
|
400
|
+
@freezed
|
|
401
|
+
class TaskWizardState with _$TaskWizardState {
|
|
402
|
+
const factory TaskWizardState({
|
|
403
|
+
@Default(0) int step,
|
|
404
|
+
@Default(null) TaskCreateStep1? step1,
|
|
405
|
+
@Default(null) TaskCreateStep2? step2,
|
|
406
|
+
}) = _TaskWizardState;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
@riverpod
|
|
410
|
+
class TaskWizard extends _$TaskWizard {
|
|
411
|
+
@override
|
|
412
|
+
TaskWizardState build() => const TaskWizardState();
|
|
413
|
+
|
|
414
|
+
void saveStep1(TaskCreateStep1 data) =>
|
|
415
|
+
state = state.copyWith(step1: data, step: 1);
|
|
416
|
+
|
|
417
|
+
void saveStep2(TaskCreateStep2 data) =>
|
|
418
|
+
state = state.copyWith(step2: data, step: 2);
|
|
419
|
+
|
|
420
|
+
void back() => state = state.copyWith(step: state.step - 1);
|
|
421
|
+
|
|
422
|
+
void reset() => state = const TaskWizardState();
|
|
423
|
+
}
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
---
|
|
427
|
+
|
|
428
|
+
## Cache Invalidation Strategy
|
|
429
|
+
|
|
430
|
+
Follow this decision tree when invalidating Riverpod state after mutations:
|
|
431
|
+
|
|
432
|
+
| Mutation | Invalidation Target |
|
|
433
|
+
|---|---|
|
|
434
|
+
| Create item | `ref.invalidate(<entity>ListProvider)` |
|
|
435
|
+
| Update item | `ref.invalidate(<entity>ListProvider)` + `ref.invalidate(<entity>ByIdProvider(id))` |
|
|
436
|
+
| Delete item | `ref.invalidate(<entity>ListProvider)` |
|
|
437
|
+
| Bulk action (delete multiple) | `ref.invalidate(<entity>ListProvider)` and individual `<entity>ByIdProvider(id)` |
|
|
438
|
+
| Status toggle | `ref.invalidate(<entity>ListProvider)` + `ref.invalidate(<entity>ByIdProvider(id))` |
|
|
439
|
+
|
|
440
|
+
Prefer `ref.invalidate(...)` over manually mutating provider state — invalidation lets
|
|
441
|
+
the next `watch` rebuild fresh from source while preserving the loading UI.
|
|
442
|
+
|
|
443
|
+
---
|
|
444
|
+
|
|
445
|
+
## Auth State Provider
|
|
446
|
+
|
|
447
|
+
```dart
|
|
448
|
+
@freezed
|
|
449
|
+
sealed class AuthState with _$AuthState {
|
|
450
|
+
const factory AuthState.unauthenticated() = _Unauthenticated;
|
|
451
|
+
const factory AuthState.authenticated({
|
|
452
|
+
required AuthUser user,
|
|
453
|
+
required String accessToken,
|
|
454
|
+
}) = _Authenticated;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
@Riverpod(keepAlive: true)
|
|
458
|
+
class Auth extends _$Auth {
|
|
459
|
+
@override
|
|
460
|
+
FutureOr<AuthState> build() async {
|
|
461
|
+
final repo = ref.watch(authRepositoryProvider);
|
|
462
|
+
final restored = await repo.restoreSession();
|
|
463
|
+
return restored ?? const AuthState.unauthenticated();
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
Future<void> login(String email, String password) async {
|
|
467
|
+
state = const AsyncLoading();
|
|
468
|
+
state = await AsyncValue.guard(() async {
|
|
469
|
+
final repo = ref.read(authRepositoryProvider);
|
|
470
|
+
final session = await repo.login(email, password);
|
|
471
|
+
return AuthState.authenticated(
|
|
472
|
+
user: session.user,
|
|
473
|
+
accessToken: session.accessToken,
|
|
474
|
+
);
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
Future<void> logout() async {
|
|
479
|
+
final repo = ref.read(authRepositoryProvider);
|
|
480
|
+
await repo.logout();
|
|
481
|
+
state = const AsyncData(AuthState.unauthenticated());
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
---
|
|
487
|
+
|
|
488
|
+
## Testing Providers in Isolation
|
|
489
|
+
|
|
490
|
+
```dart
|
|
491
|
+
// test/features/task/task_list_test.dart
|
|
492
|
+
import 'package:flutter_test/flutter_test.dart';
|
|
493
|
+
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
494
|
+
import 'package:mocktail/mocktail.dart';
|
|
495
|
+
|
|
496
|
+
import 'package:mobile_app/features/task/data/task_repository.dart';
|
|
497
|
+
import 'package:mobile_app/features/task/application/task_providers.dart';
|
|
498
|
+
|
|
499
|
+
class _MockTaskRepository extends Mock implements TaskRepository {}
|
|
500
|
+
|
|
501
|
+
void main() {
|
|
502
|
+
test('taskList returns paginated tasks', () async {
|
|
503
|
+
final repo = _MockTaskRepository();
|
|
504
|
+
when(() => repo.list(any())).thenAnswer((_) async => PaginatedResponse(
|
|
505
|
+
content: [const Task(id: '1', title: 'A', status: TaskStatus.draft, /*...*/)],
|
|
506
|
+
page: 0, size: 20, totalElements: 1, totalPages: 1,
|
|
507
|
+
));
|
|
508
|
+
|
|
509
|
+
final container = ProviderContainer(overrides: [
|
|
510
|
+
taskRepositoryProvider.overrideWithValue(repo),
|
|
511
|
+
]);
|
|
512
|
+
addTearDown(container.dispose);
|
|
513
|
+
|
|
514
|
+
final page = await container.read(taskListProvider(const TaskListParams()).future);
|
|
515
|
+
expect(page.content, hasLength(1));
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
```
|