Flutter Platform Widgets
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 conditionaly 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.
Installation
How it works
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.
Configuration
See PlatformProvider for configuration options.
Widgets
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
- PlatformButton
- PlatformIconButton
- PlatformApp
- PlatformScaffold
- PlatformTabScaffold
- PlatformAppBar
- PlatformNavBar
- PlatformAlertDialog
- PlatformDialogAction
- PlatformCircularProgressIndicator
- PlatformPageRoute
- ShowPlatformModalSheet
- PlatformProvider
- PlatformIcons
- PlatformWidgetBuilder
PlatformWidget
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),
);
PlatformText
A widget that will render uppercase for material. Cupertino will remain unchanged.
return PlatformText('Cancel');
PlatformSwitch
A switch widget that will use a Switch
for material or a CupertinoSwitch
for cupertino.
return PlatformSwitch(
onChanged: (bool value) {},
value: value,
);
Enhance
return PlatformSwitch(
onChanged: (bool value) {},
value: value,
material: (_, __) => MaterialSwitchData(...),
cupertino: (_, __) => CupertinoSwitchData(...)
);
PlatformSlider
A slider widget that will use a Slider
for material or a CupertinoSlider
for cupertino
return PlatformSlider(
onChanged: (bool value) {},
value: value,
);
Enhance
return PlatformSlider(
onChanged: (bool value) {},
value: value,
material: (_, __) => MaterialSliderData(...),
cupertino: (_, __) => CupertinoSliderData(...)
);
PlatformTextField
A text field widget that will use a TextField
for material or a CupertinoTextField
for cupertino.
return PlatformTextField();
Enhance
return PlatformTextField(
material: (_, __) => MaterialTextFieldData(...),
cupertino: (_, __) => CupertinoTextFieldData(...)
);
PlatformButton
A button that will render a RaisedButton
or FlatButton
for material or a CupertinoButton
for cupertino.
return PlatformButton(
onPressed: () => print('send'),
child: PlatformText('Send'),
);
Enhance
Extend with WidgetBuilder
for material or cupertino.
return PlatformButton(
onPressed: () => print('send'),
child: PlatformText('Send'),
material: (_, __) => MaterialRaisedButtonData(...),
cupertino: (_, __) => CupertinoButtonData(...)
);
Note: For material you can use the
FlatButton
instead. To do this use theMaterialFlatButtonData
on thematerialFlat
argument.NoteL For cupertino you can use the
CupertinoButton.filled
instead. To do this use theCupertinoFilledButtonData
on thecupertinoFilled
argument.
return PlatformButton(
onPressed: () => print('send'),
child: PlatformText('Send'),
materialFlat: (_, __) => MaterialFlatButtonData(),
cupertinoFilled: (_, __) => CupertinoFilledButtonData(),
);
PlatformIconButton
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,
),
);
Enhance
Extend with WidgetBuilder
for material or cupertino.
Widget infoIconButton() {
return PlatformIconButton(
onPressed: () => print('info pressed'),
materialIcon: Icon(Icons.info),
cupertinoIcon: Icon(CupertinoIcons.info),
material: (_, __) => MaterialIconButtonData(...),
cupertino: (_, __) => CupertinoIconButtonData(...),
);
}
PlatformApp
A top level widget for the applciation that uses MaterialApp
for material or CupertinoApp
for cupertino.
return PlatformApp(
title: 'Flutter Demo',
home: ...
);
Enhance
Extend with WidgetBuilder
for material or cupertino.
return PlatformApp(
home: ...
material: (_, __) => MaterialAppData(...)
cupertino: (_, __) => CupertinoAppData(...)
);
PlatformScaffold
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 = true
is only required if the content is being obstruced behind the appBar.iosContentBottomPadding
is 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.
Enhance
Extend with WidgetBuilder
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
Container
placeholder widget will be returned.
PlatformTabScaffold
Note: Using
PlatformTabScaffold
provides 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 = true
is only required if the content is being obstruced behind the appBar.iosContentBottomPadding
is 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.
Enhance
Extend with WidgetBuilder
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.
PlatformAppBar
The AppBar is the top Header bar with a title, leftside or rightside 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),
),
),
Enhance
Extend with WidgetBuilder
for material or cupertino.
return PlatformAppBar(
title: new Text('Platform Widgets'),
leading: PlatformIconButton(),
trailingActions: <Widget>[
PlatformIconButton(),
],
material: (_, __) => MaterialAppBarData(...),
cupertino: (_, __) => CupertinoNavigationBarData(...),
);
PlatformNavBar
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(),
],
);
Enhance
Extend with WidgetBuilder
for material or cupertino.
return PlatformNavBar(
currentIndex: _selectedTabIndex,
itemChanged: (index) => setState(
() {
_selectedTabIndex = index;
},
),
items: [
BottomNavigationBarItem(),
BottomNavigationBarItem(),
],
material: (_, __) => MaterialNavBarData(...),
cupertino: (_, __) => CupertinoTabBarData(...),
);
PlatformAlertDialog
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
showPlatformDialog
instead of eithershowDialog
from the Material library orshowCupertinoDialog
from the Cupertino library.
showPlatformDialog(
context: context,
builder: (_) => PlatformAlertDialog(
title: Text('Alert'),
content: Text('Some content'),
actions: <Widget>[
PlatformDialogAction(),
PlatformDialogAction(),
],
),
);
Enhance
Extend with WidgetBuilder
for material or cupertino.
showDialog(
context: context,
builder: (_) => PlatformAlertDialog(...),
cupertino: (_, __) => CupertinoAlertDialogData(...),
material: (_, __) => MaterialAlertDialogData(...),
)
PlatformDialogAction
The DialogAction widget is used to describe the set of buttons on the AlertDialog. Uses FlatButton
for material or CupertinoDialogAction
for cupertino.
PlatformDialogAction(
child: PlatformText('Cancel'),
onPressed: () => Navigator.pop(context),
),
Enhance
Extend with WidgetBuilder
for material or cupertino.
PlatformDialogAction(
child: PlatformText('Cancel'),
onPressed: () => Navigator.pop(context),
material: (_, __) => MaterialDialogActionData(...),
cupertino: (_, __) => CupertinoDialogActionData(...),
),
PlatformCircularProgressIndicator
A circular looking progress indicator. Uses CircularProgressIndicator
for material or CupertinoActivityIndicator
for cupertino.
return PlatformCircularProgressIndicator();
Enhance
Extend with WidgetBuilder
for material or cupertino.
return PlatformCircularProgressIndicator(
material: (_, __) => MaterialProgressIndicatorData(...),
cupertino: (_, __) => CupertinoProgressIndicatorData(...),
);
PlatformPageRoute
This function can be used within the Navigator
to push either the MaterialPageRoute
for material or CupertinoPageRoute
for cupertino.
Navigator.push(
context,
platformPageRoute(
builder: pageToDisplayBuilder,
),
);
ShowPlatformModalSheet
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 tl use
PlatformWidget
.
PlatformProvider
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(...)
);
Arguments
initialPlatform
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.
settings
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
Material
orCupertino
style on any supported platforms such as android, ios, web, macos, fuchsia, windows amd linux. For example if you wanted to useCupertino
widgets for web you would configure by setting the settings object onPlatformProvider
:
PlatformProvider(
settings: PlatformSettingsData(
platformStyle: PlatformStyleData(web: PlatformStyle.Cupertino)
),
builder: (context) => PlatformApp(...)
)
PlatformIcons
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.
PlatformWidgetBuilder
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')),
);
TODO
- UI / Unit Tests.
- Code documentation
Changing / Checking Platform
When importing flutter_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.
Issues and Feedback
Please create an issue to provide feedback or an issue.
Acknowledgements
Inspired by the example given by Swav Kulinski (https://github.com/swavkulinski/flutter-platform-specific-widgets)
Source Code
Please Visit Flutter Platform Widgets Source Code at GitHub