Flutter Cupertino and Material or Fuchsia APIs are different enough to require switches or wrappers for the native look and feel.

Flutter lets you write once and then run anywhere with the native look and feel.  It turns out that getting the native look and feel on mobile devices means using Flutter platform-specific libraries that expose the platform differences. Users expect platform-specific behavior, especially on mobile devices.

You either bury switches everywhere in your code or you wrap them. This includes a demonstration of the Material and IOS date pickers and a wrapper that hides their differences from the rest of your code.  









References

Strategies

There are really three main strategies for handling mobile device native behavior.
  1. Ignore native behavior: Pick a single interface standard, say Material in flutter, and force all the users to use it no matter what type of device they have.  This is the simplest development approach and the least customer friendly.
  2. Use native components wherever possible
    1. Add switches statements everywhere the behavior varies: This is pretty much the brute force and ignorance approach.  You just let each application developer handle the differences wherever they come across them.  The main line code base contains all the platform-specific detection and behavior.
    2. Create intermediate wrapper libraries. This approach essentially hides the platform differences by wrapping them.  All platform-specific behavior is hidden inside the wrapper libraries. The main line code base is identical for all platforms because the platform behavior is hidden in the library. This approach may dumb down the agnostic API to all match the least functional implementation.
The sample code takes a wrapper approach for Date and Time entry widgets.

Sample Code from Adaptive Cards repository

I chose the wrapper approach for an Adaptive Card Flutter project that needed to support IOS and Android.

Picker Consumer

This is a sample from Input.Date code that lets a user select a date using a Date Picker.  This invokes a date picker wrapper that is modeled on the Material date picker API.  

onTap: () async {
DateTime? result = await widgetState.datePickerForPlatform(
context, selectedDateTime, min, max);
if (result != null) {
setState(() {
selectedDateTime = result;
controller.text = selectedDateTime == null
? placeholder
: inputFormat.format(selectedDateTime!);
});
}
},


Platform Agnostic API

The platform-agnostic API is really a wrapper for platform-specific implementations.  In this case we used the Material API as the template for all platforms.

Future<DateTime?> datePickerForPlatform(
BuildContext context, DateTime? value, DateTime? min, DateTime? max) {
if (Theme.of(context).platform == TargetPlatform.macOS ||
Theme.of(context).platform == TargetPlatform.iOS) {
return datePickerCupertino(context, value, min, max);
} else {
return datePickerMaterial(context, value, min, max);
}
}


Material Implementation

We modeled the platform-agnostic API on the Material specific implementation.  This means the Material implementation method is very simple.

Future<DateTime?> datePickerMaterial(
BuildContext context, DateTime? value, DateTime? min, DateTime? max) {
DateTime initialDate = value ?? DateTime.now();
return showDatePicker(
context: context,
initialDate: initialDate,
firstDate: min ?? DateTime.now().subtract(Duration(days: 10000)),
lastDate: max ?? DateTime.now().add(Duration(days: 10000)));
}


IOS / Cupertino Implementation

The Cupertino implementation is wrapped to behave similarly to the Material Date Picker. The important part is that the selected date is returned in the same way as it is in the Material Date Picker to make the wrapper behave the same no matter what the platform.

Future<DateTime?> datePickerCupertino(BuildContext context, DateTime? value,
DateTime? min, DateTime? max) async {
DateTime initialDate = value ?? DateTime.now();
DateTime? pickedDate = initialDate;

// showCupertinoModalPopup is a built-in function of the cupertino library
await showCupertinoModalPopup<DateTime?>(
context: context,
builder: (_) => Container(
height: 500,
color: const Color.fromARGB(255, 255, 255, 255),
child: Column(
children: [
SizedBox(
height: 400,
child: CupertinoDatePicker(
mode: CupertinoDatePickerMode.date,
initialDateTime: initialDate,
onDateTimeChanged: (val) {
pickedDate = val;
}),
),

// Close the modal
CupertinoButton(
child: const Text('OK'),
onPressed: () => Navigator.of(context).pop(),
)
],
),
));
return pickedDate;
}

This sample has some color issues that need to be addressed when running in light mode. Portions of the code came from this blog article

Video

Revision History

Created 2023 07





Comments

Popular posts from this blog

Understanding your WSL2 RAM and swap - Changing the default 50%-25%

Installing the RNDIS driver on Windows 11 to use USB Raspberry Pi as network attached

DNS for Azure Point to Site (P2S) VPN - getting the internal IPs