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

How to build the Biometric feature in the Flutter client on top of 00-shared/11. Forward-looking spec; no client repo exists yet. Scope note: the PRD excludes native mobile apps — web-first (PRODUCT_REQUIREMENTS_DOCUMENT.md:144) — so this guide builds the admin console (device/logs/verification), not the capture surface. Capture stays on vendor hardware + SDK (FEATURE_ROADMAP.md:56); QR/push check-in is (forward-looking).


1. Folder structure

features/biometric/
├── domain/
│   ├── models/
│   │   ├── device.dart              # DeviceStatus, name, deviceId, model, location, config
│   │   ├── punch.dart               # studentId, deviceId, timestamp, mode, rawData?, processed?
│   │   ├── sync_state.dart          # idle/running/succeeded/failed/dlq + punches + retries
│   │   └── verify_verdict.dart      # matched/mismatched/noPunch + punches
│   └── exceptions/biometric_exceptions.dart
├── data/
│   ├── dto/
│   │   ├── register_device_dto.dart
│   │   └── update_device_dto.dart
│   └── repositories/
│       └── biometric_repository.dart
└── presentation/
    ├── cubit/
    │   ├── biometric_hub_cubit.dart
    │   ├── device_list_cubit.dart
    │   ├── device_register_cubit.dart
    │   ├── device_detail_cubit.dart
    │   ├── enroll_cubit.dart
    │   ├── log_list_cubit.dart
    │   └── verify_checkin_cubit.dart
    ├── pages/
    │   ├── biometric_hub_page.dart
    │   ├── device_list_page.dart
    │   ├── device_register_page.dart
    │   ├── device_detail_page.dart
    │   ├── enroll_page.dart
    │   ├── log_list_page.dart
    │   └── verify_checkin_page.dart
    └── widgets/
        ├── device_status_badge.dart
        ├── sync_status_tile.dart
        ├── punch_row.dart
        ├── punch_timeline.dart
        ├── enroll_step_bar.dart
        ├── status_summary_card.dart
        ├── queue_health_chip.dart
        └── config_json_viewer.dart

2. Dependencies

flutter_bloc, dio (AppDio with refresh/error interceptors), go_router, get_it, intl, json_view (raw payload viewer). No biometric SDK in the app — capture is device-side; the console only reads results. No local template storage, ever.

3. Cubits

All per 13_State_Management.md; pure Dart, unit-testable. BiometricHubCubit is the only module-wide state (fleet summary); others are screen-scoped. No singleton beyond the repository.

4. BiometricRepository (single)

class BiometricRepository {
  Future<List<Device>> devices();                          // planned GET /biometric/devices
  Future<Device> createDevice(RegisterDeviceDto dto);      // planned POST /biometric/devices
  Future<Device> updateDevice(String id, UpdateDeviceDto); // planned PATCH /biometric/devices/:id
  Future<SyncResult> syncDevice(String id);                // planned POST /biometric/devices/:id/sync
  Future<Paginated<Punch>> logs({filters…});               // planned GET /biometric/logs
  Future<EnrollResult> enroll(String deviceId, String studentId); // planned
}

Ingest is not called from the app UI (device webhook); a clerk manual-entry form (proposed) would call it via the same dio stack with biometric.log.create gating.

5. Navigation

  • go_router routes per 04: /biometric, /biometric/devices, /biometric/devices/new, /biometric/devices/:id, /biometric/devices/:id/enroll, /biometric/logs, /biometric/verify.
  • Route guard: any biometric.* perm for hub; biometric.device.manage for devices subtree; biometric.log.read for logs/verify (permissions.constants.ts:41-43).
  • Deep link /biometric/logs?student=:id for dispute sharing.

6. Theme

No new tokens (11_Design_System_Mapping.md); state colors mapped to existing M3 roles. config_json_viewer monospace on both themes.

7. Extensions

  • DeviceStatus.presentable() → localized label + color role.
  • DateTime.toRelative() reused from auth package for punch timestamps.
  • Punch.processedColor() → tertiary/surfaceVariant (07 §10).

8. Localization keys

biometric.hub.*, biometric.device.*, biometric.enroll.*, biometric.log.*, biometric.verify.* — full list in 08_Form_Specifications.md; server messages mapped to keys, fallback to message for business 4xx only.

9. Storage

  • No biometric data persisted locally. Only non-sensitive caches: last-good device list, last-good hub status (00-shared/06 §5). Never cache punches or verdicts.

10. Testing

  • Unit: sync state machine (13 §1); verdict transitions; DTO→model mappers.
  • Widget: hub degraded/healthy; device list empty/error; enroll capture-fail retry; log filter chips.
  • Golden: all module widgets light/dark × 3 sizes (00-shared/10 §9).
  • Integration (mock server): register device → sync → punch appears → verify verdict.
  • E2E (when backend lands): register → sync → ingest (simulated device) → dispute → PATCH attendance.
  • Security smoke: unauthenticated ingest 401; cross-tenant isolation; no template in any payload.

11. Performance

  • Log list paginated + ListView.builder; punch timeline virtualized per day group.
  • Hub renders from cached summary first, updates on refresh.
  • No full-screen rebuild on sync state change — SyncStatusTile scoped rebuilds.

12. Proposals flagged to the team

  1. Build order blocked by backend: device CRUD + logs endpoints, then the biometric-sync worker (OQ-4), then sync-status UI. The hub screen must not ship with a permanently-empty queue card.
  2. Template storage decision (OQ-3) gates enrollment UI; until then keep EnrollCubit designed but unshipped.
  3. biometric.sync permission name vs biometric.device.manage — settle before RBAC metadata lands on sync endpoints.
  4. Analytics wiring waits 00-shared AnalyticsService (proposed).
  5. QR/push check-in stays dormant (forward-looking) — route exists, no UI.