13 - State Management (Houses Module)
- 1. Cubit map
- 2. HouseListCubit
- 3. HouseFormCubit (create + edit share shape)
- 4. HouseDetailCubit + HouseMembersCubit
- 5. Cross-cutting
Per-screen Cubit/Bloc design on top of 00-shared/06 conventions (stack:
flutter_bloc+get_it; server state via dio repository;LoadState= Initial/Loading/Success/Error(ApiException)). Mermaid diagrams included.
1. Cubit map
| Cubit | Screen (05) | Data |
|---|---|---|
HouseListCubit | 1 | page, limit, List<House>, PaginationMeta |
HouseDetailCubit | 2 | House, members (joined list + verified flag) |
HouseFormCubit | 3 | form model, field errors, conflicts, submit |
HouseMembersCubit | 2 (members section) | List<StudentBrief> from students join, (planned) server endpoint swap |
Repositories (HouseRepository in features/houses/data/) are the only layer
touching HTTP; they map envelopes to models and throw
ApiException(status, message) (00-shared/06 §2-3). Student data is fetched via
the students module's repository (cross-module data reuse, never via the houses
repository).
2. HouseListCubit
stateDiagram-v2
[*] --> Initial
Initial --> Loading: fetch(page)
Loading --> Success: 200 envelope
Loading --> Error: 401/500
Success --> Loading: nextPage/prevPage/pullToRefresh
Success --> Error: refetch fails (keep stale)
Error --> Loading: retry
Success --> Success: create OK (prepend or refetch page 1)
Success --> Success: delete OK (refetch page 1)
- Fetch:
GET /houses?page&limit(houses.controller.ts:30-34);metadrives pagination (buildPaginationMeta,pagination-query.dto.ts:41-55). - No server sort (
houses.service.ts:30) - client sorts bycreatedAtasc for stable card order. - Mutations never optimistically mutate the grid; after create/delete the cubit refetches page 1 (response-driven; keeps conflict surfaces honest).
3. HouseFormCubit (create + edit share shape)
sequenceDiagram
participant S as Screen
participant F as HouseFormCubit
participant R as HouseRepo
participant A as API
S->>F: init(house?) // null = create
S->>F: submit(dto)
F->>R: create(dto) | update(id, dto)
R->>A: POST /houses | PATCH /houses/:id
A-->>R: 409 ConflictException | 201/200 doc
R-->>F: Success(doc) | Conflict(message)
F-->>S: SubmitDone / FieldError(code: message)
Note over S: 409 copy verbatim - houses.service.ts:19-21
Note over S: update 404 -> ApiException -> snackbar + pop (08 §5)
- State fields:
saved: bool,submitting: bool(single-flight),conflicts: Map<String, String>(server 409 verbatim). toDto()always returns the full field set (PATCH requires it,houses.controller.ts:44,08§4).- Update-mode duplicate code surfaces as generic 500/E11000 from the index
(
house.schema.ts:23) - error mapper normalizes to conflict copy (08§3).
4. HouseDetailCubit + HouseMembersCubit
stateDiagram-v2
[*] --> Initial
Initial --> Loading: load(id)
Loading --> Loaded: GET /houses/:id
Loading --> NotFound: 404 (pop + snackbar)
Loaded --> Refreshing: pullToRefresh (keep data)
Loaded --> Loaded: members refreshed separately
Members --> MembersLoading: loadMembers(houseId)
MembersLoading --> MembersLoaded: join GET /students filtered client-side
MembersLoading --> MembersError: 401/500 (inline retry, house stays)
MembersLoaded --> MembersEmpty: filter yields []
- House fetch:
GET /houses/:id(houses.controller.ts:36-40); 404House not found.(houses.service.ts:38). - Members: client join today -
GET /students?page&limit(student.controller.ts:41-43) thenwhere(houseId == id);verified: false(07§5). Swap to(planned)GET /students?houseId=/ members endpoint with zero cubit change if the repository abstracts it. - Members state is independent: one section's failure never blanks the header
(per-section
LoadState,06§2.3).
5. Cross-cutting
- Cache: house list cached in memory (
Hiveoptional) per tenant; detail reads cache-first then refreshes (offline tolerance,00-shared/10§2). Members join is never cached as truth - only the filtered snapshot withverified: false. - Ref resolution (student forms): house picker options come from
HouseListCubitcache orGET /houses; no per-picker fetch. - Events as hints: no house domain events exist (G4,
09); client refresh is response-driven, not event-driven. IfHouseUpdatedevents land(planned), the detail cubit can refresh on hint - do not build the plumbing now. - Permission gating: cubits expose
canCreate/canUpdate/canDeletefrom RBAC (permissions.constants.ts:46-49); UI hides actions accordingly (10§7). - Planned cubits:
HousePointsCubit/HouseEventsCubit(planned)- no data contract; do not scaffold (IMPLEMENTATION_PLAN.mdhas no houses items).