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

Per-screen Cubits (Flutter/bloc; proposal, 00-shared/06) + the module-wide guardian-link cache that admin screens share. Backed by ParentsRepository (dio) calling the endpoints in 12_API_Mapping.md.


  • One ParentsStore (Cubit-injected singleton within the module scope) holds: List<ParentRef> listPage, meta, and a per-parent Map<parentId, List<Link>> links.
  • Invariant: any mutation (link create/delete) invalidates the affected parent's links cache so every screen re-fetches the children section on next open.
  • No persistence for links (server truth; small data); list uses last-good cache per 00-shared/06 §3.3 (TTL 5 min; RefreshIndicator bypasses).

2. Per-screen Cubits

ScreenCubitEvents → State
Parents listParentsCubitLoad, Refresh, LoadMore, Retry{initial, loading, loaded(items, meta), empty, error(code), loadingMore}
Parent detailParentDetailCubitLoad(id), Refresh, Retry{initial, loading, loaded(parent, links, students), notFound, error(code)}
Create/Edit formParentFormCubitInit(existing?), SetField, Submit(form){idle, dirty, saving, saved(parentId), duplicate(existingId), validation(errors), error(code)}
Link sheetLinkCubitInit(context) (parentId+studentId known), PickStudent, SetRelationship, TogglePrimary/Pickup/Financial, Submit{idle, searching, ready, saving, linked(link), warnDuplicate, warnSecondPrimary, error}
UnlinkUnlinkCubitConfirm(linkId){idle, confirming, done, notFound, error}
My children (forward-looking)ParentChildrenCubitLoad(){initial, loading, loaded(children), empty, error}
Child switcherChildSwitcherCubitSwitchChild(id){children, selectedChildId}
My profile (forward-looking)MyProfileCubitLoad(), Submit(form){initial, loading, loaded(profile), saving, saved, error}

3. State objects (concise)

class ParentRef {
  final String id; String? userId;
  String? occupation, company, relationshipNotes;
  num? annualIncome; int emergencyContactPriority;
  bool pickupAuthorization;
  DateTime createdAt, updatedAt;
}
class Link {
  final String id; String studentId, parentId;
  RelationshipType relationship; // mother|father|guardian|grandparent|relative|foster_parent
  bool isPrimaryGuardian, financialResponsibility, pickupAllowed;
  int emergencyPriority;
}
class StudentRef { final String id; String admissionNumber; String? rollNumber; String? gradeId, sectionId, classId; String status; }

4. Events & actions map (UI → Cubit → API)

UI eventCubit methodRepository call
list open / pullload() / refresh()repo.parents(page, limit)
scroll endloadMore()repo.parents(page+1)
row opendetail.load(id)repo.parent(id) + repo.parentLinks(id) + per-child repo.student(id)
create submitform.submit()repo.createParent(dto)
edit submitform.submit()repo.updateParent(id, dto)
deletedetail.remove(id) (menu)repo.deleteParent(id)
link submitlink.submit()repo.linkParent(studentId, linkDto)
student guardians embedstudentsGuardians.load(studentId)repo.studentLinks(studentId) + per-parent repo.parent(id)
unlink confirmunlink.confirm(linkId)repo.unlink(linkId)
set primarylink.switchPrimary(link)repo.unlink(oldPrimary) + repo.linkParent(studentId, newDto) (OQ-4)
child switchswitcher.switch(id)— (local state)

5. Caching & refresh

  • Parents list: last-good cache sl:cache:parents:{tenant}:{page}; RefreshIndicator bypasses; infinite-scroll appends.
  • Detail: no cache — always fetch on open; children section re-fetches after any link mutation (invariant §1).
  • My children (forward-looking): last-good cache 5 min + banner; child switcher selection persisted in memory only (per session).

6. Realtime

  • No WS surface today. ParentCreatedin-app notification job (event-queue-map.ts:37); when the notification center + WS ship (planned), a parent.linked push invalidates the detail cache and nudges refresh (00-shared/06 §3.4).

7. Error states per action

ActionErrorState →
create409duplicate(existingId) → banner + open existing
any404notFoundAppErrorState (detail) or treat-as-removed (unlink)
linkduplicate pre-checkwarnDuplicate → warning + block submit (OQ-2)
linksecond primarywarnSecondPrimary → warning, submit allowed (OQ-5)
any429rateLimited → countdown
any401session expiry flow (global)
any5xxerror(code)AppErrorState + requestId

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

  • Pure-Dart cubits; unit-test: pagination mixin (append/refresh/meta), primary-switch two-step sequencing, duplicate-link warning logic, 404 mapping.
  • Widget tests: list 3 states; detail children skeleton→loaded/empty; form duplicate/validation; link sheet warnings; switcher selection.

9. Cross-cutting interplay

  • ConnectivityCubit gates all writes offline; reads serve cache + banner.
  • AuthCubit session expiry → re-login; module state discarded (no cross-login persistence).
  • Role changes (RBAC (planned)) rebuild route visibility; ParentsCubit survives only under parent.read.
  • TenantContext (from AuthCubit) implicit in every repository call — never stored client-side per record.