Import js.dart and html.dart for a mixed web / mobile flutter project

2.5k Views Asked by At

I'm using Flutter 2.5.2. I have a project which is to be used for both web and mobile (Android / iOS).

There is one particular widget where I need to use one version of the widget when deploying for web, which uses the JS and HTML packages. When deploying for mobile, I need to use a different version which just uses standard Flutter widgets. (The reasons for this are complex - I'm embedding Unity inside flutter).

So for example, I have this web_player.dart version for web:

import 'dart:html' as html;
import 'package:js/js.dart';
import 'package:flutter/material.dart';

@JS('loadPlayer')
external String loadPlayer();

class WebVersion extends StatelessWidget {
  const WebVersion({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    // Use the HTML package and return an HtmlElementView
  }
}

And this mobile_player.dart version for mobile:

class MobileVersion extends StatelessWidget {
  const MobileVersion({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Text("Mobile version")
  }
}

Building for web is fine. The problem is that when I build for mobile, the build breaks with:

Error: Not found: 'dart:js'

and

Error: Not found: 'dart:html'

I'm aware that these packages do not exist in the mobile platform, and I'm trying to find a workaround. I initially tried to conditionally use the WebPlayer or MobilePlayer widgets depending on the kIsWeb flag, but this didn't make any difference (I guess because kIsWeb is not a complile-time constant?)

So then I attempted to work around this using conditional imports. So, I created a stub player_stub.dart:


import 'package:flutter/material.dart';


Widget getPlayer() => throw UnsupportedError("This is a stub");

I import this stub in a wrapper Player widget:

import 'package:mypackage/player_stub.dart'
  if (dart.library.io) 'package:mypackage/mobile_player.dart'
  if (dart.library.js) 'package:mypackage/web_player.dart';

class Player extends StatelessWidget {

  const Player ();

  @override
  Widget build(BuildContext context) {
    return getPlayer();
  }
}

And then added stub implementations in my mobile_player.dart:

Widget getPlayer() => MobilePlayer();

and in my web_player.dart:

Widget getPlayer() => WebPlayer();

I was hoping this would resolve the build error by compile time tree shaking or something, but it doesn't.

How do I solve this? I'm totally stumped.

2

There are 2 best solutions below

1
On

Just to answer my own question for others looking for an answer: the solution was moving the stub, the mobile widget and the web widget to a separate dart library.

The library uses conditional exports like this in it's root my_player_package.dart file:

export 'src/player_stub.dart'
  if (dart.library.js) 'src/web_player.dart'
  if (dart.library.io) 'src/mobile_player.dart';

So now I reference my separate library in my pubspec.yaml:

dependencies:

  ...

  my_player_package:
    path: ../my_player_package

And I can now import 'package:my_player_package/my_player_package.dart' and build for both web and mobile.

0
On

I'm attempting to use the js package in a cross-platform app and this was my solution.

1. Create a js_stub.dart file

class JS {
  final String? name;
  const JS([this.name]);
}

allowInterop<F extends Function>(F f){
  throw UnimplementedError();
}

2. Use a conditional import statement

import 'package:pwa_install/js_stub.dart' if (dart.library.js) 'package:js/js.dart';

The rest of my code was left untouched and the app runs as expected on web and mobile.