Search code examples
flutterconfigurationmultiplatform

Flutter - Exclude unwanted platform specific dependencies


I am new to Flutter. I am experimenting with the framework and I would like to explore multiplatform apps. I am specifically targeting:

  • Windows
  • Macos
  • Linux

For each platform I'm using their own theme and icon sets (e.g. fluent & fluent_icons on Windows, adwaita & adwaita_icons on linux, cupertino & macos_ui on mac,...). However, even though the design is different across different platforms, I am sharing the component's behaviour by leveraging MVVM and platform-agnostic components.

I've been reading through all the flutter docs of packages vs plugins , however I think that either I didn't get something or there is something I'm missing.

When I perform a platform-specific build I would like to get rid of all the unnecessary dependencies (e.g. cupertino on Windows). How can I achieve this? I was thinking about creating:

  • A "shared" package for business logic (ViewModels, shared components, ...)
  • A package for each individual platform that will depend on "shared"

With this approach, I can have separate pubspec.yaml for each individual platform and, therefore, I can include only the needed dependencies.

my_app
  - my_app_linux // Only support for Linux with Adwaita widgets
    - linux 
    - lib
    - pubspec.yaml
  - my_app_windows // Only support for Windows with Fluent widgets
    - windows 
    - lib
    - pubspec.yaml
  - my_app_macos // Only support for Macos with MacOs widgets
    - macos 
    - lib
    - pubspec.yaml
  - my_app_shared (here I can just actually use the root project's "/lib" folder)
    - lib
    - pubspec.yaml

Have you got any advice/experience? Did I miss something in the official documentation of package vs plugin?


Solution

  • As of now, this is not possible without any workaround. Take a look at following GitHub issue: https://github.com/flutter/flutter/issues/23122#issuecomment-927938161

    In essence, you can create platform-specific widgets and inject those widgets during compile time by passing an environment variable with --dart-define.

    Example

    class MyApp extends StatelessWidget {
      static const bool MACOS_PLATFORM = bool.fromEnvironment("MACOS_PLATFORM", defaultValue: false);
      static const bool LINUX_PLATFORM = bool.fromEnvironment("LINUX_PLATFORM", defaultValue: false);
      static const bool WINDOWS_PLATFORM = bool.fromEnvironment("WINDOWS_PLATFORM", defaultValue: false);
    
      @override
      Widget build(BuildContext context) {
        /// Important note about tree shaking:
        /// For this to work you have to make a direct constructor call or a static function call e.g. MacOSWidget()
        ///
        /// This will NOT work:
        /// 
        ///     MacOSWidget macOSWidget;
        ///     if (MACOS_PLATFORM) return macOSWidget;
    
        if (MACOS_PLATFORM) return MacOSWidget();
        else if (LINUX_PLATFORM) return LinuxWidget();
        else if (WINDOWS_PLATFORM) return WindowsWidget();
        else return MyDefaultWidget();
      }
    }
    

    And then run your app with flutter run my_app --dart-define MACOS_PLATFORM=true or build it with flutter build my_app --dart-define MACOS_PLATFORM=true.

    Do NOT forget to include the argument --dart-define <desired_platform>=true. Otherwise, the environment variable is not exposed at compile time and tree shaking will not work.