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 (Settings Module)

Per-screen Cubits (Flutter/bloc; proposal, 00-shared/06) backed by SettingsRepository (dio) which calls the endpoints in 12_API_Mapping.md. Server truth is the upsert response — config state must never diverge from it.


1. Cubit map

ScreenCubitEvents → State
List (hub)SettingsListCubitLoad(group?), Refresh(), GroupChanged(group), Search(q), EditRow(key), ValueChanged(key, value), SaveRow(key), ToggleBool(key, value), EnterBatch(), SaveAll(), ExitBatch() → {LoadState, settings[] (grouped), selectedGroup, query, dirty: Map<key, SettingDraft>, batchMode, savingKeys: Set}
DetailSettingDetailCubitLoad(key), FieldChanged(field, value), Save(), Delete(), DuplicateKey() → {LoadState, setting, draft, saving, deleted}
Create (sheet)CreateSettingCubitCreate(form) → {idle, saving, created(setting), error(code)}

Key reducer (list):

  • GroupChanged(g)repository.list(group: g) (server filter — settings.controller.ts:26-28), clears query + dirty set.
  • SaveRow(key) → builds full dto {key, value: draft.value, group: draft.group}PUT /settings → on success replace row from server doc (data: Setting, setting.repository.ts:39), remove from dirty, snackbar; on error keep dirty + snackbar (retry safe — idempotent).
  • SaveAll() → serializes only dirty rows (full dto each) → PUT /settings/bulk (settings.service.ts:28-34) → on 200 replace all rows from response array; on 5xx keep dirty set, snackbar "Saved N of M" with per-key retry.
  • Search(q)client-side filter of the current fetch (no server q param).

2. State objects (concise)

class SettingDoc { id, tenantId, key, Object? value, group, isEncrypted,
                    createdBy?, updatedBy?, version, createdAt, updatedAt; }
class SettingDraft { Object? value; SettingGroup group; }   // per-row in dirty map
class SettingsState { LoadState load; List<SettingDoc> rows;
  SettingGroup? selectedGroup; String query;
  Map<String, SettingDraft> dirty; bool batchMode; Set<String> savingKeys; }

3. Cache & staleness

SurfaceCacheTTLPolicy
List per (group?)in-memory + shared_preferences last-good5 min (config can change via API/other admins)stale-while-revalidate (00-shared/06 §3.3); RefreshIndicator bypasses
Detailnonealways fetch on open (GET /settings/:key)
Server cachenone in settings pathAPI is single-doc Mongo reads

Bump the list cache key whenever a save/delete succeeds ({tenant}:settings:{group}) so sibling devices see fresh values on next focus. On screen focus → Refresh().

4. Optimistic vs full-save (per source semantics)

OperationStrategyReason (source)
Boolean toggleoptimistic + rollbacksafe mutation class (00-shared/06 §3.5); instant feedback expected
String/number/JSON saveserver-confirmvalue replaced wholesale (setting.repository.ts:37); must reflect server truth
Batch saveserver-confirm, per-item statussequential loop, not transactional (settings.service.ts:28-34)
Deleteserver-confirm; no undosoft delete irreversible via API (OQ-5)
Createserver-confirmupsert idempotent — retry-safe

No offline write queue (00-shared/07 §10): offline → banner, writes blocked with guidance; last-good list still renders.

5. Realtime

  • No WS topic for settings (00-shared/07 §8 enumerates no settings channel) → no push-driven invalidation. Refresh-on-focus is the correctness mechanism; a future settings.changed topic is (planned) (OQ-7/8).

6. Error states per action

ActionErrorState →
load401silent refresh → sessionExpired → login
load5xxAppErrorState(code, retry)
detail load404AppSettingNotFound (empty state)
save400inline field errors (group enum details)
save500snackbar + form kept (idempotent retry)
save (E11000)500banner "key cannot be re-created" (OQ-5)
bulk5xx mid-loop"Saved N of M"; per-key retry re-sends full dirty set
delete404treat as removed
any429countdown, disable submit, no auto-retry

7. Cross-cutting interplay

  • ConnectivityCubit gates writes (offline → editors disabled + banner).
  • AuthCubit provides tenantId/user (displayed in detail meta when createdBy present).
  • FeatureFlagsCubit not consumed by this module (settings surface is not flag-gated).
  • Permission changes (role edit) → route rebuild hides /settings without settings.read (00-shared/06 §3.6); server not enforcing (OQ-2).

8. Testing hooks (00-shared/06 §6)

  • Unit: SettingsListCubit — group switch triggers server filter call; dirty map lifecycle; SaveAll serializes only dirty full dtos; partial-failure state ("Saved N of M").
  • Unit: retry-after-500 keeps dirty set.
  • Widget: list loading/error/empty/dirty; JSON editor valid/invalid; batch bar counts.