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

15 — Flutter Implementation Guide (Reports Module)

Concrete Flutter build notes for the Reports module. App architecture per 00-shared/11 (recommended: Cubit, 00-shared/06); state layout 13; polling 10; contracts 12.


1. Packages

  • Existing shared stack only — no new dependencies for polling (core dart:async Timer).
  • Downloads/viewer (planned) — see §5 for recommended additions once the server endpoint exists: flutter_downloader or dio + path_provider (follow 00-shared/11).
  • No PDF viewer today (no PDFs exist).

2. Service layer

class ReportsApi {
  final Dio dio; // shared client, `00-shared/11`
  Future<JobCreated> generate(GenerateReportDto dto) async {
    final r = await dio.post('/reports/generate', data: dto.toJson());
    return JobCreated.fromJson(r.data['data']); // {jobId, status}
  }
  Future<ReportJobDoc> getJob(String jobId) async {
    final r = await dio.get('/reports/$jobId');
    return ReportJobDoc.fromJson(r.data['data']);
  }
}

Envelope unwrap per 00-shared/07; DTO mirrors GenerateReportDto (generate-report.dto.ts:5-34): type enum string, optional MongoIds as strings, dates as ISO strings.

3. Polling engine (shared widget-level service)

class JobPoller {
  JobPoller(this.api, {this.interval = const Duration(seconds: 2)});
  Timer? _t;
  void start(String jobId, void Function(ReportJobDoc) onDoc,
      void Function(JobPollerError) onError) {
    _t ??= Timer.periodic(interval + _jitter(), () async {
      try {
        final doc = await api.getJob(jobId);
        onDoc(doc);            // cubit maps status
        if (doc.status.isTerminal) stop();
      } on DioException catch (e) {
        if (e.type == DioExceptionType.connectionTimeout) onError(/* pause */);
      }
    });
  }
  void stop() { _t?.cancel(); _t = null; }
}

Rules: 2 s ± 400 ms jitter; pause after 2 consecutive failures; stop() on terminal/404; cancel in dispose (10 §1). ReportJobStatus maps queued|processing|completed|failed (report-job.schema.ts:13-18).

4. Cubit wiring (13 §1)

class ReportJobDetailCubit extends Cubit<JobDetailState> {
  ReportJobDetailCubit(this.api) : super(JobDetailState.initial());
  void open(String jobId) {
    poller.start(jobId, (doc) => emit(state.copyWith(
      job: doc, status: doc.status,
      // terminal → emit completed/failed; cubit stops poller
    )), (e) => emit(state.copyWith(pollPaused: true)));
  }
  void retry() {
    // re-POST params (reports.service.ts:31-43) → new jobId → pushReplacement
  }
}

Never predict status; mirror the server doc only (13 §2 mermaid).

5. Download & viewer (planned)

Until GET /reports/:jobId/download (04-Modules/Reports.md:26) exists:

  • Hide export CTAs (06 §S5); no download manager code ships.

When it lands:

  • Download manager: GET /reports/:jobId/download → stream to app docs dir (path_provider), Content-Disposition filename parsed from headers (files.controller.ts:55-64 pattern — attachment; filename="…"); notify completion; keep per-tenant folder.
  • Viewer: open PDF via flutter_pdfview or share via share_plus; never load the whole file into memory for large exports (streaming, :49).
  • Alternate path: server stores via files module → client uses GET /files/:id/download (file.read, files.controller.ts:55-64).

6. Scheduler & notifications (forward-looking)

  • Scheduled jobs (attendance-report.job.ts:13-24) surface via history once a list API exists (12 E4).
  • Completion notification: subscribe when server emits ReportGenerated (04-Modules/Reports.md:33) — deep link /reports/jobs/:jobId.

7. Offline

  • History + last-good job/result cached (13 §3); banner + paused poll (10 §4); no writes offline (generate requires network).

8. Testing

  • Unit: cubit transitions, poller pause/resume/jitter, renderer mapping (result → models per 06 §S5).
  • Widget: S4 status swaps with fake poller; S2 validation (08 §3).
  • Golden: metric cards with tabularFigures (11 §4).
  • E2E (00-shared/10): mock API — full journey J1/J2/J3 (03); failure paths: 404, job failed, network drop mid-poll.