Implement ActivityTypeCacheProvider with TTL and invalidation
epic-activity-type-configuration-business-logic-task-003 — Build an in-memory cache layer wrapping the repository. Cache active activity types per orgId with a configurable TTL (default 10 minutes). Expose getActiveTypes(orgId) returning cached or freshly fetched types, and invalidate(orgId) to clear cache entries after mutations. Use Riverpod StateNotifier for reactive cache state management.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 2 - 518 tasks
Can start after Tier 1 completes
Implementation Notes
Place in lib/features/activity_type/data/cache/activity_type_cache_notifier.dart. Use a private _CacheEntry record/class with List
For Riverpod, use StateNotifierProvider>. Wire invalidateAll() to the Riverpod auth state change listener (log out event) to ensure clean state. Do not persist the cache to disk — it is purely in-memory for the session lifetime.
Testing Requirements
Unit tests using flutter_test with a mocked IActivityTypeRepository: (1) cache hit: getActiveTypes called twice within TTL invokes fetchActiveByOrg exactly once, (2) cache miss after TTL: mock DateTime injection confirms stale entry triggers re-fetch, (3) invalidate: after invalidate(orgId), next getActiveTypes calls fetchActiveByOrg, (4) invalidateAll: clears all orgs' entries, (5) different orgIds maintain separate cache entries — org A invalidation does not affect org B, (6) repository error in getActiveTypes propagates without caching a null/empty result, (7) concurrent calls (using Future.wait) do not result in double-fetching (optional but recommended). Use fake/mock clock injection for TTL tests to avoid real timer waits.
Metadata flag combination rules differ between organisations (e.g., Blindeforbundet honorarium thresholds, HLF mutual exclusion of km and transit). Encoding these as generic service-level validation may be insufficient, forcing organisation-specific branching inside the service that becomes unmaintainable as new organisations are onboarded.
Mitigation & Contingency
Mitigation: Model flag validation as a pure function that accepts an ActivityTypeMetadata object and an org configuration record, making org-specific rules data-driven rather than hardcoded. Establish the validation contract in the foundation epic so the service just delegates to the validator.
Contingency: Defer complex cross-flag validation to a lightweight edge function that can be updated without a mobile app release, accepting that initial validation in the mobile service layer is permissive and corrected server-side.
Blindeforbundet users rely on VoiceOver and JAWS. If the selection screen is built with non-semantic widgets that fail accessibility audit late in the sprint, a significant rework of the widget tree may be required, blocking the registration wizard integration.
Mitigation & Contingency
Mitigation: Build the selection screen against the project's established accessibility design tokens and semantics wrapper conventions from the start. Run Flutter's semantic tree inspector and a manual VoiceOver pass before marking any widget task complete.
Contingency: Wrap all tappable items in the project's SemanticsWrapperWidget and schedule a dedicated accessibility review session with a screen reader user from Blindeforbundet before the epic is closed.