13 — State Management (Files Module)
Flutter-side state model for file flows. Global architecture, Cubit patterns, storage strategy: 00-shared/06_State_Management, 00-shared/11_Flutter_App_Architecture. This file defines the module's Cubits and the upload state machine.
1. Cubits
1.1 FilesCubit — list
sealed class FilesState {}
class FilesLoading extends FilesState {}
class FilesLoaded extends FilesState {
final List<FileRecord> files; // newest first (server order, files.service.ts:48-50)
final Map<String, List<FileRecord>> byContext; // client-side linkage index
}
class FilesError extends FilesState { final String message; }
- Load:
GET /api/v1/files(full tenant list; no pagination — gap). - Context filter: module passes context key;
byContextderived client-side (server has no entity linkage field,file.schema.ts:9-25). - Events:
load(),refresh(),upsertLocal(record),removeLocal(id).
1.2 UploadCubit — upload
sealed class UploadState {}
class UploadIdle extends UploadState {} // sheet closed / untouched
class UploadPicking extends UploadState {} // system picker open
class UploadSelected extends UploadState { final PickedFile f; }
class UploadUploading extends UploadState {
final PickedFile f; final double progress; final int sentBytes; final int totalBytes;
}
class UploadSuccess extends UploadState { final FileRecord record; }
class UploadFailed extends UploadState { final PickedFile f; final String message; }
- Upload flow:
POST /api/v1/files/uploadwithonSendProgress(dio) drivingprogress(see15_Flutter_Implementation_Guide.md). - Success →
FilesCubit.upsertLocal(record); failure → retry re-POSTs (new record — no server idempotency).
1.3 DownloadCubit — download manager
class DownloadTask {
final String id; final String fileId; final String name;
final DownloadStatus status; // queued | downloading | completed | failed | cancelled
final double progress; final String? localPath; final int? bytesReceived;
}
- One queue, multiple tasks (list of
DownloadTaskin state). - Cancel: abort dio request; no resume (no server Range) — retry restarts from 0.
- Completed:
localPathfrompath_providertemp dir; "Open"/"Share"(forward-looking).
1.4 FileDetailCubit — detail sheet
- Loads
GET /api/v1/files/:id; 404 → emitFileGone→ sheet closes, row removed.
2. Upload state machine
stateDiagram-v2
[*] --> Idle
Idle --> Picking : open sheet
Picking --> Selected : file picked
Picking --> Idle : cancelled
Selected --> Uploading : POST /files/upload (multipart)
Selected --> Picking : re-pick
Uploading --> UploadSuccess : 201 FileRecord
Uploading --> UploadFailed : 4xx/5xx/network
Uploading --> Idle : user cancels (abort transport)
UploadFailed --> Uploading : retry (new POST)
UploadSuccess --> [*] : tile inserted in list
Key invariants:
- Cancelled uploads are never recorded server-side (body discarded at transport; no
partial-cleanup job yet —
STORAGE_ARCHITECTURE.md:66(planned)). - Retry after failure creates a new
FileRecord(no idempotency key) — duplicate possibility must be surfaced as "Retry again?" not "Resume".
3. Download state machine
stateDiagram-v2
[*] --> Queued : user taps download
Queued --> Downloading : GET /files/:id/download
Downloading --> Completed : bytes → local file
Downloading --> Failed : error/network
Downloading --> Cancelled : user cancels
Failed --> Downloading : retry (restart, no resume)
Completed --> [*] : Open / Share (forward-looking)
4. Persistence
byContextlinkage index: cached in app prefs (sharedLocalStore)(proposed)— rebuilt fromGET /fileson load; never treated as source of truth.- Upload outbox for offline retry:
(forward-looking). - No server-side state beyond the record itself (stateless module).
5. Cross-cutting
- 401/403 → global auth event (guards
(planned)). - Tenant switching (multi-institution, Phase 6
(planned)) → full Cubit reset + reload.