virtual_platform 0.3.0 copy "virtual_platform: ^0.3.0" to clipboard
virtual_platform: ^0.3.0 copied to clipboard

discontinued

A package that simplifies the cross-platform development process by providing a virtual platform than can be changed at runtime.

Welcome to virtual_platform, a package that simplifies the cross-platform development process by providing a virtual platform than can be changed at runtime, together with declarative virtual platform utils.

NB: If you are using also physical_platform, you can opt to only use omni_platform instead. omni_platform integrates virtual_platform and physical_platform, without adding any extra functionality. This way you only need to declare one package in your pubspec.yaml.

Warning: use this package only when targeting a single design language

This package assumes that you use only one design language (e.g. only MaterialApp is defined in your widget tree), however, some parts of the code might require better-fitting widgets (e.g., the date picker and the bottom sheets should look like proper iOS ones if the virtual platform is iOS).

On the other hand, if your intent is to use multiple design languages (e.g. MaterialApp should be used in Android devices, CupertinoApp on iOS devices, etc.), then you should use another package: design_language. A virtual platform can still be used in combination with design_language if the package does not provide all necessary utils.

Motivation #

Standard Flutter does not offer a straightforward approach for rendering platform-specific widgets on a different platform. For example, let's say that the widget tree is mainly Material widgets, however the date picker and the bottom sheets should look like proper iOS ones. One would usually need an Android smartphone and an iPhone to test this.

However, what if you don't have an iOS device? Or what if constantly switching between physical devices ends up being very time consuming and/or annoying? Wouldn't you prefer to to test all these different layouts directly from your main PC? Since all platforms can render widgets from any design language — e.g., Cupertino widgets can be rendered on any platform — why not take full advantage of that?

This package was made to cover all those scenarios: by introducing a virtual platform, which can be changed at runtime, we can force all the virtual-platform-specific widgets to be rebuilt when needed.

This package — virtual_platform — does not come with any prebuilt, ready-to-use widgets: it is up to the developer to determine the exact widget, per virtual platform. In general, I think that by forcing developers to build all the bricks by themselves the outcome is leading-edge adaption.

Getting started #

You need to add virtual_platform to your dependencies.

dependencies:
  virtual_platform: ^latest # replace latest with version number

Next, you have to import package:virtual_platform/virtual_platform.dart.

Usage #

NB: the arguments of both instruments VirtualPlatformDispatcher and matchVirtualPlatform are of type Widget Function(BuildContext context, Widget? child) and T Function(), respectively. If the goal is to only select a widget or a value — a function expression is what needs to be passed, e.g., (_, __) => Text('text') or () => 'value', respectively.

Platforms and platform groups are summarized at the end of this README.

VirtualPlatform and its notifier #

You need to instantiate VirtualPlatform.notifier before you use VirtualPlatformDispatcher or anything that relies on the virtual platform. A good place would be inside your main function.

void main() {
  VirtualPlatform.notifier = VirtualPlatformNotifier(VirtualPlatform.fromPhysicalPlatform);
  // the virtual platform is reset upon every restart
  runApp(VirtualPlatformDispatcher(
    other: (_, __) => const MyApp(), // this will get selected by all devices but iOS devices
    ios: (_, __) => const MyIosApp(), // this will get selected by iOS devices
  ));
}

If the goal is to force a specific virtual platform on startup for all platforms:

void main() {
we  VirtualPlatform.notifier = VirtualPlatformNotifier(VirtualPlatforms.ios);
  // the virtual platform is reset upon every restart
  runApp(VirtualPlatformDispatcher(
    other: (_, __) => const MyApp(),
    ios: (_, __) => const MyIosApp(), // this will get selected by all devices
  ));
}

The previous virtual platform can also be loaded from persistance storage.


// in a previous session:
sharedPreferences.setString('virtual_platform', VirtualPlatform.current.toString());

// when the app starts:
void main() async {
  final sharedPreferences = await SharedPreferences.getInstance();
  final previous = sharedPreferences.getString('virtual_platform');
  final previousVirtualPlatform = VirtualPlatform.fromString(previous);
  VirtualPlatform.notifier = VirtualPlatformNotifier(previousVirtualPlatform);
  runApp(VirtualPlatformDispatcher(
    other: (_, __) => const MyApp(),
    ios: (_, __) => const MyIosApp(),
  ));
}

How to change the virtual platform

You can change the platform by accessing VirtualPlatform.notifier's setter chosenPlatform, e.g.:

ElevatedButton(
    onPressed: () => VirtualPlatform.notifier.chosenPlatform =
        VirtualPlatforms.linux,
    child: const Text('set linux'),
),
ElevatedButton(
    onPressed: () => VirtualPlatform.notifier.chosenPlatform =
        VirtualPlatforms.ios,
    child: const Text('set ios'),
),
...

How to reset the virtual platform:

ElevatedButton(
    onPressed: () => VirtualPlatform.notifier.chosenPlatform =
        VirtualPlatform.fromPhysicalPlatform,
    child: const Text('reset virtual platform'),
),

VirtualPlatforms

VirtualPlatforms is a class containing all possible VirtualPlatform values as static fields. The constructors of VirtualPlatforms and VirtualPlatform are both private.

N.B.: The default and current virtual platform are accessed through VirtualPlatform (and not through VirtualPlatforms):

  • VirtualPlatform.fromPhysicalPlatform
  • VirtualPlatform.current (once the notifier is set)

VirtualPlatformDispatcher #

VirtualPlatformDispatcher is a widget builder dispatcher that should be used if there's a dependency only on the virtual platform.

VirtualPlatformDispatcher in action

The following snippets should be self-explanatory. If they are not, consult the platform table at the end of this README.

Each defined platform requires a nullable TransitionBuilder, with exception to the other chain, whose TransitionBuilder is non-nullable.

VirtualPlatformDispatcher(
  desktopSystems: (_, __) => Text('desktop'),
  other: (_, __) => Text('other'),
);
// 'other' on web, all mobile platforms and fuchsia
// 'desktop' will be displayed on macOS, Windows and Linux
VirtualPlatformDispatcher(
  desktopSystems: (_, __) => Text('desktop'),
  linux: (_, __) => Text('linux'),
  other: (_, __) => Text('other'),
);
// 'other' on web, all mobile platforms and fuchsia
// 'desktop' will be displayed only on macOS and Windows
// 'linux' will be displayed on Linux
VirtualPlatformDispatcher(
  appleSystems: (_, __) => Text('apple'),
  other: (_, __) => Text('other'),
);
// 'apple' will be displayed on macOS and iOS
VirtualPlatformDispatcher(
  appleSystems: (_, __) => Text('apple'),
  ios: (_, __) => Text('ios'),
  other: (_, __) => Text('other'),
);
// 'apple' will be displayed only on macOS

Other chain and safety measures

The following code is pretty safe:

VirtualPlatformDispatcher(
  other: (_, __) => Text('other'),
  mobileSystems: (_, __) => Text('mobile'),
  appleSystems: (_, __) => Text('apple'),
  ios: (_, __) => Text('ios'),
  desktopSystems: (_, __) => Text('desktop'),
  linux: (_, __) => Text('linux'),
  web: (_, __) => Text('web'),
);

However, not the same might be said about the following two widgets:

// [1] At the top of the widget tree:
VirtualPlatformDispatcher(
  other: (_, __) => MaterialApp(...), // NB: do not use the context, since it doesn't exist yet.
  ios: (_, __) => CupertinoApp(...), // NB: do not use the context, since it doesn't exist yet.
  macos: (_, __) => MacosApp(...), // NB: do not use the context, since it doesn't exist yet.
);

// [2] More down in the widget tree:
VirtualPlatformDispatcher(
  other: (_, __) => ListTile(...), // will crash if virtual platform is iOS
  macos: (_, __) => MacosListTile(...),
);

Specifying other does not automatically bring safety, i.e., a crash might occur even if the other chain is specified. For example, let us assume the virtual platform is VirtualPlatforms.ios and the widget tree consists of Cupertino widgets ([1] in the code snippet above). If the matched chain is other and this chain builds a widget using the Material design ([2] in the code snippet above), this widget should be wrapped in a Material before getting added to the widget tree. If not, it might result in a crash. The following snippet is a possible fix:

// Safe variant of [2]:
VirtualPlatformDispatcher(
  other: (_, __) => ListTile(...),
  ios: (_, __) => Material(child: ListTile(...)),
  macos: (_, __) => MacosListTile(...),
);

This is the reason why in this case one should prefer the package design_language over virtual_platform. Also, if support for an additional design language is added or dropped, the static analysis will not actually help you if you have been using this package for design-language dispatching.

Context and child provision

One might need the context in some cases. The first parameter of all builders is a BuildContext context. You can also specify a Widget? child widget, so that you do not have to repeat it in all builders.

Example:

return MaterialApp(
    title: 'Flutter Demo',
    theme: ThemeData(
        primarySwatch: Colors.blue,
    ),
    home: Scaffold(
        appBar: AppBar(title: const Text('example')),
        body: VirtualPlatformDispatcher(
            child: const CommonSubtree(),
            mobileSystems: (context, child) => SpecialWidget(
                child,
                onPressed(() {do something with context}),
            ),
            other: (context, child) => OtherSpecialWidget(
                child,
                onPressed(() {do something with context}),
            ),
        );
    ),
);

matchVirtualPlatform #

matchVirtualPlatform is a declarative pattern to invoke the right function for the matching physical platform.

You can use this in case you need to invoke some virtual-platform-specific functions, e.g.:

  • showCupertinoModalPopup for iOS
  • showModalBottomSheet for all other cases

Same example, in code:

// inside some callback
matchVirtualPlatform(
  other: () => showModalBottomSheet(...),
  ios: () => showCupertinoModalPopup(...),
);

Avoid using this function for building widgets as VirtualPlatformDispatcher should be used for that use-case.

Platforms and platform groups #


Platform appleSystems mobileSystems desktopSystems
android
ios
linux
macos
windows
web
fuchsia

Priority order: from left to right.

0
likes
160
pub points
4%
popularity

Publisher

verified publishermanuelplavsic.ch

A package that simplifies the cross-platform development process by providing a virtual platform than can be changed at runtime.

Repository (GitLab)
View/report issues

Documentation

API reference

License

BSD-3-Clause (license)

Dependencies

abstract_platform, flutter

More

Packages that depend on virtual_platform