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

Per-screen Cubits (Flutter/bloc; proposal, 00-shared/06) backed by HomeworkRepository (dio) calling the endpoints in 12_API_Mapping.md. Module-wide rules: no optimistic mutations, server-confirmed writes only; submission is a write-once op with a 409 handler; grading is optimistic-free (side effects: events → notifications).


1. HomeworkListCubit (list screen, both roles)

stateDiagram-v2
    [*] --> initial
    initial --> loading : Load(role)
    loading --> loaded : class homework array (dueDate desc)
    loading --> error : 5xx / 404
    loaded --> loading : Refresh (pull) | ChangeClass(filter)
    loaded --> empty : 0 items
    error --> loading : Retry
    loaded --> loaded : Delete done (row removed server-confirmed)
  • State: {status, items[], classId, role}.
  • items = HomeworkSummary[] (doc → model: id, teacherId, classId, subjectId, title, description, attachments, assignedDate, dueDate, status).
  • Student variant: classId from StudentProfileCubit (Students module, cached 24 h); emits myState per card lazily via per-card GET /homework/:id/submissions filter by own studentId (proposed) — OQ-6.
  • Caching: list cache key hw:list:{tenant}:{classId}, TTL 5 min, stale-while-revalidate (00-shared/06 §3.3); RefreshIndicator bypasses.
  • Events: Load, Refresh, ChangeClass(classId), Retry, Delete(id){status}. Delete is server-confirmed; on 200 remove from items.

2. HomeworkDetailCubit

  • State: {status, homework?, mySubmission? (student), submissionsCount? (teacher)}.
  • Load: GET /homework/:id → doc; teacher additionally fetches GET /homework/:id/submissions (count). Student fetches submissions and filters own row (studentId from profile).
  • Events: Load(id), Refresh, EditDone (re-fetch after PATCH), SubmitDone (replace mySubmission with server doc).
  • Late flag derived: submittedAt > dueDate (client-computed; OQ-1).
  • No cache (volatile); re-fetch on focus (grade may have landed elsewhere).

3. HomeworkFormCubit (create + edit)

  • State: {mode, form{title, description, classId, subjectId, dueDate, status?}, attachmentUploads: Map<fileKey, UploadState>, status: idle|submitting|error(field?) }.
  • UploadState: {idle, uploading(progress), uploaded(fileId), failed} — one per file; only uploaded ids enter attachments[] on submit.
  • Create submit → POST /homework → success → clear draft → navigate detail.
  • Edit submit → PATCH /homework/:id → refresh detail.
  • Errors: 400 → fieldErrors (map details[].message → field); 5xx → error (form preserved). Draft persisted locally on abandon (proposed).
  • No optimistic writes anywhere in this cubit.

4. SubmitCubit (submission form)

stateDiagram-v2
    [*] --> idle
    idle --> uploading : pick files
    uploading --> ready : all uploaded
    uploading --> failed : file error (retry per file)
    ready --> submitting : Submit
    submitting --> done(submittedDoc) : 201
    submitting --> duplicate : 409 "Already submitted."
    submitting --> error : 5xx / network
    duplicate --> done : treat as submitted (fetch doc)
  • Events: AddFile, RetryFile, RemoveFile, Submit(remarks, attachments).
  • Key decision: duplicate is a terminal success-like state — navigate to detail with info banner (homework.service.ts:92 semantics).
  • Late-warning computed from homework.dueDate when now > dueDate (banner, never blocks).
  • No optimistic write; submittedAt from server doc (homework.service.ts:96).

5. SubmissionsCubit (teacher list)

  • State: {status, items[], homeworkTitle, grouped{ungraded[], graded[]}}.
  • Load: GET /homework/:id/submissions → map docs (studentId, submittedAt, status, marks?, remarks?, attachments, gradedAt?); client groups ungraded (status='submitted') / graded (status='graded') and computes isLate.
  • Events: Load, Refresh, GradeDone(submissionId, updatedDoc) → replace item + regroup.
  • No pagination (array).

6. GradeCubit (grading sheet)

  • State: {status: idle|grading|done|error, current: SubmissionGradeModel?, form{marks, remarks}, isRegrade}.
  • isRegrade = current.status == 'graded' (schema enum homework-submission.schema.ts:24).
  • Submit → PATCH …/grade → 200 doc → done → pop with result → SubmissionsCubit applies (GradeDone).
  • Optimistic vs server-confirmed: grading is server-confirmed by design — the row's marks/status/gradedAt come only from the 200 payload; no local paint-before- write (event HomeworkGraded has side effects: notifications). Rationale: 00-shared/07 §9 write-with-side-effects rule.
  • Errors: 404 → gone (pop + refresh); 400 → field; 5xx → keep form.

7. Attachment upload (shared mixin)

stateDiagram-v2
    [*] --> idle
    idle --> uploading : POST /files/upload (multipart, progress)
    uploading --> uploaded : 201 FileRecord → fileId
    uploading --> failed : network / 4xx / 5xx
    failed --> uploading : Retry (new upload, no resume — B7)
    uploaded --> [*] : referenced in attachments[]
  • Progress via dio onSendProgress; uploading count gates the Submit/Assign button (submitEnabled = no uploading && no failed).

8. Realtime & cross-cutting interplay

  • WS: no homework topic in 00-shared/07 §8 — subscribe to notification.new (planned); on reconnect re-fetch current screen (00-shared/06 §3.4).
  • ConnectivityCubit: offline → list from cache + banner; submit/grade/create blocked with guidance (no offline write queue defined for homework writes — only uploads retry).
  • AuthCubit session expiry mid-flow → re-login → screen re-loads (state preserved where safe).

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

  • Unit: SubmitCubit 409→duplicate transition; GradeCubit regrade branch; LateBadge derivation (timezone-safe); upload state machine.
  • Widget: list loading/error/empty; submit states incl. duplicate banner; grade sheet error keeps values; regrade warning visible.