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 (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; byContext derived 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/upload with onSendProgress (dio) driving progress (see 15_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 DownloadTask in state).
  • Cancel: abort dio request; no resume (no server Range) — retry restarts from 0.
  • Completed: localPath from path_provider temp dir; "Open"/"Share" (forward-looking).

1.4 FileDetailCubit — detail sheet

  • Loads GET /api/v1/files/:id; 404 → emit FileGone → 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

  • byContext linkage index: cached in app prefs (shared LocalStore) (proposed) — rebuilt from GET /files on 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.