From a6eb4298910586f2e6434594a21bda9bacabcb58 Mon Sep 17 00:00:00 2001 From: timbotimbo Date: Thu, 24 Jul 2025 16:30:43 +0200 Subject: [PATCH 1/2] Migrate dart:html to package:web to support WASM. --- example/pubspec.yaml | 6 +- lib/flutter_unity_widget.dart | 2 +- lib/src/web/web_unity_widget_controller.dart | 109 ++++++++++++------- pubspec.yaml | 5 +- 4 files changed, 78 insertions(+), 44 deletions(-) diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 7e1f7bdef..5a37ba032 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -6,7 +6,9 @@ description: Demonstrates how to use the flutter_unity_widget plugin. publish_to: "none" # Remove this line if you wish to publish to pub.dev environment: - sdk: ">=2.12.0 <4.0.0" + sdk: ">=2.17.0 <4.0.0" +# >=2.17 or higher is needed for pointer_interceptor to work on web with WASM. + dependencies: cupertino_icons: ^1.0.0 @@ -14,7 +16,7 @@ dependencies: sdk: flutter flutter_unity_widget: path: ../ - pointer_interceptor: ^0.9.3+2 + pointer_interceptor: ^0.10.0 dev_dependencies: flutter_test: diff --git a/lib/flutter_unity_widget.dart b/lib/flutter_unity_widget.dart index fe2d84961..3390c5808 100755 --- a/lib/flutter_unity_widget.dart +++ b/lib/flutter_unity_widget.dart @@ -3,7 +3,7 @@ library flutter_unity_widget; export 'src/facade_controller.dart'; export 'src/facade_widget.dart' if (dart.library.io) 'src/io/unity_widget.dart' - if (dart.library.html) 'src/web/unity_widget.dart'; + if (dart.library.js_interop) 'src/web/unity_widget.dart'; export 'src/helpers/events.dart'; export 'src/helpers/misc.dart'; export 'src/helpers/types.dart'; diff --git a/lib/src/web/web_unity_widget_controller.dart b/lib/src/web/web_unity_widget_controller.dart index 1713089e3..7485e5c7c 100644 --- a/lib/src/web/web_unity_widget_controller.dart +++ b/lib/src/web/web_unity_widget_controller.dart @@ -1,8 +1,9 @@ import 'dart:developer'; import 'dart:async'; import 'dart:convert'; -import 'dart:html' as html; +import 'dart:js_interop'; +import 'package:web/web.dart' as web; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:flutter_web_plugins/flutter_web_plugins.dart'; @@ -23,13 +24,16 @@ class UnityWebEvent { final dynamic data; } +// use JSON.stringify to turn JS objects into strings which we can json decode. +@JS('JSON.stringify') +external JSString jsonStringify(JSAny value); + class WebUnityWidgetController extends UnityWidgetController { final WebUnityWidgetState _unityWidgetState; static Registrar? webRegistrar; - late html.MessageEvent _unityFlutterBiding; - late html.MessageEvent _unityFlutterBidingFn; + late JSFunction _messageListener; bool unityReady = false; bool unityPause = true; @@ -99,27 +103,52 @@ class WebUnityWidgetController extends UnityWidgetController { _registerEvents() { if (kIsWeb) { - html.window.addEventListener('message', (event) { - final raw = (event as html.MessageEvent).data.toString(); - // ignore: unnecessary_null_comparison - if (raw == '' || raw == null) return; - if (raw == 'unityReady') { - unityReady = true; - unityPause = false; - - _unityStreamController.add(UnityCreatedEvent(0, {})); - return; - } - - try { - _processEvents(UnityWebEvent( - name: event.data['name'], - data: event.data['data'], - )); - } catch (e) { - log('Unexpected format', error: e); + _messageListener = ((web.Event event) { + if (event is web.MessageEvent) { + final jsData = event.data; + String data = ""; + + // Handle a raw JS Object [Object object] instead of a json string. + if (jsData is JSObject) { + try { + data = jsonStringify(jsData).toDart; + } catch (e) { + log('Failed to stringify JS object', error: e); + return; + } + } + // this can be either a raw string like "unityReady" or a json string "{\"name\":\"\", ..}" + else if (jsData is JSString) { + data = jsData.toDart; + } + + if (data.isNotEmpty) { + if (data == 'unityReady') { + unityReady = true; + unityPause = false; + _unityStreamController.add(UnityCreatedEvent(0, {})); + return; + } else { + try { + final decoded = json.decode(data); + if (decoded is Map && + decoded.containsKey("name") && + decoded.containsKey("data")) { + _processEvents(UnityWebEvent( + name: decoded['name'], + data: decoded['data'], + )); + } else { + log('Unexpected json object', error: data); + } + } catch (e) { + log('Unexpected json object', error: e); + } + } + } } - }); + }).toJS; + web.window.addEventListener('message', _messageListener); } } @@ -189,11 +218,13 @@ class WebUnityWidgetController extends UnityWidgetController { void callUnityFn({required String fnName}) { if (kIsWeb) { - _unityFlutterBidingFn = html.MessageEvent( + final web.MessageEvent _unityFlutterBidingFn = web.MessageEvent( 'unityFlutterBidingFnCal', - data: fnName, + web.MessageEventInit( + data: fnName.toJS, + ), ); - html.window.dispatchEvent(_unityFlutterBidingFn); + web.window.dispatchEvent(_unityFlutterBidingFn); } } @@ -203,27 +234,29 @@ class WebUnityWidgetController extends UnityWidgetController { required String message, }) { if (kIsWeb) { - _unityFlutterBiding = html.MessageEvent( + final web.MessageEvent _unityFlutterBiding = web.MessageEvent( 'unityFlutterBiding', - data: json.encode({ - "gameObject": gameObject, - "methodName": methodName, - "message": message, - }), + web.MessageEventInit( + data: json.encode({ + "gameObject": gameObject, + "methodName": methodName, + "message": message, + }).toJS, + ), ); - html.window.dispatchEvent(_unityFlutterBiding); + web.window.dispatchEvent(_unityFlutterBiding); postProcess(); } } /// This method makes sure Unity has been refreshed and is ready to receive further messages. void postProcess() { - html.Element? frame = html.document + web.Element? frame = web.window.document .querySelector('flt-platform-view') ?.querySelector('iframe'); - if (frame != null) { - (frame as html.IFrameElement).focus(); + if (frame != null && frame is web.HTMLIFrameElement) { + frame.focus(); } } @@ -292,9 +325,7 @@ class WebUnityWidgetController extends UnityWidgetController { void dispose() { _cancelSubscriptions(); if (kIsWeb) { - html.window.removeEventListener('message', (_) {}); - html.window.removeEventListener('unityFlutterBiding', (event) {}); - html.window.removeEventListener('unityFlutterBidingFnCal', (event) {}); + web.window.removeEventListener('message', _messageListener); } } } diff --git a/pubspec.yaml b/pubspec.yaml index 14e0fa8f8..d960eb7a9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -8,8 +8,8 @@ version: 2022.2.1 homepage: https://github.com/juicycleff/flutter-unity-view-widget/tree/master environment: - sdk: ">=2.16.0 <4.0.0" - flutter: ">=3.3.0" + sdk: ">=3.2.0 <4.0.0" + flutter: ">=3.16.0" dependencies: flutter: @@ -21,6 +21,7 @@ dependencies: plugin_platform_interface: ^2.1.2 webview_flutter: ^4.0.0 webview_flutter_web: ^0.2.2 + web: '>=0.3.0 <2.0.0' # Needs to resolve to >=0.5.0 to use WebAssembly (WASM). # ffi: ^1.2.1 // required for windows support dev_dependencies: From 04c8d5e3a852f93f13c14067bf1bd97a9ed17cfb Mon Sep 17 00:00:00 2001 From: timbotimbo Date: Thu, 24 Jul 2025 16:31:39 +0200 Subject: [PATCH 2/2] Remove dependency on webview_flutter. --- lib/src/web/web_unity_widget_view.dart | 17 ++++++++++++----- pubspec.yaml | 4 ++-- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/lib/src/web/web_unity_widget_view.dart b/lib/src/web/web_unity_widget_view.dart index 63cb6bbb1..28f5c23d9 100644 --- a/lib/src/web/web_unity_widget_view.dart +++ b/lib/src/web/web_unity_widget_view.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; -import 'package:webview_flutter/webview_flutter.dart'; +import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart'; +// ignore: unused_import +import 'package:webview_flutter_web/webview_flutter_web.dart'; // used indirectly through webview_flutter_platform_interface class WebUnityWidgetView extends StatefulWidget { const WebUnityWidgetView({ @@ -16,9 +18,12 @@ class WebUnityWidgetView extends StatefulWidget { } class _WebUnityWidgetViewState extends State { - final WebViewController _controller = WebViewController() - ..loadRequest( - Uri.parse('${_getBasePath()}/UnityLibrary/index.html'), + final PlatformWebViewController _controller = PlatformWebViewController( + const PlatformWebViewControllerCreationParams(), + )..loadRequest( + LoadRequestParams( + uri: Uri.parse('${_getBasePath()}/UnityLibrary/index.html'), + ), ); @override @@ -34,7 +39,9 @@ class _WebUnityWidgetViewState extends State { @override Widget build(BuildContext context) { - return WebViewWidget(controller: _controller); + return PlatformWebViewWidget( + PlatformWebViewWidgetCreationParams(controller: _controller), + ).build(context); } static String _getBasePath() { diff --git a/pubspec.yaml b/pubspec.yaml index d960eb7a9..083ab99bf 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -19,8 +19,8 @@ dependencies: flutter_plugin_android_lifecycle: ^2.0.7 stream_transform: ^2.0.0 plugin_platform_interface: ^2.1.2 - webview_flutter: ^4.0.0 - webview_flutter_web: ^0.2.2 + webview_flutter_web: ^0.2.2+4 + webview_flutter_platform_interface: ^2.0.0 web: '>=0.3.0 <2.0.0' # Needs to resolve to >=0.5.0 to use WebAssembly (WASM). # ffi: ^1.2.1 // required for windows support