critical priority low complexity infrastructure pending infrastructure specialist Tier 2

Acceptance Criteria

A Riverpod provider named localStorageAdapterProvider (or matching codebase naming convention) is defined and exported from the infrastructure layer
The provider uses FutureProvider<LocalStorageAdapter> or Provider<LocalStorageAdapter> depending on whether SharedPreferences initialization is async — if async, use FutureProvider and document why
SharedPreferences.getInstance() is called once within the provider body and its result is passed to the LocalStorageAdapter constructor
The provider is scoped appropriately (not unnecessarily scoped to a subtree) — available at root ProviderScope unless there is a documented reason for narrower scope
Consuming widgets/services can read the adapter via ref.watch(localStorageAdapterProvider) or ref.read(localStorageAdapterProvider) without additional setup
Provider is overridable in tests via ProviderScope(overrides: [localStorageAdapterProvider.overrideWithValue(mockAdapter)])
The provider file follows the codebase's file naming and directory placement conventions for Riverpod providers
dart analyze reports no warnings on the provider file

Technical Requirements

frameworks
Flutter
Riverpod
shared_preferences
performance requirements
SharedPreferences.getInstance() is called exactly once per app lifetime — the provider must be kept-alive (not auto-disposed) to prevent redundant initialization
security requirements
Provider must not expose the underlying SharedPreferences instance directly — only the typed LocalStorageAdapter is exposed

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

If the codebase uses code generation (riverpod_generator with @riverpod annotations), follow that pattern exactly — do not introduce a manual Provider if the codebase uses generated providers. If using FutureProvider (because SharedPreferences.getInstance() is async), consumers of this provider will need to handle the AsyncValue — consider whether downstream services should await the FutureProvider or whether the app's startup sequence (in main.dart or a bootstrap function) should pre-initialize SharedPreferences and provide it synchronously via ProviderScope overrides. The latter approach (pre-init in main()) is recommended for avoiding AsyncValue boilerplate throughout the app. Check how other infrastructure providers (e.g., Supabase client provider) handle async initialization and match that pattern.

Add a // ignore: avoid_manual_providers comment only if truly necessary — prefer code-gen conventions.

Testing Requirements

Write one widget test that: (1) creates a ProviderContainer with a mock LocalStorageAdapter override; (2) reads localStorageAdapterProvider and asserts it returns the mock instance. This verifies that the provider is overridable for downstream tests. Additionally, verify in a separate test that the real provider (using SharedPreferences.setMockInitialValues) returns a non-null LocalStorageAdapter instance.

No integration tests against a real device are needed for this task.

Component
Local Storage Adapter
infrastructure low
Epic Risks (2)
medium impact medium prob technical

SharedPreferences behaves differently on iOS (NSUserDefaults) vs Android (SharedPreferences) for edge cases such as first-launch cold reads, storage quota exceeded, or process kill mid-write. If the adapter does not account for these differences, the persistence layer can silently return null on one platform while returning a stale value on the other, causing incorrect routing decisions downstream.

Mitigation & Contingency

Mitigation: Write platform-specific integration tests using flutter_test device runners for both iOS and Android. Document known platform delta in the adapter's inline comments and encode defensive fallback for null returns at the repository boundary.

Contingency: If platform delta causes persistent issues, replace SharedPreferences with flutter_secure_storage for this key — the LocalStorageAdapter abstraction makes this a single-file swap with no impact on the repository or service layer.

high impact low prob dependency

The shared_preferences Flutter plugin may have a version conflict with other plugins already in the project pubspec.yaml. A conflict discovered late in the epic blocks all downstream epics.

Mitigation & Contingency

Mitigation: Resolve and pin the shared_preferences version in pubspec.yaml as the very first task of this epic before writing any implementation code. Run flutter pub get and resolve any version conflicts immediately.

Contingency: If version pinning is impossible due to transitive conflicts, implement LocalStorageAdapter using path_provider + dart:io for JSON file storage as an alternative — the interface contract remains unchanged.