Build and publish a Flutter-based app for the App Store and Google Play — revisited and updated for 2026, where Material 3 is now the default design language and several APIs have changed significantly since this series began.
TL:DR — In this article we add Buttons, Floating Action Button, Chips, Data Table, Dialogs, List View, Popup Menu, and Navigation Drawer.
Contents
Key things to be aware of before reading on:
- Material 3 is the default. You no longer need to opt in with
useMaterial3: true. Opting out is now the unusual choice, and Material 2 support is considered legacy. FlatButtonis gone. It was deprecated and then removed. UseTextButtoninstead — this article already covers the replacement buttons introduced in the "New Button Universe" update, so the code samples remain valid.accentColoris gone. TheThemeData.accentColorproperty referenced in the Floating Action Button section was deprecated and removed. The equivalent in Material 3 theming iscolorScheme.secondary.- Text theme names changed.
headline3,headline6,subtitle2and similar names from the oldTextThemeAPI were replaced. UsedisplaySmall,titleLarge, andbodyMediumrespectively in current Flutter projects. - Navigation Drawer has a dedicated widget. Material 3 introduced
NavigationDraweralongside the existingDrawer/ListViewpattern. The older approach still works but the newer widget gives you M3 styling automatically.
The screenshots in this article reflect the Material 2 era. Your app will look somewhat different today — generally more refined, with better colour-scheme integration — but the structural concepts and most of the code carry over cleanly.
Adding Material Components widgets to our app page
Adding MDC-Flutter widgets to the app remains one of the best ways to learn how they behave. In earlier parts of this series we built an AppBar, MaterialApp, Scaffold, ButtonBar, and TextButton inside a Card. There are many more to explore.
Buttons
There are three main button types in Material 3 Flutter. The names and concepts introduced in the "New Button Universe" update remain current and are now the only supported API.
| A text label in the theme colour that reacts to taps with a colour shift. Use for the least prominent actions. | |
A filled, tonal button that lifts on tap. In Material 3 the default fill uses colorScheme.surface with a tonal overlay. |
|
| A button with a thin border, used for medium-emphasis actions alongside a filled button. |
The "New Button Universe" (PR #59702) landed in Flutter 1.22.0 and replaced the old FlatButton, RaisedButton, and OutlineButton widgets. Those legacy widgets have since been fully removed, so the code below is the only current approach:
// Current button API — works with Material 3 theming
TextButton(
onPressed: () { },
child: const Text('TEXT'),
),
ElevatedButton(
onPressed: () { },
child: const Text('ELEVATED'),
),
OutlinedButton(
onPressed: () { },
child: const Text('OUTLINED'),
),
In Material 3 the buttons pick up colours from ThemeData.colorScheme and adapt automatically for both light and dark appearance. You rarely need to override individual button colours — lean on the colour scheme instead.

Note: these screenshots are from the Material 2 era. In a current Flutter 3.44 project the button shapes and colour roles will reflect the Material 3 defaults.
Floating Action Button
A Floating Action Button (FAB) represents the primary action on a screen. The extended variant combines an icon and a label, which is useful when the action needs a little more context than an icon alone can provide.
One important update: the old accentColor property used to tint the FAB has been removed. In a current project, set colorScheme.secondary (or colorScheme.primaryContainer for the M3 large FAB variant) in your ThemeData instead:
// Current theming approach — accentColor no longer exists
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.indigo,
brightness: Brightness.light,
),
),
darkTheme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.indigo,
brightness: Brightness.dark,
),
),
// Extended FAB — unchanged API
floatingActionButton: FloatingActionButton.extended(
onPressed: () {
// TBD
},
label: const Text('SHARE'),
icon: const Icon(Icons.share),
),

These screenshots predate Material 3. The current FAB uses a rounded container shape by default and derives its colour from the active colour scheme.
Chips
Chips are compact elements that represent an attribute, text, entity, or action. Once you start noticing them you see them everywhere — filter rows in shopping apps, tag lists in note-taking tools, suggestion strips in search bars. The chip API is largely unchanged, though Material 3 gives them updated shapes and colour roles.
ExpansionTile(
leading: const Icon(Icons.folder_special),
title: const Text('Chips'),
subtitle: const Text('Chip, InputChip, ChoiceChip, FilterChip, ActionChip'),
children: [
Chip(
avatar: const CircleAvatar(child: Text('C')),
label: const Text('Chip'),
),
InputChip(
avatar: const CircleAvatar(child: Text('IC')),
label: const Text('Input Chip'),
onPressed: () {
debugPrint('Input Chip pressed.');
},
),
ChoiceChip(
label: const Text('Choice Selected'),
selected: true,
onSelected: (bool selected) { },
),
ChoiceChip(
label: const Text('Choice'),
selected: false,
onSelected: (bool selected) { },
),
ChoiceChip(
label: const Text('Choice Disabled'),
selected: false,
),
FilterChip(
label: const Text('Filter Selected'),
selected: true,
onSelected: (bool selected) { },
),
FilterChip(
label: const Text('Filter'),
selected: false,
onSelected: (bool selected) { },
),
ActionChip(
label: const Text('Action Chip'),
onPressed: () {
debugPrint('Action Chip pressed.');
},
),
],
),

Data Table
Data tables display sets of data across rows and columns. The Flutter documentation still cautions that DataTable is computationally expensive because it must negotiate column widths — worth keeping in mind if you are working with large datasets. For those cases, PaginatedDataTable splits the data across pages and keeps things manageable.
Styling the data table has improved somewhat with Material 3's colour scheme integration, but it remains one of the less flexible components out of the box. The header row picks up theme colours; individual cells inherit the default text style.
ExpansionTile(
leading: const Icon(Icons.table_chart),
title: const Text('Data Tables'),
subtitle: const Text('DataTable, DataColumn, DataRow, DataCell'),
children: [
DataTable(
columns: const [
DataColumn(label: Text('Shape')),
DataColumn(label: Text('Points')),
DataColumn(label: Text('Like')),
],
rows: const [
DataRow(cells: [
DataCell(Text('Triangle')),
DataCell(Text('3')),
DataCell(Text('No')),
]),
DataRow(cells: [
DataCell(Text('Square')),
DataCell(Text('4')),
DataCell(Text('No')),
]),
DataRow(cells: [
DataCell(Text('Octagon')),
DataCell(Text('8')),
DataCell(Text('Yes')),
]),
],
),
],
),

Dark appearance support for DataTable has improved in recent Flutter versions as the colour scheme integration matured — it is worth testing in your own theme rather than assuming it will not adapt.
Dialogs
Two dialog types cover most use cases: AlertDialog for messages with one or more action buttons, and SimpleDialog for presenting a list of options. The API is unchanged, though note that any FlatButton instances inside dialogs from older code must be replaced with TextButton.
ExpansionTile(
leading: const Icon(Icons.error),
title: const Text('Dialogs'),
subtitle: const Text('AlertDialog, SimpleDialog'),
children: [
AlertDialog(
title: const Text('AlertDialog'),
content: const Text('AlertDialog content'),
actions: [
TextButton(
onPressed: () {
debugPrint('Close button pressed.');
},
child: const Text('Close'),
),
],
),
SimpleDialog(
title: const Text('SimpleDialog'),
children: [
SimpleDialogOption(
onPressed: () { debugPrint('Item 1 pressed.'); },
child: const Text('Item 1'),
),
SimpleDialogOption(
onPressed: () { debugPrint('Item 2 pressed.'); },
child: const Text('Item 2'),
),
],
),
],
),

List View
Adding a ListView inside an ExpansionTile requires setting shrinkWrap: true and specifying a scrollDirection, otherwise Flutter cannot resolve the unbounded height constraint. This is the ListView example with dividers from the Flutter documentation, adapted for the expansion tile context.
// Values for the ListView
final List<String> entries = ['A', 'B', 'C'];
final List<int> colorCodes = [600, 500, 100];
ExpansionTile(
leading: const Icon(Icons.list),
title: const Text('ListView'),
subtitle: const Text('ListView with dividers'),
children: [
ListView.separated(
scrollDirection: Axis.vertical,
shrinkWrap: true,
itemCount: entries.length,
itemBuilder: (BuildContext context, int index) {
return Container(
height: 50,
color: Colors.amber[colorCodes[index]],
child: Center(child: Text('Entry ${entries[index]}')),
);
},
separatorBuilder: (BuildContext context, int index) =>
const Divider(),
),
],
),

Popup Menu
Attaching a popup menu to the AppBar actions list is straightforward. A vertical-dots icon (Icons.more_vert) is the conventional trigger and is immediately recognisable to users on both platforms.
PopupMenuButton<String>(
icon: const Icon(Icons.more_vert),
itemBuilder: (context) => [
const PopupMenuItem(
value: 'settings',
child: Text('Settings'),
),
const PopupMenuItem(
value: 'help',
child: Text('Help and Contact Us'),
),
const PopupMenuItem(
value: 'about',
child: Text('About your project'),
),
],
),

Navigation Drawer
Navigation drawers are well understood by users on both Android and iOS, and Flutter makes adding one almost trivially easy. Assigning a drawer to the Scaffold automatically replaces any custom leading widget in the AppBar with the hamburger menu icon — so if you have a branding image there, it will need to move.
The classic Drawer approach
The Drawer + ListView pattern shown below still works perfectly in current Flutter. The text styles referenced in the original article (headline3, headline6, subtitle2) have been renamed in the current TextTheme API — the updated equivalents are used here:
drawer: Drawer(
child: ListView(
children: [
DrawerHeader(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primary,
),
child: Text(
'Your Project',
style: Theme.of(context).textTheme.displaySmall?.copyWith(
color: Theme.of(context).colorScheme.onPrimary,
),
),
),
ListTile(
leading: const Icon(Icons.star_border),
title: Text(
'Outline Star',
style: Theme.of(context).textTheme.titleLarge,
),
trailing: const Icon(Icons.keyboard_arrow_right),
),
ListTile(
leading: const Icon(Icons.star_half),
title: Text(
'Half Star',
style: Theme.of(context).textTheme.titleLarge,
),
trailing: Text(
'39',
style: Theme.of(context).textTheme.bodyMedium,
),
),
ListTile(
leading: const Icon(Icons.star),
title: Text(
'Star',
style: Theme.of(context).textTheme.titleLarge,
),
trailing: const Icon(Icons.keyboard_arrow_right),
),
],
),
),
The Material 3 NavigationDrawer widget
Material 3 introduced a dedicated NavigationDrawer widget that handles selection state, active indicators, and colour roles automatically. If you are building a new app today it is worth considering this over the manual ListView approach:
// Material 3 NavigationDrawer — available in current Flutter
drawer: NavigationDrawer(
onDestinationSelected: (int index) {
// Handle navigation
},
children: const [
Padding(
padding: EdgeInsets.fromLTRB(28, 16, 16, 10),
child: Text('Your Project'),
),
NavigationDrawerDestination(
icon: Icon(Icons.star_border),
label: Text('Outline Star'),
),
NavigationDrawerDestination(
icon: Icon(Icons.star_half),
label: Text('Half Star'),
),
NavigationDrawerDestination(
icon: Icon(Icons.star),
label: Text('Star'),
),
],
),


The drawer is still a little bare at this stage — branding and real navigation targets will come in a later part of the series.
Where things stand
We have now added the core Material Components to our app page. Most of the widget APIs are stable and have carried forward cleanly from Flutter 1.x through to Flutter 3.44, with the main breaking changes concentrated in theming (accentColor → colorScheme.secondary), button names (FlatButton → TextButton), and text theme property names. If you are picking up an older Flutter project, those three areas are where you are most likely to find deprecation warnings.
In the next article we will add the remaining components and then start turning this demonstration scaffold into something that functions as a real app. With Material 3 as the baseline, the theming work in particular becomes considerably more powerful — ColorScheme.fromSeed alone can take a single brand colour and derive a complete, accessible, dark-and-light palette with no manual tuning required.