skip to content

In Flutter, what do constructors like Switch.adaptive, Slider.adaptive and CircularProgressIndicator.adaptive do, and how do they decide which look to render?

level: middleimportance: should knowfreq 42%

answer

  1. one widget, two looks
  2. iOS and macOS get the Cupertino look
  3. decided by ThemeData.platform
  4. some parameters ignored on Apple platforms
  5. test with ThemeData(platform: TargetPlatform.iOS)

basics

~10 s

They render the Cupertino look when Theme.of(context).platform is iOS or macOS and the Material widget elsewhere. The platform comes from ThemeData.platform, which defaults to defaultTargetPlatform, and some Material-only parameters are ignored on Apple platforms.

solid answer

~30 s

The `.adaptive` constructors on Material widgets check `Theme.of(context).platform`, which defaults to `defaultTargetPlatform`. On `TargetPlatform.iOS` and `macOS`, `Slider.adaptive` builds a `CupertinoSlider`, `CircularProgressIndicator.adaptive` builds a `CupertinoActivityIndicator`, `Checkbox.adaptive` a `CupertinoCheckbox`, and `Switch.adaptive` renders an iOS-style switch; on Android, Linux, Windows and Fuchsia they are the ordinary Material widgets. `AlertDialog.adaptive` and `showAdaptiveDialog` do the same for dialogs. Parameters with no Cupertino meaning are ignored on Apple platforms, for example `label` on the slider or `strokeWidth` and `semanticsLabel` on the progress indicator. To see the iOS look on an Android device or in a test, set `ThemeData(platform: TargetPlatform.iOS)`. For adaptive-only styling, `ThemeData.adaptations` accepts an `Adaptation<SwitchThemeData>`.

go deeper

for a junior

Know that .adaptive gives an iOS-style control on Apple platforms and a Material one elsewhere, for switches, sliders, spinners and dialogs.

for a middle

Explain that the decision reads ThemeData.platform, covers iOS and macOS, and that some parameters are ignored in the Cupertino path.

for a senior

Show how you test both looks from one CI host, theme adaptive widgets with Adaptation, and wrap missing cases in your own platform-aware widgets.

for a principal

Judge how far per-control adaptation should go before the app needs a deliberate one-look or fully adaptive strategy.

## What an adaptive constructor is Several Material widgets have a named constructor `.adaptive` that produces **the Material widget on non-Apple platforms and an iOS-style widget on Apple platforms**. They let one widget tree feel native on iPhone and on Android for the handful of controls where users notice the difference most: toggles, sliders, spinners, checkboxes and alert dialogs. Available in Flutter 3.47 include: - `Switch.adaptive` and `SwitchListTile.adaptive` - `Slider.adaptive` - `CircularProgressIndicator.adaptive` - `Checkbox.adaptive` and `Radio.adaptive` - `RefreshIndicator.adaptive` - `AlertDialog.adaptive`, together with the function `showAdaptiveDialog` ## How the platform is decided The decision is **not** made by asking the operating system at the call site. Each adaptive widget reads **`Theme.of(context).platform`**, the `platform` field of the active `ThemeData`. When you do not set it, `ThemeData` fills it with `defaultTargetPlatform`, which reflects the real device (or `debugDefaultTargetPlatformOverride` in debug builds). 1. `TargetPlatform.iOS` or `TargetPlatform.macOS` selects the Cupertino look. 2. `TargetPlatform.android`, `fuchsia`, `linux` and `windows` select the Material look. 3. Setting `ThemeData(platform: TargetPlatform.iOS)` therefore forces the iOS look everywhere below that theme, which is how you preview it on an Android emulator or in a widget test. Web follows the same rule: `defaultTargetPlatform` on the web reports the platform of the browser's host, so Safari on an iPhone gets the iOS look. ## What each one renders | Constructor | On iOS and macOS | Ignored there (examples) | |---|---|---| | `Slider.adaptive` | `CupertinoSlider` | `label`, `inactiveColor`, `secondaryTrackValue` | | `CircularProgressIndicator.adaptive` | `CupertinoActivityIndicator` | `strokeWidth`, `valueColor`, `semanticsLabel` | | `Checkbox.adaptive` | `CupertinoCheckbox` | Material-only visuals | | `Switch.adaptive` | iOS-style switch (51 x 31 track) | Material thumb icons behave differently | | `showAdaptiveDialog` | behaves like `showCupertinoDialog` | `barrierColor`, `useSafeArea` | Two details from the source are worth knowing. `CircularProgressIndicator.adaptive` passes its `backgroundColor` as the Cupertino indicator's tick color, and a non-null `value` produces a partially revealed activity indicator rather than a determinate ring. And because `semanticsLabel` is ignored on Apple platforms, screen-reader text must be supplied another way if the design depends on it. ## Theming adaptive widgets separately A Material `switchTheme` would normally apply to the adaptive switch on iOS too, which can make it look wrong there. `ThemeData.adaptations` takes a list of **`Adaptation<T>`** subclasses; `Switch.adaptive` asks the theme for an `Adaptation<SwitchThemeData>` and lets it adjust the switch theme only in its adaptive path. That keeps the Android `switchTheme` from leaking into the iOS rendering. ## An example ```dart import 'package:flutter/material.dart'; class CellarSettings extends StatelessWidget { const CellarSettings({super.key, required this.notify, required this.onNotify}); final bool notify; final ValueChanged<bool> onNotify; @override Widget build(BuildContext context) { return SwitchListTile.adaptive( title: const Text('Drink-by reminders'), value: notify, onChanged: onNotify, ); } } ``` On an iPhone this row shows an iOS-style switch; on Android, a Material 3 switch. The rest of the row, the `ListTile` layout, is identical on both, which is exactly the scope of an adaptive constructor. ## Where adaptive constructors stop Adaptive constructors cover individual controls, not whole screens. They do not: - turn a `Scaffold` into a `CupertinoPageScaffold`, or an `AppBar` into a `CupertinoNavigationBar`; - change navigation structure, typography or spacing; - exist for every widget (there is no adaptive `TextField`, `ListTile` or `AppBar`). For those, you choose explicitly, usually with a switch on `Theme.of(context).platform` in a small widget of your own, or you decide the app keeps one look everywhere. ## Common mistakes - Branching on `Platform.isIOS` from `dart:io`, which breaks on the web and ignores a `ThemeData.platform` override that tests rely on. - Styling the adaptive switch through `switchTheme` and being surprised on iPhone, instead of using an adaptation. - Relying on `semanticsLabel` of an adaptive progress indicator for accessibility on iOS. - Assuming macOS gets the Material look; it gets the Cupertino one.

  • In a Flutter widget test running on a Linux CI machine, how do you verify that Switch.adaptive renders its iOS style?
    Pump the widget under `MaterialApp(theme: ThemeData(platform: TargetPlatform.iOS), ...)`, or set `debugDefaultTargetPlatformOverride` and reset it at the end of the test. The adaptive switch reads `Theme.of(context).platform`, so the test host's real platform does not matter.
  • In Flutter, why prefer Theme.of(context).platform over Platform.isIOS when writing your own adaptive widget?
    `Platform` from `dart:io` is unavailable on the web and cannot be overridden in tests or previews. `Theme.of(context).platform` is what Flutter's own adaptive constructors use, works on every target, and honours a `ThemeData(platform: ...)` override, so your widget and Flutter's switch in the same form agree.
  • In Flutter, what does CircularProgressIndicator.adaptive show on iOS when you pass value: 0.6?
    A `CupertinoActivityIndicator.partiallyRevealed` indicator, not a 60 percent ring. iOS has no determinate circular spinner, so the Cupertino path approximates progress by revealing part of the activity indicator's ticks.

An adaptive constructor is like a hotel power socket that accepts both plug shapes: you bring one device, and the socket decides which contacts to use based on the country sign on the wall (ThemeData.platform), not on where the device was made.

saying these in an interview costs you the question

  • Believing adaptive constructors call the native iOS UISwitch or UISlider
  • Checking Platform.isIOS instead of Theme.of(context).platform
  • Assuming macOS gets the Material look from adaptive constructors
  • Expecting semanticsLabel on CircularProgressIndicator.adaptive to reach VoiceOver on iOS
  • Thinking an adaptive Scaffold or AppBar constructor exists