This project is an attempt to see if it is possible to create widgets that are platform aware. Currently in order to render targeted Material or Cupertino device specific styles, you need to either conditionally check the platform or create a set of widgets to render differently depending on the running platform.
This package supports the Stable release as a full released version.
Beta or Dev channels might be supported when there is a pre-release version. Please check the CHANGELOG for version compatibility version.
Due to Master being in rapid development this package is unable to support Master. If this support is required then it is best to fork the repo and locally reference the forked version where changes can be made appropriately.
The flutter ThemeData object used with the Theme widget has a platform property. This defaults to TargetPlatform.android on Android and TargetPlatform.ios on iOS (also for macos etc), but when creating a ThemeData object, it can be set programmatically. Calling Theme.of(context).platform will return the current platform. Several Flutter library widgets use this field to change how they are rendered, and all of the Flutter Platform Widgets library widgets use this field to render the platform specific versions of things.
See PlatformProvider for configuration options.
These set of widgets allow for rendering based on the target platform using a single cross platform set of widget.
Each PlatformWidget provides common properties directly as constructor arguments. If required further customization can be achieved by using the platform widget builder. See the Enhance section of each widget.
- PlatformWidget
- PlatformText
- PlatformSwitch
- PlatformSlider
- [PlatformTextField](https://github.com/stryder-dev/flutter_platform_widgets/blob/master/README.md#platformtextfield
- PlatformIconButton
- PlatformApp
- PlatformScaffold
- PlatformTabScaffold
- PlatformAppBar
- PlatformNavBar
- PlatformPopupMenu
- PlatformAlertDialog
- PlatformDialogAction
- PlatformCircularProgressIndicator
- PlatformPageRoute
- PlatformPage
- ShowPlatformModalSheet
- ShowPlatformDatePicker
- PlatformProvider
- PlatformThemeData
- PlatformIcons
- PlatformWidgetBuilder
A widget that will render either the material widget or cupertino widget based on the target platform. The widgets themselves do not need to be specifically Material or Cupertino.
return PlatformWidget(
cupertino: (_, __) => Icon(CupertinoIcons.flag),
material: (_, __) => Icon(Icons.flag),
);A widget that will render uppercase for material. Cupertino will remain unchanged.
return PlatformText('Cancel');A switch widget that will use a Switch for material or a CupertinoSwitch for cupertino.
return PlatformSwitch(
onChanged: (bool value) {},
value: value,
);return PlatformSwitch(
onChanged: (bool value) {},
value: value,
material: (_, __) => MaterialSwitchData(...),
cupertino: (_, __) => CupertinoSwitchData(...)
);A slider widget that will use a Slider for material or a CupertinoSlider for cupertino
return PlatformSlider(
onChanged: (bool value) {},
value: value,
);return PlatformSlider(
onChanged: (bool value) {},
value: value,
material: (_, __) => MaterialSliderData(...),
cupertino: (_, __) => CupertinoSliderData(...)
);A text field widget that will use a TextField for material or a CupertinoTextField for cupertino.
return PlatformTextField();return PlatformTextField(
material: (_, __) => MaterialTextFieldData(...),
cupertino: (_, __) => CupertinoTextFieldData(...)
);A clickable (tappable) button with an icon. Uses IconButton for material or CupertinoButton for cupertino.
return PlatformIconButton(
onPressed: () => print('info pressed'),
materialIcon: Icon(Icons.info),
cupertinoIcon: Icon(
CupertinoIcons.info,
size: 28.0,
),
);Extend with PlatformBuilder for material or cupertino.
Widget infoIconButton() {
return PlatformIconButton(
onPressed: () => print('info pressed'),
materialIcon: Icon(Icons.info),
cupertinoIcon: Icon(CupertinoIcons.info),
material: (_, __) => MaterialIconButtonData(...),
cupertino: (_, __) => CupertinoIconButtonData(...),
);
}A top level widget for the application that uses MaterialApp for material or CupertinoApp for cupertino.
return PlatformApp(
title: 'Flutter Demo',
home: ...
);or
return PlatformApp.router(
routeInformationParser: ...
routerDelegate: ...
)Extend with PlatformBuilder for material or cupertino.
return PlatformApp(
home: ...
material: (_, __) => MaterialAppData(...)
cupertino: (_, __) => CupertinoAppData(...)
);or
return PlatformApp.router(
material: (_, __) => MaterialAppRouterData(...)
cupertino: (_, __) => CupertinoAppRouterData(...)
);A Scaffold that provides the correctly hosted header (AppBar) and navigation bar (Bottom Bar) for each platform. Uses Scaffold for material or CupertinoTabScaffold for cupertino with bottom tabs or CupertinoPageScaffold for cupertino without bottom tabs.
return PlatformScaffold(
appBar: PlatformAppBar()
body: _buildContent(),
bottomNavBar: PlatformNavBar(),
iosContentPadding: false,
iosContentBottomPadding: false
);Note that the use of
iosContentPadding = trueis only required if the content is being obstructed behind the appBar.iosContentBottomPaddingis used if the content needs to be above the navBar and not go behind it. This will not have the translucent effect for iOS when these are set totrue. If that is desirable, then the scrolling and content alignment need to be managed yourself.
Extend with PlatformBuilder for material or cupertino.
return PlatformScaffold(
appBar: PlatformAppBar()
body: _buildContent(),
bottomNavBar: PlatformNavBar(),
material: (_, __) => MaterialScaffoldData(...)
cupertino: (_, __) => CupertinoPageScaffoldData(...);
);Both the material and cupertino builders are optional. If not provided the
Containerplaceholder widget will be returned.
Note: Using
PlatformTabScaffoldprovides a more refined and flexible experience than usingPlatformScaffold.
A Scaffold that provides the correctly hosted header (AppBar) and navigation bar (Bottom Bar) for each platform. Uses Scaffold for material or CupertinoTabScaffold for cupertino with bottom tabs.
return PlatformTabScaffold(
tabController: tabController,
appBarBuilder: (_, index) => PlatformAppBar(),
bodyBuilder: (context, index) => _buildContent(index),
items: _items(context),
);More more detailed example look at:
Note that the use of
iosContentPadding = trueis only required if the content is being obstructed behind the appBar.iosContentBottomPaddingis used if the content needs to be above the navBar and not go behind it. This will not have the translucent effect for iOS when these are set totrue. If that is desirable, then the scrolling and content alignment need to be managed yourself.
Extend with PlatformBuilder for material or cupertino.
return PlatformTabScaffold(
tabController: tabController,
appBarBuilder: (_, index) => PlatformAppBar(),
bodyBuilder: (context, index) => _buildContent(index),
items: _items(context),
material: (_, __) => MaterialTabScaffoldData(...)
cupertino: (_, __) => CupertinoTabScaffoldData(...);
materialtabs: (_, __) => MaterialNavBarData(...)
cupertinoTabs: (_, __) => CupertinoTabBarData(...);
);Both the material and cupertino builders are optional. If not provided the
SizedBox.shrink()placeholder widget will be returned.materialcan be replaced withmaterialBuilderfor dynamic rendering on index changecupertinocan be replaced withcupertinoBuilderfor dynamic rendering on index change
The AppBar is the top Header bar with a title, left-side or right-side buttons. Uses AppBar for material or CupertinoNavigationBar for cupertino.
return PlatformAppBar(
title: new Text('Platform Widgets'),
leading: PlatformIconButton(),
trailingActions: <Widget>[
PlatformIconButton(),
],
);In Cupertino if a solid color header is required and there is a ListView on the page, you would need to add some alpha to the color so that the ListView is not pushed down too far
appBar: PlatformAppBar(
title: Text('iOS Colored Header'),
cupertino: (_, __) => CupertinoNavigationBarData(
// Issue with cupertino where a bar with no transparency
// will push the list down. Adding some alpha value fixes it (in a hacky way)
backgroundColor: Colors.lightGreen.withAlpha(254),
),
),
Extend with PlatformBuilder for material or cupertino.
return PlatformAppBar(
title: new Text('Platform Widgets'),
leading: PlatformIconButton(),
trailingActions: <Widget>[
PlatformIconButton(),
],
material: (_, __) => MaterialAppBarData(...),
cupertino: (_, __) => CupertinoNavigationBarData(...),
);The NavBar is placed at the bottom of the page with a set of buttons that typically navigate between screens. Implementing this widget requires the parent widget to manage the currentIndex of the page and to set PlatformNavBar.currrentIndex. Uses BottomAppBar with BottomNavigationBar for material or CupertinoTabBar for cupertino.
return PlatformNavBar(
currentIndex: _selectedTabIndex,
itemChanged: (index) => setState(
() {
_selectedTabIndex = index;
},
),
items: [
BottomNavigationBarItem(),
BottomNavigationBarItem(),
],
);Extend with PlatformBuilder for material or cupertino.
return PlatformNavBar(
currentIndex: _selectedTabIndex,
itemChanged: (index) => setState(
() {
_selectedTabIndex = index;
},
),
items: [
BottomNavigationBarItem(),
BottomNavigationBarItem(),
],
material: (_, __) => MaterialNavBarData(...),
cupertino: (_, __) => CupertinoTabBarData(...),
);The PlatformPopupMenu will render a using a PopupMenuButton for material or use a CupertinoActionSheet for cupertino which will display a list of actions.
return PlatformPopupMenu(
options: [
PopupMenuOption(label: 'One', onTap: _navToPageOne),
PopupMenuOption(label: 'Two', onTap: _navToPageTwo),
PopupMenuOption(label: 'Three', onTap: _navToPageThree)
],
icon: Icon(
context.platformIcon(
material: Icons.more_vert_rounded,
cupertino: CupertinoIcons.ellipsis,
),
),
);Extend with PlatformBuilder for material or cupertino.
return PlatformPopupMenu(
options: [
PopupMenuOption(label: 'One', onTap: _navToPageOne),
PopupMenuOption(label: 'Two', onTap: _navToPageTwo),
PopupMenuOption(label: 'Three', onTap: _navToPageThree)
],
icon: Icon(
context.platformIcon(
material: Icons.more_vert_rounded,
cupertino: CupertinoIcons.ellipsis,
),
),
material: (_, __) => MaterialPopupMenuData(...),
cupertino: (_, __) => CupertinoPopupMenuData(...),
);The AlertDialog will render a caption/title, body/text and a set of action buttons specific for the platform. Uses AlertDialog for material or CupertinoAlertDialog for cupertino.
Note use
showPlatformDialoginstead of eithershowDialogfrom the Material library orshowCupertinoDialogfrom the Cupertino library.
showPlatformDialog(
context: context,
builder: (_) => PlatformAlertDialog(
title: Text('Alert'),
content: Text('Some content'),
actions: <Widget>[
PlatformDialogAction(),
PlatformDialogAction(),
],
),
);Extend with PlatformBuilder for material or cupertino.
showDialog(
context: context,
builder: (_) => PlatformAlertDialog(...),
cupertino: (_, __) => CupertinoAlertDialogData(...),
material: (_, __) => MaterialAlertDialogData(...),
)The DialogAction widget is used to describe the set of buttons on the AlertDialog. Uses TextButton for material or CupertinoDialogAction for cupertino.
PlatformDialogAction(
child: PlatformText('Cancel'),
onPressed: () => Navigator.pop(context),
),Extend with PlatformBuilder for material or cupertino.
PlatformDialogAction(
child: PlatformText('Cancel'),
onPressed: () => Navigator.pop(context),
material: (_, __) => MaterialDialogActionData(...),
cupertino: (_, __) => CupertinoDialogActionData(...),
),A circular looking progress indicator. Uses CircularProgressIndicator for material or CupertinoActivityIndicator for cupertino.
return PlatformCircularProgressIndicator();Extend with PlatformBuilder for material or cupertino.
return PlatformCircularProgressIndicator(
material: (_, __) => MaterialProgressIndicatorData(...),
cupertino: (_, __) => CupertinoProgressIndicatorData(...),
);This function can be used within the Navigator to push either the MaterialPageRoute for material or CupertinoPageRoute for cupertino.
Navigator.push(
context,
platformPageRoute(
context: context,
builder: pageToDisplayBuilder,
),
);Extend with PlatformBuilder for material or cupertino.
return platformPageRoute(
context: context,
material: (_, __) => MaterialPageRouteData(...),
cupertino: (_, __) => CupertinoPageRouteData(...),
);This function can be used within flutter's Navigator 2 to push either the MaterialPage for material or CupertinoPage for cupertino.
platformPage(
context: context,
child: child,
),
);Extend with PlatformBuilder for material or cupertino.
return platformPage(
context: context,
material: (_, __) => MaterialPageData(...),
cupertino: (_, __) => CupertinoPageData(...),
);This function is used to either display a ModalBottomSheet for material or CupertinoModalPopup for cupertino.
showPlatformModalSheet(
context: context,
builder: (_) => PlatformWidget(
material: (_, __) => _materialPopupContent(),
cupertino: (_, __) => _cupertinoSheetContent(),
),
);Note: Since Material and Cupertino content may be quite different it may be useful to use
PlatformWidget.
This function is used to either display a DatePickerDialog for material or CupertinoDatePicker via a showCupertinoModalPopup for cupertino.
showPlatformDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime.now().subtract(const Duration(days: 1000)),
lastDate: DateTime.now().add(const Duration(days: 1000)),
);Note: The Cupertino popup uses a modal bottom sheet. If you need to customize the look then set the
cupertinoContentBuilderproperty and return a custom content. If you create your own content builder you will need to manage the state of the date yourself. See the implementation of usingStatefulBuilderorStatefulWidgetto manage state updates within the example project.
final date = await showPlatformDatePicker(
context: context,
firstDate: DateTime.now().subtract(const Duration(days: 100)),
lastDate: DateTime.now().add(const Duration(days: 100)),
initialDate: DateTime.now(),
cupertinoContentBuilder: (contentData, data) =>
_CustomCupertinoDatePicker(contentData: contentData),
);A Provider that provides access to the functions of switching platforms which can be accessed from any screen.
Requires to be placed at the root (above MaterialApp, CupertinoApp or PlatformApp).
return PlatformProvider(
builder: (BuildContext context) => MaterialApp(...)
);An optional argument initialPlatform can be passed in to force the platform upon startup. This could be useful for development or if the platform is persisted externally (i.e. Shared preferences) and needs to be set on startup.
And to switch platforms...
PlatformProvider.of(context).changeToMaterialPlatform();
or
PlatformProvider.of(context).changeToCupertinoPlatform();
or
PlatformProvider.of(context).changeToPlatform(Platform.fuchsia);
This will set the Theme.of(context).platform but the platform widgets will use the style as defined in the PlatformStyle as set inside the settings object. See below.
The settings argument have been added to assist in configuring Platform Widgets.
iosUsesMaterialWidgets
- If true it will add a Material widget above the CupertinoPageScaffold so that Material widgets can be added to the ios page. This does affect dark mode and some ios rendering so it is best to have it false (default). If you use Material widgets on the page simply add
Material(child: yourWidget).
platformStyle
- Provides a way to set either
MaterialorCupertinostyle on any supported platforms such as android, ios, web, macos, fuchsia, windows amd linux. For example if you wanted to useCupertinowidgets for web you would configure by setting the settings object onPlatformProvider:
PlatformProvider(
settings: PlatformSettingsData(
platformStyle: PlatformStyleData(web: PlatformStyle.Cupertino)
),
builder: (context) => PlatformApp(...)
)
legacyIosUsesMaterialWidgets
- If true will have the
Materialwidget aboveCupertinoScaffoldandCupertinoTabScaffoldrather than one level down. Having set tofalsewill likely prevent an exception when using a material widget for a cupertino style. This setting was the default pre v1.6.0
legacyMaterialDialogActionButtons
- If true all material dialog action buttons will use
FlatButtonwhich is the default pre v1.9.0. As of v1.9.0 the material dialog action button will use the newer materialTextButton
Helper function to a Material or Cupertino theme data property based on the platform
Text(
platform.text,
textAlign: TextAlign.center,
style: platformThemeData(
context,
material: (data) => data.textTheme.headline5,
cupertino: (data) => data.textTheme.navTitleTextStyle,
),
)Render a Material or Cupertino looking icon
Icon(context.platformIcons.book)
//or
Icon(PlatformIcons(context).book)View the source or screenshots for the list of icons.
Renders a parent widget for either Cupertino or Material while sharing a common child Widget
PlatformWidgetBuilder(;
cupertino: (_, child, __) => GestureDetector(child: child, onTap: _handleTap),
material: (_, child, __) => IniWell(child: child, onTap: _handleTap),
child: Container(child: Text('Common text')),
);- UI / Unit Tests.
- Code documentation
When importing sunny_platform_widgets you can check isMaterial(context) or isCupertino(context) to determine what style will be used. This is independent to Platform.isAndroid or Platform.isIOS from 'import 'dart:io'
You can call platform(context) to get the current platform. This is an enhancement on the existing TargetPlatform enum which now includes a value for web.
See the example code for how this is used.
Please create an issue to provide feedback or an issue.
Special thanks for everyone that have contributed to this project...
Inspired by the example given by Swav Kulinski (https://github.com/swavkulinski/flutter-platform-specific-widgets)





