Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

13 - State Management (Houses Module)

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

CubitScreen (05)Data
HouseListCubit1page, limit, List<House>, PaginationMeta
HouseDetailCubit2House, members (joined list + verified flag)
HouseFormCubit3form model, field errors, conflicts, submit
HouseMembersCubit2 (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); meta drives pagination (buildPaginationMeta, pagination-query.dto.ts:41-55).
  • No server sort (houses.service.ts:30) - client sorts by createdAt asc 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); 404 House not found. (houses.service.ts:38).
  • Members: client join today - GET /students?page&limit (student.controller.ts:41-43) then where(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 (Hive optional) 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 with verified: false.
  • Ref resolution (student forms): house picker options come from HouseListCubit cache or GET /houses; no per-picker fetch.
  • Events as hints: no house domain events exist (G4, 09); client refresh is response-driven, not event-driven. If HouseUpdated events land (planned), the detail cubit can refresh on hint - do not build the plumbing now.
  • Permission gating: cubits expose canCreate/canUpdate/canDelete from 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.md has no houses items).