In Flutter, what do CupertinoApp, CupertinoPageScaffold and CupertinoNavigationBar give you compared with MaterialApp, Scaffold and AppBar?
answer
- iOS look from Flutter's own widgets
- CupertinoThemeData instead of ThemeData
- CupertinoPageRoute for plain routes
- middle, leading, trailing, previousPageTitle
- Material widgets need a Material ancestor
basics
~20 sThey are Flutter's own iOS-style counterparts: CupertinoApp sets up navigation, a CupertinoThemeData and iOS page routes; CupertinoPageScaffold lays out a page under an optional navigation bar; CupertinoNavigationBar draws the iOS bar with leading, middle and trailing slots.
solid answer
~40 s`CupertinoApp` is the iOS-styled app root: it wires a `Navigator`, localizations and a `CupertinoTheme` from `CupertinoThemeData`, and builds `CupertinoPageRoute`s for `home` and `routes`. `CupertinoPageScaffold` is much thinner than `Scaffold`: a `navigationBar`, a `child`, a `backgroundColor` and `resizeToAvoidBottomInset`, with no drawer, FAB or snack bars. `CupertinoNavigationBar` takes `leading`, `middle` and `trailing`, can show a back button labelled with `previousPageTitle`, and by default is translucent with a background blur. These are Flutter widgets painted by Flutter, not wrappers around UIKit, so they look the same on every platform you run them on. Many Material widgets, such as `ListTile` or `TextField`, need a `Material` ancestor or `MaterialLocalizations`, so under a bare `CupertinoApp` you use Cupertino equivalents like `CupertinoListTile` and `CupertinoTextField`.
code
dart · 36 linesimport 'package:flutter/cupertino.dart';
void main() => runApp(const CellarApp());
class CellarApp extends StatelessWidget {
const CellarApp({super.key});
@override
Widget build(BuildContext context) {
return const CupertinoApp(
theme: CupertinoThemeData(primaryColor: CupertinoColors.systemRed),
home: CellarPage(),
);
}
}
class CellarPage extends StatelessWidget {
const CellarPage({super.key});
@override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: const Text('My Cellar'),
trailing: CupertinoButton(
padding: EdgeInsets.zero,
onPressed: () {},
child: const Icon(CupertinoIcons.add),
),
),
child: const SafeArea(
child: Center(child: Text('42 bottles')),
),
);
}
}go deeper
Recall the three widgets, that CupertinoApp takes a CupertinoThemeData, and the leading, middle and trailing slots of the navigation bar.
Explain what CupertinoPageScaffold lacks compared with Scaffold, the translucent bar, and why Material widgets fail under a bare CupertinoApp.
Discuss how you mix Cupertino and Material widgets safely, and which one owns the app root when a product needs both.
Relate these widgets to the broader choice between an iOS-styled app and a single brand look, and what each costs in maintenance.
## Two widget libraries, one engine Flutter ships two design libraries on top of its core widgets: **Material** (`package:flutter/material.dart`) and **Cupertino** (`package:flutter/cupertino.dart`, also published as the standalone `cupertino_ui` package since Flutter 3.47). Cupertino implements Apple's Human Interface Guidelines look. Crucially, both are drawn by Flutter itself: a `CupertinoNavigationBar` is a Flutter widget that paints an iOS-style bar, not a `UINavigationBar`. That is why it renders identically on Android, and why platform behaviours like the back-swipe are reimplemented rather than inherited. ## CupertinoApp `CupertinoApp` plays the role of `MaterialApp` for an iOS-styled app: - It builds a `Navigator` and, for `home` and the `routes` table, creates **`CupertinoPageRoute`** pages with the iOS slide transition. - It takes a **`CupertinoThemeData`** through `theme`, not a Material `ThemeData`. The main fields are `primaryColor`, `primaryContrastingColor`, `barBackgroundColor`, `scaffoldBackgroundColor`, `textTheme` and `brightness`. - It has no `darkTheme` or `themeMode`. When `CupertinoThemeData.brightness` is null, Cupertino widgets follow the platform brightness, and system colors such as `CupertinoColors.systemBackground` are `CupertinoDynamicColor`s that resolve to their light or dark variant automatically. ## CupertinoPageScaffold Its constructor is short: `navigationBar`, `backgroundColor`, `resizeToAvoidBottomInset` (default `true`) and a required `child`. Compared with `Scaffold`: | Feature | `Scaffold` | `CupertinoPageScaffold` | |---|---|---| | Top bar | `appBar` (`PreferredSizeWidget`) | `navigationBar` (`ObstructingPreferredSizeWidget`) | | Drawer, FAB, bottom sheet | Yes | No | | Snack bars | Through `ScaffoldMessenger` | No | | Content under a translucent bar | Only with `extendBodyBehindAppBar` | Yes, by default | | Tabs | Put a `NavigationBar` in `bottomNavigationBar` | Use `CupertinoTabScaffold` | The last difference matters: the default Cupertino bar background is slightly translucent, so the scaffold lets the `child` draw behind it and reports the overlap through `MediaQuery` padding instead of pushing content down. ## CupertinoNavigationBar The bar has three slots and several iOS behaviours built in: - **`leading`**, **`middle`** and **`trailing`** widgets. `automaticallyImplyLeading` (default `true`) adds a back chevron when the route can pop, and **`previousPageTitle`** labels it with the previous page's title, as iOS does. - **`backgroundColor`** defaults to the theme's `barBackgroundColor`, which is translucent; `enableBackgroundFilterBlur` (default `true`) blurs what scrolls beneath. - **`transitionBetweenRoutes`** (default `true`) animates the bar's contents between pages with a hero-like transition. - A **`CupertinoNavigationBar.large`** constructor and the sliver version, `CupertinoSliverNavigationBar`, provide the iOS large title. Text in the bar does not scale with the system text size by default, matching native iOS behaviour. ## CupertinoButton `CupertinoButton` is the iOS text-style button: it fades to `pressedOpacity` (default `0.4`) while pressed instead of showing a ripple. `CupertinoButton.filled` and `CupertinoButton.tinted` give the filled and tinted styles, `sizeStyle` (default `CupertinoButtonSize.large`) picks small, medium or large metrics, and a null `onPressed` (with a null `onLongPress`) disables it. `minSize` is deprecated in favour of `minimumSize`. ## Choosing Cupertino widgets for an iOS-first app For an iOS-first wine-cellar app, the Cupertino set covers most screens: `CupertinoListSection` and `CupertinoListTile` for grouped settings-style lists, `CupertinoTextField` and `CupertinoSearchTextField` for input, `CupertinoSegmentedControl` or `CupertinoSlidingSegmentedControl` for filters, `CupertinoActionSheet` and `CupertinoAlertDialog` for choices, `CupertinoDatePicker` and `CupertinoPicker` for vintages and dates, and `CupertinoTabScaffold` with `CupertinoTabBar` for top-level tabs. Where the set has no counterpart, such as a data table or a chip, you either compose your own widget from core widgets or wrap a Material widget in a `Material` surface. Knowing these names is what interviewers look for at this level: they want to hear that the Cupertino library is a complete kit rather than a handful of look-alikes. ## Mixing Material widgets into a Cupertino app A bare `CupertinoApp` provides no `Material` widget and no `MaterialLocalizations`. Material widgets that depend on them, such as `ListTile`, `TextField` or `InkWell`, fail in debug mode with errors like *No Material widget found* or *No MaterialLocalizations found*. Either use Cupertino counterparts (`CupertinoListTile`, `CupertinoTextField`, `CupertinoListSection`) or wrap the subtree in a `Material` widget and add the Material localizations delegates. In the other direction, Cupertino widgets work fine under `MaterialApp`, because the Material `Theme` provides a `CupertinoTheme` derived from the Material theme, adjustable through `ThemeData.cupertinoOverrideTheme`.
- In Flutter, why does a ListTile placed under a bare CupertinoApp throw in debug mode?`ListTile` asserts that a `Material` ancestor exists, because its ink effects paint on one, and several Material widgets also need `MaterialLocalizations`. `CupertinoApp` provides neither. Use `CupertinoListTile`, or wrap the subtree in `Material` and add the Material localizations delegates to `CupertinoApp`.
- In Flutter, how do Cupertino widgets under a MaterialApp pick up the app's colors?The Material `Theme` widget provides a `CupertinoTheme` for descendants, derived from the Material `ThemeData` when no `CupertinoTheme` is above it. You can override the derived values with `ThemeData.cupertinoOverrideTheme`, or wrap a subtree in its own `CupertinoTheme`.
saying these in an interview costs you the question
- Saying CupertinoNavigationBar wraps the native UINavigationBar
- Passing a Material ThemeData to CupertinoApp's theme parameter
- Expecting CupertinoPageScaffold to offer a drawer or floating action button
- Using ListTile and TextField under a bare CupertinoApp without a Material ancestor
- Expecting CupertinoApp to have darkTheme and themeMode like MaterialApp