Integrate theme builder at MaterialApp level
epic-visual-design-accessibility-theme-integration-task-008 — Wire the ThemeBuilder Riverpod provider into the root MaterialApp widget so that the app.theme and app.darkTheme properties are always sourced from the builder output. Ensure the active organisation context drives the branding override selection, and that theme switches (light/dark mode toggle, org context change) propagate correctly through the widget tree without requiring hot restart.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 7 - 84 tasks
Can start after Tier 6 completes
Handles integration between different epics or system components. Requires coordination across multiple development streams.
Implementation Notes
Use a ConsumerWidget (or HookConsumerWidget if the project uses flutter_hooks) at the app root rather than Consumer inside MaterialApp — this ensures the entire MaterialApp rebuilds when the theme changes, which is required for ThemeData to propagate correctly. Watch the ThemeBuilder provider with ref.watch, not ref.read, so reactive rebuilds are triggered. The org context should be a separate Riverpod provider (e.g., activeOrganisationProvider) that the ThemeBuilder provider depends on via ref.watch — this creates a clean reactive chain: org change → ThemeBuilder re-runs → MaterialApp rebuilds. Avoid storing ThemeData in BLoC state; keep it in Riverpod so it integrates cleanly with Flutter's Theme inheritance.
If the org context is currently in BLoC, create a bridge provider using StreamProvider that listens to the BLoC stream. Persist the active org ID using a secure local key (e.g., via flutter_secure_storage) and restore it in the provider's build method so the theme is correct on cold start without a frame of default theme. Be careful with MediaQuery for system brightness — use ref.watch(brightnessProvider) derived from PlatformDispatcher rather than reading MediaQuery inside the provider, which can cause context issues.
Testing Requirements
Unit tests: verify that the root widget reads ThemeBuilder provider output for both theme and darkTheme properties; verify that an org context change causes the provider to emit updated ThemeData. Widget tests: mount the root app with each of the four org configs and assert that Theme.of(context).colorScheme matches the expected values; simulate a light/dark toggle and assert the theme switches without rebuilding the full tree; simulate an org switch mid-session and assert the new branding is applied. Integration tests: cold-start the app with a persisted org context and verify the correct theme is applied before user interaction. Accessibility tests: run Flutter's SemanticsChecker after each theme change to confirm no accessibility regressions.
All tests must pass on both iOS and Android targets.
One or more of the four partner organisations may supply brand primary colors that cannot be paired with any standard foreground at 4.5:1 contrast (for example, a mid-range hue that is too light for dark text and too dark for white text). Rejecting these colors programmatically could cause a political dispute with the organisation and delay the feature.
Mitigation & Contingency
Mitigation: Before implementation begins, run all four organisations' existing brand primary colors through the contrast-ratio-validator against both white (#FFFFFF) and a near-black (#1A1A1A). Share the results with each organisation's contact person ahead of the theme builder sprint so any problematic colors can be adjusted collaboratively with advance notice.
Contingency: If an organisation insists on a non-compliant brand color, produce a compliant near-match (lightened or darkened along the hue's luminance axis) and present both options with contrast ratio evidence. Document the adjusted token in the manifest with an explicit note that the original brand color was non-compliant, and obtain written sign-off from the organisation.
Flutter's ThemeData contains over 30 component theme properties. If the theme-builder only addresses the most common ones (Button, InputDecoration, Card) and leaves others at Flutter defaults, downstream feature teams may unknowingly use default-themed widgets that do not meet sizing or contrast requirements.
Mitigation & Contingency
Mitigation: Produce a full inventory of all ThemeData component theme properties and map each to either a token-driven override or an explicit pass-through decision documented in the theme builder code. Prioritise the inventory by frequency of use in the existing codebase (identified via Grep). Include a check in the CI lint runner that flags widgets using Flutter default component themes not covered by the theme builder.
Contingency: If the full inventory scope exceeds the sprint budget, ship with the highest-frequency components covered and add a tracked backlog item for each uncovered component theme, pairing with a temporary lint suppression comment that includes the backlog reference.