App Scaffolding
MaterialApp at the root
Section titled “MaterialApp at the root”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(), ); }}Scaffold: the standard screen frame
Section titled “Scaffold: the standard screen frame”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(),);Theming, read from context
Section titled “Theming, read from context”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.
Responsive and adaptive layout
Section titled “Responsive and adaptive layout”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).sizegives the screen dimensions — useful for global breakpoints.LayoutBuildergives 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"]