11/* *
22 *
3- * Highcharts Flutter
3+ * Highcharts Flutter
44 *
5- * Copyright (c) 2023-2025, Highsoft AS
5+ * Copyright (c) 2023-2025, Highsoft AS
66 *
7- * License: www.highcharts.com/license
7+ * License: www.highcharts.com/license
88 *
99 * */
1010
@@ -34,82 +34,7 @@ class HighchartsView extends StatefulWidget {
3434 this .onLoading,
3535 this .onMounted,
3636 }) {
37- PlatformWebViewControllerCreationParams params;
38-
39- if (WebViewPlatform .instance is AndroidWebViewPlatform ) {
40- params = AndroidWebViewControllerCreationParams ();
41- } else if (WebViewPlatform .instance is WebKitWebViewPlatform ) {
42- params = WebKitWebViewControllerCreationParams ();
43- } else {
44- params = const PlatformWebViewControllerCreationParams ();
45- }
46-
47- webViewController = WebViewController .fromPlatformCreationParams (params);
48-
49- if (webViewController.platform is WebKitWebViewController ) {
50- (webViewController.platform as WebKitWebViewController )
51- .setInspectable (true );
52- }
53-
54- if (webViewController.platform is AndroidWebViewController ||
55- webViewController.platform is WebKitWebViewController ) {
56- webViewController
57- ..setBackgroundColor (const Color (0x00000000 ))
58- ..setJavaScriptMode (JavaScriptMode .unrestricted);
59- }
60-
61- webViewController
62- ..setNavigationDelegate (NavigationDelegate (onPageFinished: (_) {
63- if (onMounted != null ) {
64- onMounted !(this );
65- }
66- }))
67- ..addJavaScriptChannel (
68- 'highcharts_flutter_channel' ,
69- onMessageReceived: (e) async {
70- final dataIndex = e.message.indexOf ('\n ' );
71-
72- final callbackKey =
73- (dataIndex < 0 ? e.message : e.message.substring (0 , dataIndex));
74- final data = (dataIndex < 0
75- ? []
76- : (jsonDecode (e.message.substring (dataIndex + 1 )) ?? []));
77-
78- if (callbackKey == 'highcharts_flutter.chart' ) {
79- if (onLoaded != null ) {
80- onLoaded !(this );
81- }
82- return ;
83- }
84-
85- if (HighchartsCallback .registry.containsKey (callbackKey)) {
86- HighchartsCallback .registry[callbackKey]! (data);
87- return ;
88- }
89-
90- if (_events.containsKey (callbackKey)) {
91- for (final callback in _events[callbackKey]! ) {
92- await callback (data);
93- }
94- return ;
95- }
96-
97- if (debug) {
98- debugPrint ('Unhandled callback: ${e .message }' );
99- }
100- },
101- );
102-
103- if (WebViewPlatform .instance is AndroidWebViewPlatform ) {
104- webView = WebViewWidget .fromPlatformCreationParams (
105- params: AndroidWebViewWidgetCreationParams (
106- controller: webViewController.platform,
107- displayWithHybridComposition: true , // Fix scrolling interference
108- ),
109- );
110- } else {
111- webView = WebViewWidget (controller: webViewController);
112- }
37+ _stateBridge = _HighchartsViewStateBridge (this );
11338 }
11439
11540 final String ? body;
@@ -132,9 +57,15 @@ class HighchartsView extends StatefulWidget {
13257
13358 final void Function (HighchartsView )? onMounted;
13459
135- late final WebViewWidget webView ;
60+ late final _HighchartsViewStateBridge _stateBridge ;
13661
137- late final WebViewController webViewController;
62+ WebViewController get webViewController {
63+ return _stateBridge.webViewController;
64+ }
65+
66+ WebViewWidget get webView {
67+ return _stateBridge.webView;
68+ }
13869
13970 @override
14071 State <HighchartsView > createState () {
@@ -175,11 +106,17 @@ class HighchartsView extends StatefulWidget {
175106class _HighchartsViewState extends State <HighchartsView >
176107 with AutomaticKeepAliveClientMixin {
177108 List <String > _assets = [];
109+
178110 bool _disposed = false ;
111+
179112 bool _prepared = false ;
180113
114+ late _HighchartsViewStateBridge _stateBridge;
115+
181116 @override
182- bool get wantKeepAlive => widget.keepAlive;
117+ bool get wantKeepAlive {
118+ return widget.keepAlive;
119+ }
183120
184121 @override
185122 Widget build (BuildContext context) {
@@ -191,24 +128,27 @@ class _HighchartsViewState extends State<HighchartsView>
191128 'packages/highcharts_flutter/assets/highcharts_flutter.js' ,
192129 'packages/highcharts_flutter/assets/highcharts_view.html' ,
193130 ], assetBundle: DefaultAssetBundle .of (context))
194- .then ((loadedAssets) => {
195- if (! _disposed)
196- {
197- setState (() {
198- _assets = loadedAssets;
199- })
200- }
201- });
131+ .then ((loadedAssets) {
132+ if (! _disposed) {
133+ setState (() {
134+ _assets = loadedAssets;
135+ });
136+ }
137+ });
138+
139+ if (widget.onLoading != null ) {
140+ return widget.onLoading !(widget);
141+ }
142+
143+ return Container ();
202144 } catch (error) {
203145 if (widget.onError != null ) {
204146 return widget.onError !(widget, error);
205147 }
206148
207149 throw error;
208150 }
209- }
210-
211- if (_assets.isNotEmpty && ! _prepared) {
151+ } else if (! _prepared) {
212152 try {
213153 (() async {
214154 final String bridge = _assets[0 ];
@@ -218,14 +158,16 @@ class _HighchartsViewState extends State<HighchartsView>
218158 (widget.body ?? '' ) + HighchartsHelpers .scriptTag (bridge))
219159 .replaceAll ('{HIGHCHARTS_VIEW_FOOT}' , widget.foot ?? '' );
220160
221- await widget .webViewController.loadHtmlString (html);
161+ await _stateBridge .webViewController.loadHtmlString (html);
222162
223163 if (! _disposed) {
224164 setState (() {
225165 _prepared = true ;
226166 });
227167 }
228168 })();
169+
170+ return Container ();
229171 } catch (error) {
230172 if (widget.onError != null ) {
231173 return widget.onError !(widget, error);
@@ -235,26 +177,147 @@ class _HighchartsViewState extends State<HighchartsView>
235177 }
236178 }
237179
238- if (_assets.isEmpty || ! _prepared) {
239- if (widget.onLoading != null ) {
240- return widget.onLoading !(widget);
241- }
242-
243- return Container ();
244- }
245-
246- return widget.webView;
180+ return _stateBridge.webView;
247181 }
248182
249183 @override
250184 void dispose () {
251- super .dispose ();
252185 _disposed = true ;
186+ widget._stateBridge.dispose ();
187+ super .dispose ();
188+ }
189+
190+ @override
191+ void didUpdateWidget (HighchartsView oldWidget) {
192+ super .didUpdateWidget (oldWidget);
193+ widget._stateBridge.transfer (_stateBridge);
253194 }
254195
255196 @override
256197 void initState () {
257198 super .initState ();
258199 _disposed = false ;
200+ _stateBridge = widget._stateBridge;
201+ }
202+ }
203+
204+ /// Manages the exchange between state and widget for optimized rendering tasks.
205+ /// The widget can be assigned to new states, while the state can be
206+ /// assigned to new widgets.
207+ class _HighchartsViewStateBridge {
208+ _HighchartsViewStateBridge (
209+ this .widget,
210+ );
211+
212+ WebViewWidget ? _webView;
213+
214+ WebViewWidget get webView {
215+ if (_webView == null ) {
216+ init ();
217+ }
218+ return _webView! ;
219+ }
220+
221+ WebViewController ? _webViewController;
222+
223+ WebViewController get webViewController {
224+ if (_webViewController == null ) {
225+ init ();
226+ }
227+ return _webViewController! ;
228+ }
229+
230+ HighchartsView widget;
231+
232+ void dispose () {
233+ this ._webView = null ;
234+ this ._webViewController = null ;
235+ }
236+
237+ void init () {
238+ HighchartsView widget = this .widget;
239+ PlatformWebViewControllerCreationParams params;
240+
241+ if (WebViewPlatform .instance is AndroidWebViewPlatform ) {
242+ params = AndroidWebViewControllerCreationParams ();
243+ } else if (WebViewPlatform .instance is WebKitWebViewPlatform ) {
244+ params = WebKitWebViewControllerCreationParams ();
245+ } else {
246+ params = const PlatformWebViewControllerCreationParams ();
247+ }
248+
249+ _webViewController = WebViewController .fromPlatformCreationParams (params);
250+
251+ if (webViewController.platform is WebKitWebViewController ) {
252+ (webViewController.platform as WebKitWebViewController )
253+ .setInspectable (true );
254+ }
255+
256+ if (webViewController.platform is AndroidWebViewController ||
257+ webViewController.platform is WebKitWebViewController ) {
258+ webViewController
259+ ..setBackgroundColor (const Color (0x00000000 ))
260+ ..setJavaScriptMode (JavaScriptMode .unrestricted);
261+ }
262+
263+ webViewController
264+ ..setNavigationDelegate (NavigationDelegate (onPageFinished: (_) {
265+ if (widget.onMounted != null ) {
266+ // Use `widget` to access properties of the *current* widget
267+ widget.onMounted !(widget);
268+ }
269+ }))
270+ ..addJavaScriptChannel (
271+ 'highcharts_flutter_channel' ,
272+ onMessageReceived: (e) async {
273+ final dataIndex = e.message.indexOf ('\n ' );
274+
275+ final callbackKey =
276+ (dataIndex < 0 ? e.message : e.message.substring (0 , dataIndex));
277+ final data = (dataIndex < 0
278+ ? []
279+ : (jsonDecode (e.message.substring (dataIndex + 1 )) ?? []));
280+
281+ if (callbackKey == 'highcharts_flutter.chart' ) {
282+ if (widget.onLoaded != null ) {
283+ widget.onLoaded !(widget);
284+ }
285+ return ;
286+ }
287+
288+ if (HighchartsCallback .registry.containsKey (callbackKey)) {
289+ HighchartsCallback .registry[callbackKey]! (data);
290+ return ;
291+ }
292+
293+ // Use `widget._events` to access the map on the current widget
294+ if (widget._events.containsKey (callbackKey)) {
295+ for (final callback in widget._events[callbackKey]! ) {
296+ await callback (data);
297+ }
298+ return ;
299+ }
300+
301+ if (widget.debug) {
302+ debugPrint ('Unhandled callback: ${e .message }' );
303+ }
304+ },
305+ );
306+
307+ if (WebViewPlatform .instance is AndroidWebViewPlatform ) {
308+ _webView = WebViewWidget .fromPlatformCreationParams (
309+ params: AndroidWebViewWidgetCreationParams (
310+ controller: webViewController.platform,
311+ displayWithHybridComposition: true , // Fix scrolling interference
312+ ),
313+ );
314+ } else {
315+ _webView = WebViewWidget (controller: webViewController);
316+ }
317+ }
318+
319+ void transfer (_HighchartsViewStateBridge oldStateBridge) {
320+ _webView = oldStateBridge._webView;
321+ _webViewController = oldStateBridge._webViewController;
259322 }
260323}
0 commit comments