Skip to content

App Scaffolding

MaterialApp is the widget you wrap your whole app in. It provides the things every screen needs: the theme, the routing config, localization, and the Navigator/Overlay machinery. There is a CupertinoApp sibling for an iOS-styled app, and WidgetsApp if you want neither design language.

void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue)),
darkTheme: ThemeData.dark(),
themeMode: ThemeMode.system, // follow the OS setting
home: const HomeScreen(),
);
}
}

Inside each route, a Scaffold gives you the conventional Material layout slots — an app bar, a body, a floating action button, a bottom navigation bar, and a drawer. It also handles overlaps like the on-screen keyboard for you.

Scaffold(
appBar: AppBar(title: const Text('Home')),
body: const CenteredContent(),
floatingActionButton: FloatingActionButton(onPressed: () {}, child: const Icon(Icons.add)),
bottomNavigationBar: const MyNavBar(),
drawer: const MyDrawer(),
);

A theme is defined once on MaterialApp and read anywhere below with Theme.of(context). Modern Material 3 apps drive their whole palette from a single seed color via ColorScheme.fromSeed, so light and dark stay consistent.

// Read theme values instead of hardcoding colors:
final scheme = Theme.of(context).colorScheme;
Container(color: scheme.primary);
Text('Title', style: Theme.of(context).textTheme.titleLarge);

Reading from the theme (rather than hardcoding a Color) is what makes dark mode and rebranding a one-line change.

The same app runs on a phone, a foldable, a tablet, and the web, so a screen must adapt to the space it is given.

  • MediaQuery.of(context).size gives the screen dimensions — useful for global breakpoints.
  • LayoutBuilder gives the constraints of the specific slot a widget occupies — better for local decisions, because it reacts to the actual available space, not the whole screen.
LayoutBuilder(
builder: (context, constraints) {
// Switch layout based on available width at THIS position.
if (constraints.maxWidth > 600) {
return const TwoPaneLayout(); // tablet / desktop
}
return const SinglePaneLayout(); // phone
},
);
flowchart LR
  lb["LayoutBuilder
(constraints of this slot)"] --> check{"maxWidth over 600?"}
  check -->|yes| two["two-pane layout"]
  check -->|no| one["single-pane layout"]
Adapt layout to the space you are given
What does MaterialApp provide to the whole app?
What is a Scaffold?
Why read colors from `Theme.of(context)` instead of hardcoding them?
When is LayoutBuilder preferable to MediaQuery for layout decisions?