Flutter Platform Widgets and Usage

Sreehari Vasudevan
8 min readMay 24, 2020

--

While going through cross platform solutions, it is expected one code base to give platform specific behaviour while deployed into individual platforms. Eventhough google is more aligned to push Material Design for all platforms, it would be unfair to force the same on Great Steve Jobs 💖 fans ✌️, who’s family already suggested Human Interface Guidlines for ios.

Big Salute to the Legend

Luckily there is platform widget’s library developed by google itself , which is having support for many widgets (not all for now 😟) and lets hope for more in future. This article is speculating usage of platform specific widgets in Flutter and its implications.

Main flutter setup does not change much. As Cupertinos and platform widgets need to be included, we require the below dependency in pubspec.yaml file. Run get packages and dependencies will be added to your project.

cupertino_icons: ^0.1.3
flutter_platform_widgets: ^0.50.0

Now, in-order to start with, let’s tell source to handle different patterns for both the platforms. By naming pattern, lets give it a global meaning which has different aspects in different platforms to achieve the same outcome. In Android, screens may or may not have back navigation button in app bar due to presence to physical back button (This physical button’s may change in future with Gesture naviation for utilizing the screen real estate😇). But for iOS we need mandatory back button in left side of the app bar as there is no physical back button. In iOS , title will be placed in centre of the app bar, were as in Android title will be aligned to left side in app bar. Like wise the differences go on increasing in different scenarios.

Open the main.dart file. By default flutter apps will have StatelessWidget. This widget’s build method will return MaterialApp like below.

@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo'

For having platform specific app, we will remove this MaterialApp and replace it with PlatformApp from and pass specific theme for two platforms. (flutter_platform_widgets/flutter_platform_widgets.dart)

@override
Widget build(BuildContext context) {
return PlatformApp(
title: 'Flutter Demo',
android: (_) => MaterialAppData(theme: materialThemeData),
ios: (_) => CupertinoAppData(theme: cupertinoTheme),

You have noticed , I have used different themes in both platform versions. This we can define according to our specification shown as below for materialThemeData.

final materialThemeData = ThemeData(
primarySwatch: Colors.blue,
scaffoldBackgroundColor: Colors.white,
accentColor: Colors.blue,
appBarTheme: AppBarTheme(color: Colors.blue.shade600),
primaryColor: Colors.blue,
secondaryHeaderColor: Colors.blue,
canvasColor: Colors.blue,
backgroundColor: Colors.red,
textTheme: TextTheme().copyWith(bodyText1: TextTheme().bodyText2));

Now for cupertinoTheme , we can define the following.

final cupertinoTheme = CupertinoThemeData(
primaryColor: Colors.blue,
barBackgroundColor: Colors.blue,
scaffoldBackgroundColor: Colors.white);

Now define the home screen , I have created AppHomePage under ui folder. And created a stateful widget.

class AppHomePage extends StatefulWidget {
AppHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_AppHomePageState createState() => _AppHomePageState();
}

Now the _AppHomePageState will be returning PlatformScaffold which will have an appBar, inturn creates PlatformAppBar inside which iOS specific CupertinoNavigationBarData will be defined.

PlatformScaffold will have another parameter android, which will return MaterialScaffoldData like shown below.

class _AppHomePageState extends State<AppHomePage> {
@override
Widget build(BuildContext context) {
return PlatformScaffold(
appBar: PlatformAppBar(
title: Text(widget.title, style: toolbarTextStyle,),
ios: (_)=> CupertinoNavigationBarData(
transitionBetweenRoutes: false,
),),
android: (_) => MaterialScaffoldData(),
body: Center(child:Text("Screen 1",
style: TextStyle(color: Colors.black, fontSize: 30),),));}}

Now you can run on both devices. Below will be the outcome👏😇

First impression of platform specific widgets

As you can see same code base behaves specific to platform with the differentiations we did. Check the differences in title alignment. The source can be found in branch #01-initial-screen over here Github Repo.

Moving on further, let us add few widgets in screen and see how to handle the same in both platforms. For this task, let us have a Stateful widget as First Screen. And add two entry fields , a switch for remember me and button to mimic look and feel of a login screen. And an icon at the top as well.

For icons, we can use Icon(context.platformIcons. …) , but which has a limited number of assets available. Let’s have a nice logo at the top before the entry fields. I ‘m a big fan of Avengers. Hence got an Iron Man face logo from https://icons8.com/icons

For adding platform icon or regular icon, the pattern is same. But here we will be referring to the downloaded icon which will be kept in images folder of our project. In-order to add the icon , we should edit the pubspec.yaml file. There should be a 2 space difference between the parent in pubspec file.

assets:
- images/icon_iron_man.png

Now we can refer the icon in our screens. For doing so, within our _FirstScreenState , create a method , which will return Widget , who’s container will be having an Image as child.

Widget getAssetImage() {AssetImage assetImage = AssetImage('images/icon_iron_man.png');
Image image = Image(image: assetImage,width: 125.0,height: 125.0,);
return Container(
padding: EdgeInsets.only(top: padding_20, bottom: padding_20 * 2),
child: image,
alignment: Alignment.center,);
}

Now, in the _FirstScreenState, we can refer the same in our column.

Widget build(BuildContext context) {return Column(children: <Widget>[
getAssetImage(),
//other elements goes here
After adding image from asset.

Now by adding the first screen with 2 entry fields , we will have a user name field and password field (password will be masked) as shown below.

class _FirstScreenState extends State<FirstScreen>{@overrideWidget build(BuildContext context) {return Column(children: <Widget>[getAssetImage(),Padding(
padding: const EdgeInsets.only(top: 50.0, left: 20.0, right: 20.0),
child: PlatformTextField(
keyboardType: TextInputType.text,
android: (_) => MaterialTextFieldData(
decoration: InputDecoration(labelText: 'User name'),),
ios: (_) => CupertinoTextFieldData(
placeholder: 'User name',
),),),
Padding(
padding: const EdgeInsets.all(20.0),
child: PlatformTextField(
keyboardType: TextInputType.text,
android: (_) => MaterialTextFieldData(
decoration: InputDecoration(labelText: 'Password'),obscureText: true,),
ios: (_) => CupertinoTextFieldData(
placeholder: 'Password',
obscureText: true,
),),),
Entry fields difference while deployed to both platforms

Now you can see, there is a considerable difference in the elements when deployed to respective platforms. These are the main advantages of Platform specific widgets. The adaption when same source is deployed into different platforms is much appreciable

There may be errors like bottom overflowed in some devices. 🤯 Do not panic. Let’s resolve this error later after completing rest of elements in first screen.🤗

Moving forward, we can have Switch for remember me functionality and login button.

Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(padding_20),
child: PlatformSwitch(
value: _currentSwitchValue,
onChanged: (value) {
setState(() {
_currentSwitchValue = value;
if (value) {
switchText = "Yes,";
} else {
switchText = "No, Don't";}});
},),),
Text('$switchText remember me ',
style: TextStyle(color: Colors.blueGrey, fontSize: 15),),],),
Padding(
padding: const EdgeInsets.all(padding_20),
child: PlatformButton(
onPressed: () {},
child: Text('Login'),
android: (_) => MaterialRaisedButtonData(),
ios: (_) => CupertinoButtonData(),
)),
Required elements in login screen

I have changed the theme, to suite our Iron Man logo. Now you can see, the differences of switches in both platforms. Check and uncheck the Switch, you can see the remember me label changes. Also checkout the button difference. Even though this looks pale, still the platform difference is evident. Let us now handle the error (bottom overflow by XX pixels) as well as , add some validation.

Inorder to handle overflow error, We have to change the heirarchy. _FirstScreenState will return a ListView 🧐. And inside the listview , we will have an array of widgets. So below are the changes to be applied

return ListView(
children: <Widget>[
....
The change in hierarchy, will allow us to scroll and avoid the overflow error

For validation, we will do track of PlatformTextField’s onChanged method. At present the Form’s are not applicable for platform widgets. So we have to do the validation manually or build our own. Let us show an alert (PlatformAlertDialog) if any of the TextFields are empty. For now I am checking only for empty values, you may add any kind of validations.

Platform specific alert dialogues

As this may seems annoying, you can avoid this validation from onChanged and only add checking for error scenarios in Login Button. I have kept both for demonstration.

child: PlatformButton(
onPressed: () {
setState(() {
if(_userNameEntered.isEmpty || _passwordEntered.isEmpty){
showAlert('Please enter both user name and password');
}else{
debugPrint('proceed');
}
});},
child: Text('Login'),
android: (_) => MaterialRaisedButtonData(),
ios: (_) => CupertinoButtonData(),)
Alert dialog for fields validation.

The source can be found in branch #02-elements-in-landing

Now lets move on and see , how to navigate from first to second screen post successful login. Create dashboard.dart under same package. Place a text in the body of DashboardScreenState. Create a function named moveToNextScreen and give the below for navigation.

void moveToNextScreen(BuildContext context) {
Navigator.pushReplacement(context,MaterialPageRoute(builder: (context) => DashboardScreen()),);
}

If we use regular push, that will cause creation of back button in app bar of Android devices. Now give user name, password and press Login button. You can see the dashboard.

The source can be found in branch #03-dashboard-added

In dashboards, there will be quite a good segregation mostly we need to do. So let us create navigation with bottom bar for the same. To start with bottom tabs (three), create three different widgets for three tabs.

class ScreenTabOne extends StatelessWidget {@override
Widget build(BuildContext context) {
return Center(
child: Text(
"Dashboard",
style: TextStyle(color: Colors.brown, fontSize: 30),
),);}
}

Similarly create other two. Check screen_tab_one.dart ,screen_tab_two.dart and screen_tab_three.dart. In the DashboardScreenState , give corresponding parameters for bottomNavBar. Which will be the PlatformNavBar. Define the BottomNavigationBarItem with icon, title and active icon for all the three tabs.

bottomNavBar: PlatformNavBar(
currentIndex: _tabSelectedIndex,
itemChanged: (index) {
setState(() {
_tabSelectedIndex = index;
title = getScreenTitle(index);
});},
backgroundColor: Colors.blueGrey,
items: [BottomNavigationBarItem(
icon: Icon(Icons.apps, color: Colors.grey),
title: Text('Dashboard', style: ColorUtils.bottomNavTextStyle),
activeIcon: Icon(Icons.apps, color: Colors.white),),
BottomNavigationBarItem(
icon: Icon(Icons.update, color: Colors.grey),
title: Text('Feeds', style: ColorUtils.bottomNavTextStyle),
activeIcon: Icon(Icons.update, color: Colors.white),),
BottomNavigationBarItem(icon: Icon(Icons.settings, color: Colors.grey),title: Text('Settings',style: ColorUtils.bottomNavTextStyle,),
activeIcon: Icon(Icons.settings, color: Colors.white),)
]),
After adding bottom nav bar

With this our initial app flow is completed which exposes the power of Flutter Platform Widgets. By navigating to other tabs the title also will change correspondingly. You can run the app from start and see the complete app flow.

Full source code can be found over Github

Final version of the screens in both platforms

If you find this article helpful, do clap 👏👏 which will keep writers motivated to contribute more in the different technological aspects. 😇

Full source code can be found over Github

Thanks for reading✌️ Happy Coding 👨‍💻 Cheers 🥂🍻

Icons downloaded from https://icons8.com/icons

Gif images copyright https://giphy.com/

--

--