Dynamic Type Scale Service
Component Detail
Description
Manages the relationship between OS-level dynamic type settings and the app's text scale factor. Computes safe per-role scale caps so that scaling up to the OS maximum does not break form layouts or truncate critical UI labels, while still respecting the user's preference.
dynamic-type-scale-service
Summaries
The Dynamic Type Scale Service ensures the application remains fully usable when users increase their device text size — a setting heavily relied upon by older adults and users with low vision, representing a large and growing demographic segment. By intelligently capping scale factors per text role, the service prevents layouts from breaking at large text sizes, directly avoiding a common source of one-star reviews, accessibility complaints, and app store demotion risks. Supporting this OS preference signals genuine inclusivity, broadens the addressable user base, and reduces the need for expensive post-launch layout remediation triggered by accessibility audits or regulatory action.
Medium-complexity mobile service with a single dependency on design-token-provider. A critical early requirement is aligning with design and UX on the maximum safe scale cap for each text role (headline, body, label, caption) — this is a design decision with direct engineering implications that must be resolved before implementation begins to avoid rework across form layouts. The listener registration pattern (`registerScaleChangeListener`, `removeScaleChangeListener`) requires lifecycle management in consuming widgets to prevent memory leaks; include this as an explicit acceptance criterion in the story definition. Testing must cover the full OS textScaleFactor range including edge cases at the maximum OS scale on the smallest supported screen dimensions, where overflow risk is highest.
Wraps Flutter's `MediaQuery.textScaleFactorOf(context)` with a per-role capping policy driven by design-token-provider config. `getEffectiveScale(TextRole, double osScale)` returns `min(osScale, getMaxScale(role))` — max scales are read from tokens, not hardcoded, so design can adjust caps without code changes. The listener system should be implemented as a ChangeNotifier for testability, with `registerScaleChangeListener` and `removeScaleChangeListener` managing a VoidCallback list. Consuming widgets must call register in `initState` and remove in `dispose` to prevent memory leaks — add a debug assertion that fires if a listener is registered without a corresponding remove.
`isScaleAccessible(double scale)` validates the capped effective scale still meets WCAG minimum size after capping. Primary consumer is accessible-text-style-system's `applyDynamicTypeScale()` — keep the interface contract stable as changes there cascade to all text rendering.
Responsibilities
- Read OS textScaleFactor and map it to per-role layout budgets
- Define maximum safe scale per text role (headline, body, label, caption)
- Notify dependent widgets when scale factor changes
- Ensure layouts reflow rather than clip or overflow on large scales
Interfaces
getEffectiveScale(TextRole role, double osScale) → double
registerScaleChangeListener(VoidCallback listener)
removeScaleChangeListener(VoidCallback listener)
getMaxScale(TextRole role) → double
isScaleAccessible(double scale) → bool
Relationships
Related Data Entities (1)
Data entities managed by this component