1// Copyright 2014 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import 'package:flutter/foundation.dart';
6import 'package:flutter/gestures.dart';
7import 'package:flutter/rendering.dart';
8import 'package:flutter/scheduler.dart';
9import 'package:flutter/services.dart';
10
11import '_html_element_view_io.dart' if (dart.library.js_util) '_html_element_view_web.dart';
12import 'basic.dart';
13import 'debug.dart';
14import 'focus_manager.dart';
15import 'focus_scope.dart';
16import 'framework.dart';
17
18// Examples can assume:
19// PlatformViewController createFooWebView(PlatformViewCreationParams params) { return (null as dynamic) as PlatformViewController; }
20// Set> gestureRecognizers = >{};
21// late PlatformViewController _controller;
22
23/// Embeds an Android view in the Widget hierarchy.
24///
25/// Requires Android API level 23 or greater.
26///
27/// Embedding Android views is an expensive operation and should be avoided when a Flutter
28/// equivalent is possible.
29///
30/// The embedded Android view is painted just like any other Flutter widget and transformations
31/// apply to it as well.
32///
33/// {@template flutter.widgets.AndroidView.layout}
34/// The widget fills all available space, the parent of this object must provide bounded layout
35/// constraints.
36/// {@endtemplate}
37///
38/// {@template flutter.widgets.AndroidView.gestures}
39/// The widget participates in Flutter's gesture arenas, and dispatches touch events to the
40/// platform view iff it won the arena. Specific gestures that should be dispatched to the platform
41/// view can be specified in the `gestureRecognizers` constructor parameter. If
42/// the set of gesture recognizers is empty, a gesture will be dispatched to the platform
43/// view iff it was not claimed by any other gesture recognizer.
44/// {@endtemplate}
45///
46/// The Android view object is created using a [PlatformViewFactory](/javadoc/io/flutter/plugin/platform/PlatformViewFactory.html).
47/// Plugins can register platform view factories with [PlatformViewRegistry#registerViewFactory](/javadoc/io/flutter/plugin/platform/PlatformViewRegistry.html#registerViewFactory-java.lang.String-io.flutter.plugin.platform.PlatformViewFactory-).
48///
49/// Registration is typically done in the plugin's registerWith method, e.g:
50///
51/// ```java
52/// public static void registerWith(Registrar registrar) {
53/// registrar.platformViewRegistry().registerViewFactory("webview", WebViewFactory(registrar.messenger()));
54/// }
55/// ```
56///
57/// {@template flutter.widgets.AndroidView.lifetime}
58/// The platform view's lifetime is the same as the lifetime of the [State] object for this widget.
59/// When the [State] is disposed the platform view (and auxiliary resources) are lazily
60/// released (some resources are immediately released and some by platform garbage collector).
61/// A stateful widget's state is disposed when the widget is removed from the tree or when it is
62/// moved within the tree. If the stateful widget has a key and it's only moved relative to its siblings,
63/// or it has a [GlobalKey] and it's moved within the tree, it will not be disposed.
64/// {@endtemplate}
65class AndroidView extends StatefulWidget {
66 /// Creates a widget that embeds an Android view.
67 ///
68 /// {@template flutter.widgets.AndroidView.constructorArgs}
69 /// If `creationParams` is not null then `creationParamsCodec` must not be null.
70 /// {@endtemplate}
71 const AndroidView({
72 super.key,
73 required this.viewType,
74 this.onPlatformViewCreated,
75 this.hitTestBehavior = PlatformViewHitTestBehavior.opaque,
76 this.layoutDirection,
77 this.gestureRecognizers,
78 this.creationParams,
79 this.creationParamsCodec,
80 this.clipBehavior = Clip.hardEdge,
81 }) : assert(creationParams == null || creationParamsCodec != null);
82
83 /// The unique identifier for Android view type to be embedded by this widget.
84 ///
85 /// A [PlatformViewFactory](/javadoc/io/flutter/plugin/platform/PlatformViewFactory.html)
86 /// for this type must have been registered.
87 ///
88 /// See also:
89 ///
90 /// * [AndroidView] for an example of registering a platform view factory.
91 final String viewType;
92
93 /// {@template flutter.widgets.AndroidView.onPlatformViewCreated}
94 /// Callback to invoke after the platform view has been created.
95 ///
96 /// May be null.
97 /// {@endtemplate}
98 final PlatformViewCreatedCallback? onPlatformViewCreated;
99
100 /// {@template flutter.widgets.AndroidView.hitTestBehavior}
101 /// How this widget should behave during hit testing.
102 ///
103 /// This defaults to [PlatformViewHitTestBehavior.opaque].
104 /// {@endtemplate}
105 final PlatformViewHitTestBehavior hitTestBehavior;
106
107 /// {@template flutter.widgets.AndroidView.layoutDirection}
108 /// The text direction to use for the embedded view.
109 ///
110 /// If this is null, the ambient [Directionality] is used instead.
111 /// {@endtemplate}
112 final TextDirection? layoutDirection;
113
114 /// Which gestures should be forwarded to the Android view.
115 ///
116 /// {@template flutter.widgets.AndroidView.gestureRecognizers.descHead}
117 /// The gesture recognizers built by factories in this set participate in the gesture arena for
118 /// each pointer that was put down on the widget. If any of these recognizers win the
119 /// gesture arena, the entire pointer event sequence starting from the pointer down event
120 /// will be dispatched to the platform view.
121 ///
122 /// When null, an empty set of gesture recognizer factories is used, in which case a pointer event sequence
123 /// will only be dispatched to the platform view if no other member of the arena claimed it.
124 /// {@endtemplate}
125 ///
126 /// For example, with the following setup vertical drags will not be dispatched to the Android
127 /// view as the vertical drag gesture is claimed by the parent [GestureDetector].
128 ///
129 /// ```dart
130 /// GestureDetector(
131 /// onVerticalDragStart: (DragStartDetails d) {},
132 /// child: const AndroidView(
133 /// viewType: 'webview',
134 /// ),
135 /// )
136 /// ```
137 ///
138 /// To get the [AndroidView] to claim the vertical drag gestures we can pass a vertical drag
139 /// gesture recognizer factory in [gestureRecognizers] e.g:
140 ///
141 /// ```dart
142 /// GestureDetector(
143 /// onVerticalDragStart: (DragStartDetails details) {},
144 /// child: SizedBox(
145 /// width: 200.0,
146 /// height: 100.0,
147 /// child: AndroidView(
148 /// viewType: 'webview',
149 /// gestureRecognizers: <Factory<OneSequenceGestureRecognizer>>{
150 /// Factory<OneSequenceGestureRecognizer>(
151 /// () => EagerGestureRecognizer(),
152 /// ),
153 /// },
154 /// ),
155 /// ),
156 /// )
157 /// ```
158 ///
159 /// {@template flutter.widgets.AndroidView.gestureRecognizers.descFoot}
160 /// A platform view can be configured to consume all pointers that were put
161 /// down in its bounds by passing a factory for an [EagerGestureRecognizer] in
162 /// [gestureRecognizers]. [EagerGestureRecognizer] is a special gesture
163 /// recognizer that immediately claims the gesture after a pointer down event.
164 ///
165 /// The [gestureRecognizers] property must not contain more than one factory
166 /// with the same [Factory.type].
167 ///
168 /// Changing [gestureRecognizers] results in rejection of any active gesture
169 /// arenas (if the platform view is actively participating in an arena).
170 /// {@endtemplate}
171 // We use OneSequenceGestureRecognizers as they support gesture arena teams.
172 // TODO(amirh): get a list of GestureRecognizers here.
173 // https://github.com/flutter/flutter/issues/20953
174 final Set<Factory<OneSequenceGestureRecognizer>>? gestureRecognizers;
175
176 /// Passed as the args argument of [PlatformViewFactory#create](/javadoc/io/flutter/plugin/platform/PlatformViewFactory.html#create-android.content.Context-int-java.lang.Object-)
177 ///
178 /// This can be used by plugins to pass constructor parameters to the embedded Android view.
179 final dynamic creationParams;
180
181 /// The codec used to encode `creationParams` before sending it to the
182 /// platform side. It should match the codec passed to the constructor of [PlatformViewFactory](/javadoc/io/flutter/plugin/platform/PlatformViewFactory.html#PlatformViewFactory-io.flutter.plugin.common.MessageCodec-).
183 ///
184 /// This is typically one of: [StandardMessageCodec], [JSONMessageCodec], [StringCodec], or [BinaryCodec].
185 ///
186 /// This must not be null if [creationParams] is not null.
187 final MessageCodec<dynamic>? creationParamsCodec;
188
189 /// {@macro flutter.material.Material.clipBehavior}
190 ///
191 /// Defaults to [Clip.hardEdge].
192 final Clip clipBehavior;
193
194 @override
195 State<AndroidView> createState() => _AndroidViewState();
196}
197
198/// Common superclass for iOS and macOS platform views.
199///
200/// Platform views are used to embed native views in the widget hierarchy, with
201/// support for transforms, clips, and opacity similar to any other Flutter widget.
202abstract class _DarwinView extends StatefulWidget {
203 /// Creates a widget that embeds a platform view.
204 ///
205 /// {@macro flutter.widgets.AndroidView.constructorArgs}
206 const _DarwinView({
207 super.key,
208 required this.viewType,
209 this.onPlatformViewCreated,
210 this.hitTestBehavior = PlatformViewHitTestBehavior.opaque,
211 this.layoutDirection,
212 this.creationParams,
213 this.creationParamsCodec,
214 this.gestureRecognizers,
215 }) : assert(creationParams == null || creationParamsCodec != null);
216
217 // TODO(amirh): reference the iOS API doc once available.
218 /// The unique identifier for iOS view type to be embedded by this widget.
219 ///
220 /// A PlatformViewFactory for this type must have been registered.
221 final String viewType;
222
223 /// {@macro flutter.widgets.AndroidView.onPlatformViewCreated}
224 final PlatformViewCreatedCallback? onPlatformViewCreated;
225
226 /// {@macro flutter.widgets.AndroidView.hitTestBehavior}
227 final PlatformViewHitTestBehavior hitTestBehavior;
228
229 /// {@macro flutter.widgets.AndroidView.layoutDirection}
230 final TextDirection? layoutDirection;
231
232 /// Passed as the `arguments` argument of [-\[FlutterPlatformViewFactory createWithFrame:viewIdentifier:arguments:\]](/ios-embedder/protocol_flutter_platform_view_factory-p.html#a4e3c4390cd6ebd982390635e9bca4edc)
233 ///
234 /// This can be used by plugins to pass constructor parameters to the embedded iOS view.
235 final dynamic creationParams;
236
237 /// The codec used to encode `creationParams` before sending it to the
238 /// platform side. It should match the codec returned by [-\[FlutterPlatformViewFactory createArgsCodec:\]](/ios-embedder/protocol_flutter_platform_view_factory-p.html#a32c3c067cb45a83dfa720c74a0d5c93c)
239 ///
240 /// This is typically one of: [StandardMessageCodec], [JSONMessageCodec], [StringCodec], or [BinaryCodec].
241 ///
242 /// This must not be null if [creationParams] is not null.
243 final MessageCodec<dynamic>? creationParamsCodec;
244
245 /// Which gestures should be forwarded to the UIKit view.
246 ///
247 /// {@macro flutter.widgets.AndroidView.gestureRecognizers.descHead}
248 ///
249 /// For example, with the following setup vertical drags will not be dispatched to the UIKit
250 /// view as the vertical drag gesture is claimed by the parent [GestureDetector].
251 ///
252 /// ```dart
253 /// GestureDetector(
254 /// onVerticalDragStart: (DragStartDetails details) {},
255 /// child: const UiKitView(
256 /// viewType: 'webview',
257 /// ),
258 /// )
259 /// ```
260 ///
261 /// To get the [UiKitView] to claim the vertical drag gestures we can pass a vertical drag
262 /// gesture recognizer factory in [gestureRecognizers] e.g:
263 ///
264 /// ```dart
265 /// GestureDetector(
266 /// onVerticalDragStart: (DragStartDetails details) {},
267 /// child: SizedBox(
268 /// width: 200.0,
269 /// height: 100.0,
270 /// child: UiKitView(
271 /// viewType: 'webview',
272 /// gestureRecognizers: <Factory<OneSequenceGestureRecognizer>>{
273 /// Factory<OneSequenceGestureRecognizer>(
274 /// () => EagerGestureRecognizer(),
275 /// ),
276 /// },
277 /// ),
278 /// ),
279 /// )
280 /// ```
281 ///
282 /// {@macro flutter.widgets.AndroidView.gestureRecognizers.descFoot}
283 // We use OneSequenceGestureRecognizers as they support gesture arena teams.
284 // TODO(amirh): get a list of GestureRecognizers here.
285 // https://github.com/flutter/flutter/issues/20953
286 final Set<Factory<OneSequenceGestureRecognizer>>? gestureRecognizers;
287}
288
289// TODO(amirh): describe the embedding mechanism.
290// TODO(ychris): remove the documentation for conic path not supported once https://github.com/flutter/flutter/issues/35062 is resolved.
291/// Embeds an iOS view in the Widget hierarchy.
292///
293/// Embedding iOS views is an expensive operation and should be avoided when a Flutter
294/// equivalent is possible.
295///
296/// {@macro flutter.widgets.AndroidView.layout}
297///
298/// {@macro flutter.widgets.AndroidView.gestures}
299///
300/// {@macro flutter.widgets.AndroidView.lifetime}
301///
302/// Construction of UIViews is done asynchronously, before the UIView is ready this widget paints
303/// nothing while maintaining the same layout constraints.
304///
305/// Clipping operations on a UiKitView can result slow performance.
306/// If a conic path clipping is applied to a UIKitView,
307/// a quad path is used to approximate the clip due to limitation of Quartz.
308class UiKitView extends _DarwinView {
309 /// Creates a widget that embeds an iOS view.
310 ///
311 /// {@macro flutter.widgets.AndroidView.constructorArgs}
312 const UiKitView({
313 super.key,
314 required super.viewType,
315 super.onPlatformViewCreated,
316 super.hitTestBehavior = PlatformViewHitTestBehavior.opaque,
317 super.layoutDirection,
318 super.creationParams,
319 super.creationParamsCodec,
320 super.gestureRecognizers,
321 }) : assert(creationParams == null || creationParamsCodec != null);
322
323 @override
324 State<UiKitView> createState() => _UiKitViewState();
325}
326
327/// Widget that contains a macOS AppKit view.
328///
329/// Embedding macOS views is an expensive operation and should be avoided where
330/// a Flutter equivalent is possible.
331///
332/// The platform view's lifetime is the same as the lifetime of the [State]
333/// object for this widget. When the [State] is disposed the platform view (and
334/// auxiliary resources) are lazily released (some resources are immediately
335/// released and some by platform garbage collector). A stateful widget's state
336/// is disposed when the widget is removed from the tree or when it is moved
337/// within the tree. If the stateful widget has a key and it's only moved
338/// relative to its siblings, or it has a [GlobalKey] and it's moved within the
339/// tree, it will not be disposed.
340///
341/// Construction of AppKitViews is done asynchronously, before the underlying
342/// NSView is ready this widget paints nothing while maintaining the same
343/// layout constraints.
344class AppKitView extends _DarwinView {
345 /// Creates a widget that embeds a macOS AppKit NSView.
346 const AppKitView({
347 super.key,
348 required super.viewType,
349 super.onPlatformViewCreated,
350 super.hitTestBehavior = PlatformViewHitTestBehavior.opaque,
351 super.layoutDirection,
352 super.creationParams,
353 super.creationParamsCodec,
354 super.gestureRecognizers,
355 });
356
357 @override
358 State<AppKitView> createState() => _AppKitViewState();
359}
360
361/// Callback signature for when the platform view's DOM element was created.
362///
363/// [element] is the DOM element that was created.
364///
365/// Also see [HtmlElementView.fromTagName] that uses this callback
366/// signature.
367typedef ElementCreatedCallback = void Function(Object element);
368
369/// Embeds an HTML element in the Widget hierarchy in Flutter Web.
370///
371/// *NOTE*: This only works in Flutter Web. To embed web content on other
372/// platforms, consider using the `flutter_webview` plugin.
373///
374/// Embedding HTML is an expensive operation and should be avoided when a
375/// Flutter equivalent is possible.
376///
377/// The embedded HTML is painted just like any other Flutter widget and
378/// transformations apply to it as well. This widget should only be used in
379/// Flutter Web.
380///
381/// {@macro flutter.widgets.AndroidView.layout}
382///
383/// Due to security restrictions with cross-origin `<iframe>` elements, Flutter
384/// cannot dispatch pointer events to an HTML view. If an `<iframe>` is the
385/// target of an event, the window containing the `<iframe>` is not notified
386/// of the event. In particular, this means that any pointer events which land
387/// on an `<iframe>` will not be seen by Flutter, and so the HTML view cannot
388/// participate in gesture detection with other widgets.
389///
390/// The way we enable accessibility on Flutter for web is to have a full-page
391/// button which waits for a double tap. Placing this full-page button in front
392/// of the scene would cause platform views not to receive pointer events. The
393/// tradeoff is that by placing the scene in front of the semantics placeholder
394/// will cause platform views to block pointer events from reaching the
395/// placeholder. This means that in order to enable accessibility, you must
396/// double tap the app *outside of a platform view*. As a consequence, a
397/// full-screen platform view will make it impossible to enable accessibility.
398/// Make sure that your HTML views are sized no larger than necessary, or you
399/// may cause difficulty for users trying to enable accessibility.
400///
401/// {@macro flutter.widgets.AndroidView.lifetime}
402class HtmlElementView extends StatelessWidget {
403 /// Creates a platform view for Flutter Web.
404 ///
405 /// `viewType` identifies the type of platform view to create.
406 const HtmlElementView({
407 super.key,
408 required this.viewType,
409 this.onPlatformViewCreated,
410 this.creationParams,
411 });
412
413 /// Creates a platform view that creates a DOM element specified by [tagName].
414 ///
415 /// [isVisible] indicates whether the view is visible to the user or not.
416 /// Setting this to false allows the rendering pipeline to perform extra
417 /// optimizations knowing that the view will not result in any pixels painted
418 /// on the screen.
419 ///
420 /// [onElementCreated] is called when the DOM element is created. It can be
421 /// used by the app to customize the element by adding attributes and styles.
422 factory HtmlElementView.fromTagName({
423 Key? key,
424 required String tagName,
425 bool isVisible = true,
426 ElementCreatedCallback? onElementCreated,
427 }) =>
428 HtmlElementViewImpl.createFromTagName(
429 key: key,
430 tagName: tagName,
431 isVisible: isVisible,
432 onElementCreated: onElementCreated,
433 );
434
435 /// The unique identifier for the HTML view type to be embedded by this widget.
436 ///
437 /// A PlatformViewFactory for this type must have been registered.
438 final String viewType;
439
440 /// Callback to invoke after the platform view has been created.
441 ///
442 /// May be null.
443 final PlatformViewCreatedCallback? onPlatformViewCreated;
444
445 /// Passed as the 2nd argument (i.e. `params`) of the registered view factory.
446 final Object? creationParams;
447
448 @override
449 Widget build(BuildContext context) => buildImpl(context);
450}
451
452class _AndroidViewState extends State<AndroidView> {
453 int? _id;
454 late AndroidViewController _controller;
455 TextDirection? _layoutDirection;
456 bool _initialized = false;
457 FocusNode? _focusNode;
458
459 static final Set<Factory<OneSequenceGestureRecognizer>> _emptyRecognizersSet =
460 <Factory<OneSequenceGestureRecognizer>>{};
461
462 @override
463 Widget build(BuildContext context) {
464 return Focus(
465 focusNode: _focusNode,
466 onFocusChange: _onFocusChange,
467 child: _AndroidPlatformView(
468 controller: _controller,
469 hitTestBehavior: widget.hitTestBehavior,
470 gestureRecognizers: widget.gestureRecognizers ?? _emptyRecognizersSet,
471 clipBehavior: widget.clipBehavior,
472 ),
473 );
474 }
475
476 void _initializeOnce() {
477 if (_initialized) {
478 return;
479 }
480 _initialized = true;
481 _createNewAndroidView();
482 _focusNode = FocusNode(debugLabel: 'AndroidView(id: $_id)');
483 }
484
485 @override
486 void didChangeDependencies() {
487 super.didChangeDependencies();
488 final TextDirection newLayoutDirection = _findLayoutDirection();
489 final bool didChangeLayoutDirection = _layoutDirection != newLayoutDirection;
490 _layoutDirection = newLayoutDirection;
491
492 _initializeOnce();
493 if (didChangeLayoutDirection) {
494 // The native view will update asynchronously, in the meantime we don't want
495 // to block the framework. (so this is intentionally not awaiting).
496 _controller.setLayoutDirection(_layoutDirection!);
497 }
498 }
499
500 @override
501 void didUpdateWidget(AndroidView oldWidget) {
502 super.didUpdateWidget(oldWidget);
503
504 final TextDirection newLayoutDirection = _findLayoutDirection();
505 final bool didChangeLayoutDirection = _layoutDirection != newLayoutDirection;
506 _layoutDirection = newLayoutDirection;
507
508 if (widget.viewType != oldWidget.viewType) {
509 _controller.disposePostFrame();
510 _createNewAndroidView();
511 return;
512 }
513
514 if (didChangeLayoutDirection) {
515 _controller.setLayoutDirection(_layoutDirection!);
516 }
517 }
518
519 TextDirection _findLayoutDirection() {
520 assert(widget.layoutDirection != null || debugCheckHasDirectionality(context));
521 return widget.layoutDirection ?? Directionality.of(context);
522 }
523
524 @override
525 void dispose() {
526 _controller.dispose();
527 _focusNode?.dispose();
528 _focusNode = null;
529 super.dispose();
530 }
531
532 void _createNewAndroidView() {
533 _id = platformViewsRegistry.getNextPlatformViewId();
534 _controller = PlatformViewsService.initAndroidView(
535 id: _id!,
536 viewType: widget.viewType,
537 layoutDirection: _layoutDirection!,
538 creationParams: widget.creationParams,
539 creationParamsCodec: widget.creationParamsCodec,
540 onFocus: () {
541 _focusNode!.requestFocus();
542 },
543 );
544 if (widget.onPlatformViewCreated != null) {
545 _controller.addOnPlatformViewCreatedListener(widget.onPlatformViewCreated!);
546 }
547 }
548
549 void _onFocusChange(bool isFocused) {
550 if (!_controller.isCreated) {
551 return;
552 }
553 if (!isFocused) {
554 _controller.clearFocus().catchError((dynamic e) {
555 if (e is MissingPluginException) {
556 // We land the framework part of Android platform views keyboard
557 // support before the engine part. There will be a commit range where
558 // clearFocus isn't implemented in the engine. When that happens we
559 // just swallow the error here. Once the engine part is rolled to the
560 // framework I'll remove this.
561 // TODO(amirh): remove this once the engine's clearFocus is rolled.
562 return;
563 }
564 });
565 return;
566 }
567 SystemChannels.textInput.invokeMethod<void>(
568 'TextInput.setPlatformViewClient',
569 <String, dynamic>{'platformViewId': _id},
570 ).catchError((dynamic e) {
571 if (e is MissingPluginException) {
572 // We land the framework part of Android platform views keyboard
573 // support before the engine part. There will be a commit range where
574 // setPlatformViewClient isn't implemented in the engine. When that
575 // happens we just swallow the error here. Once the engine part is
576 // rolled to the framework I'll remove this.
577 // TODO(amirh): remove this once the engine's clearFocus is rolled.
578 return;
579 }
580 });
581 }
582}
583
584abstract class _DarwinViewState<PlatformViewT extends _DarwinView, ControllerT extends DarwinPlatformViewController, RenderT extends RenderDarwinPlatformView<ControllerT>, ViewT extends _DarwinPlatformView<ControllerT, RenderT>> extends State<PlatformViewT> {
585 ControllerT? _controller;
586 TextDirection? _layoutDirection;
587 bool _initialized = false;
588
589 @visibleForTesting
590 FocusNode? focusNode;
591
592 static final Set<Factory<OneSequenceGestureRecognizer>> _emptyRecognizersSet =
593 <Factory<OneSequenceGestureRecognizer>>{};
594
595 @override
596 Widget build(BuildContext context) {
597 final ControllerT? controller = _controller;
598 if (controller == null) {
599 return const SizedBox.expand();
600 }
601 return Focus(
602 focusNode: focusNode,
603 onFocusChange: (bool isFocused) => _onFocusChange(isFocused, controller),
604 child: childPlatformView()
605 );
606 }
607
608 ViewT childPlatformView();
609
610 void _initializeOnce() {
611 if (_initialized) {
612 return;
613 }
614 _initialized = true;
615 _createNewUiKitView();
616 }
617
618 @override
619 void didChangeDependencies() {
620 super.didChangeDependencies();
621 final TextDirection newLayoutDirection = _findLayoutDirection();
622 final bool didChangeLayoutDirection = _layoutDirection != newLayoutDirection;
623 _layoutDirection = newLayoutDirection;
624
625 _initializeOnce();
626 if (didChangeLayoutDirection) {
627 // The native view will update asynchronously, in the meantime we don't want
628 // to block the framework. (so this is intentionally not awaiting).
629 _controller?.setLayoutDirection(_layoutDirection!);
630 }
631 }
632
633 @override
634 void didUpdateWidget(PlatformViewT oldWidget) {
635 super.didUpdateWidget(oldWidget);
636
637 final TextDirection newLayoutDirection = _findLayoutDirection();
638 final bool didChangeLayoutDirection = _layoutDirection != newLayoutDirection;
639 _layoutDirection = newLayoutDirection;
640
641 if (widget.viewType != oldWidget.viewType) {
642 _controller?.dispose();
643 _controller = null;
644 focusNode?.dispose();
645 focusNode = null;
646 _createNewUiKitView();
647 return;
648 }
649
650 if (didChangeLayoutDirection) {
651 _controller?.setLayoutDirection(_layoutDirection!);
652 }
653 }
654
655 TextDirection _findLayoutDirection() {
656 assert(widget.layoutDirection != null || debugCheckHasDirectionality(context));
657 return widget.layoutDirection ?? Directionality.of(context);
658 }
659
660 @override
661 void dispose() {
662 _controller?.dispose();
663 _controller = null;
664 focusNode?.dispose();
665 focusNode = null;
666 super.dispose();
667 }
668
669 Future<void> _createNewUiKitView() async {
670 final int id = platformViewsRegistry.getNextPlatformViewId();
671 final ControllerT controller = await createNewViewController(
672 id
673 );
674 if (!mounted) {
675 controller.dispose();
676 return;
677 }
678 widget.onPlatformViewCreated?.call(id);
679 setState(() {
680 _controller = controller;
681 focusNode = FocusNode(debugLabel: 'UiKitView(id: $id)');
682 });
683 }
684
685 Future<ControllerT> createNewViewController(int id);
686
687 void _onFocusChange(bool isFocused, ControllerT controller) {
688 if (!isFocused) {
689 // Unlike Android, we do not need to send "clearFocus" channel message
690 // to the engine, because focusing on another view will automatically
691 // cancel the focus on the previously focused platform view.
692 return;
693 }
694 SystemChannels.textInput.invokeMethod<void>(
695 'TextInput.setPlatformViewClient',
696 <String, dynamic>{'platformViewId': controller.id},
697 );
698 }
699}
700
701class _UiKitViewState extends _DarwinViewState<UiKitView, UiKitViewController, RenderUiKitView, _UiKitPlatformView> {
702 @override
703 Future<UiKitViewController> createNewViewController(int id) async {
704 return PlatformViewsService.initUiKitView(
705 id: id,
706 viewType: widget.viewType,
707 layoutDirection: _layoutDirection!,
708 creationParams: widget.creationParams,
709 creationParamsCodec: widget.creationParamsCodec,
710 onFocus: () {
711 focusNode?.requestFocus();
712 }
713 );
714 }
715
716 @override
717 _UiKitPlatformView childPlatformView() {
718 return _UiKitPlatformView(
719 controller: _controller!,
720 hitTestBehavior: widget.hitTestBehavior,
721 gestureRecognizers: widget.gestureRecognizers ?? _DarwinViewState._emptyRecognizersSet,
722 );
723 }
724}
725
726class _AppKitViewState extends _DarwinViewState<AppKitView, AppKitViewController, RenderAppKitView, _AppKitPlatformView> {
727 @override
728 Future<AppKitViewController> createNewViewController(int id) async {
729 return PlatformViewsService.initAppKitView(
730 id: id,
731 viewType: widget.viewType,
732 layoutDirection: _layoutDirection!,
733 creationParams: widget.creationParams,
734 creationParamsCodec: widget.creationParamsCodec,
735 onFocus: () {
736 focusNode?.requestFocus();
737 }
738 );
739 }
740
741 @override
742 _AppKitPlatformView childPlatformView() {
743 return _AppKitPlatformView(
744 controller: _controller!,
745 hitTestBehavior: widget.hitTestBehavior,
746 gestureRecognizers: widget.gestureRecognizers ?? _DarwinViewState._emptyRecognizersSet,
747 );
748 }
749}
750
751class _AndroidPlatformView extends LeafRenderObjectWidget {
752 const _AndroidPlatformView({
753 required this.controller,
754 required this.hitTestBehavior,
755 required this.gestureRecognizers,
756 this.clipBehavior = Clip.hardEdge,
757 });
758
759 final AndroidViewController controller;
760 final PlatformViewHitTestBehavior hitTestBehavior;
761 final Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers;
762 final Clip clipBehavior;
763
764 @override
765 RenderObject createRenderObject(BuildContext context) =>
766 RenderAndroidView(
767 viewController: controller,
768 hitTestBehavior: hitTestBehavior,
769 gestureRecognizers: gestureRecognizers,
770 clipBehavior: clipBehavior,
771 );
772
773 @override
774 void updateRenderObject(BuildContext context, RenderAndroidView renderObject) {
775 renderObject.controller = controller;
776 renderObject.hitTestBehavior = hitTestBehavior;
777 renderObject.updateGestureRecognizers(gestureRecognizers);
778 renderObject.clipBehavior = clipBehavior;
779 }
780}
781
782abstract class _DarwinPlatformView<TController extends DarwinPlatformViewController, TRender extends RenderDarwinPlatformView<TController>> extends LeafRenderObjectWidget {
783 const _DarwinPlatformView({
784 required this.controller,
785 required this.hitTestBehavior,
786 required this.gestureRecognizers,
787 });
788
789 final TController controller;
790 final PlatformViewHitTestBehavior hitTestBehavior;
791 final Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers;
792
793 @override
794 @mustCallSuper
795 void updateRenderObject(BuildContext context, TRender renderObject) {
796 renderObject
797 ..viewController = controller
798 ..hitTestBehavior = hitTestBehavior
799 ..updateGestureRecognizers(gestureRecognizers);
800 }
801}
802
803class _UiKitPlatformView extends _DarwinPlatformView<UiKitViewController, RenderUiKitView> {
804 const _UiKitPlatformView({required super.controller, required super.hitTestBehavior, required super.gestureRecognizers});
805
806 @override
807 RenderObject createRenderObject(BuildContext context) {
808 return RenderUiKitView(
809 viewController: controller,
810 hitTestBehavior: hitTestBehavior,
811 gestureRecognizers: gestureRecognizers,
812 );
813 }
814}
815
816class _AppKitPlatformView extends _DarwinPlatformView<AppKitViewController, RenderAppKitView> {
817 const _AppKitPlatformView({required super.controller, required super.hitTestBehavior, required super.gestureRecognizers});
818
819 @override
820 RenderObject createRenderObject(BuildContext context) {
821 return RenderAppKitView(
822 viewController: controller,
823 hitTestBehavior: hitTestBehavior,
824 gestureRecognizers: gestureRecognizers,
825 );
826 }
827}
828
829/// The parameters used to create a [PlatformViewController].
830///
831/// See also:
832///
833/// * [CreatePlatformViewCallback] which uses this object to create a [PlatformViewController].
834class PlatformViewCreationParams {
835
836 const PlatformViewCreationParams._({
837 required this.id,
838 required this.viewType,
839 required this.onPlatformViewCreated,
840 required this.onFocusChanged,
841 });
842
843 /// The unique identifier for the new platform view.
844 ///
845 /// [PlatformViewController.viewId] should match this id.
846 final int id;
847
848 /// The unique identifier for the type of platform view to be embedded.
849 ///
850 /// This viewType is used to tell the platform which type of view to
851 /// associate with the [id].
852 final String viewType;
853
854 /// Callback invoked after the platform view has been created.
855 final PlatformViewCreatedCallback onPlatformViewCreated;
856
857 /// Callback invoked when the platform view's focus is changed on the platform side.
858 ///
859 /// The value is true when the platform view gains focus and false when it loses focus.
860 final ValueChanged<bool> onFocusChanged;
861}
862
863/// A factory for a surface presenting a platform view as part of the widget hierarchy.
864///
865/// The returned widget should present the platform view associated with `controller`.
866///
867/// See also:
868///
869/// * [PlatformViewSurface], a common widget for presenting platform views.
870typedef PlatformViewSurfaceFactory = Widget Function(BuildContext context, PlatformViewController controller);
871
872/// Constructs a [PlatformViewController].
873///
874/// The [PlatformViewController.viewId] field of the created controller must match the value of the
875/// params [PlatformViewCreationParams.id] field.
876///
877/// See also:
878///
879/// * [PlatformViewLink], which links a platform view with the Flutter framework.
880typedef CreatePlatformViewCallback = PlatformViewController Function(PlatformViewCreationParams params);
881
882/// Links a platform view with the Flutter framework.
883///
884/// Provides common functionality for embedding a platform view (e.g an android.view.View on Android)
885/// with the Flutter framework.
886///
887/// {@macro flutter.widgets.AndroidView.lifetime}
888///
889/// To implement a new platform view widget, return this widget in the `build` method.
890/// For example:
891///
892/// ```dart
893/// class FooPlatformView extends StatelessWidget {
894/// const FooPlatformView({super.key});
895/// @override
896/// Widget build(BuildContext context) {
897/// return PlatformViewLink(
898/// viewType: 'webview',
899/// onCreatePlatformView: createFooWebView,
900/// surfaceFactory: (BuildContext context, PlatformViewController controller) {
901/// return PlatformViewSurface(
902/// gestureRecognizers: gestureRecognizers,
903/// controller: controller,
904/// hitTestBehavior: PlatformViewHitTestBehavior.opaque,
905/// );
906/// },
907/// );
908/// }
909/// }
910/// ```
911///
912/// The `surfaceFactory` and the `onCreatePlatformView` are only called when the
913/// state of this widget is initialized, or when the `viewType` changes.
914class PlatformViewLink extends StatefulWidget {
915 /// Construct a [PlatformViewLink] widget.
916 ///
917 /// See also:
918 ///
919 /// * [PlatformViewSurface] for details on the widget returned by `surfaceFactory`.
920 /// * [PlatformViewCreationParams] for how each parameter can be used when implementing `createPlatformView`.
921 const PlatformViewLink({
922 super.key,
923 required PlatformViewSurfaceFactory surfaceFactory,
924 required CreatePlatformViewCallback onCreatePlatformView,
925 required this.viewType,
926 }) : _surfaceFactory = surfaceFactory,
927 _onCreatePlatformView = onCreatePlatformView;
928
929
930 final PlatformViewSurfaceFactory _surfaceFactory;
931 final CreatePlatformViewCallback _onCreatePlatformView;
932
933 /// The unique identifier for the view type to be embedded.
934 ///
935 /// Typically, this viewType has already been registered on the platform side.
936 final String viewType;
937
938 @override
939 State<StatefulWidget> createState() => _PlatformViewLinkState();
940}
941
942class _PlatformViewLinkState extends State<PlatformViewLink> {
943 int? _id;
944 PlatformViewController? _controller;
945 bool _platformViewCreated = false;
946 Widget? _surface;
947 FocusNode? _focusNode;
948
949 @override
950 Widget build(BuildContext context) {
951 final PlatformViewController? controller = _controller;
952 if (controller == null) {
953 return const SizedBox.expand();
954 }
955 if (!_platformViewCreated) {
956 // Depending on the implementation, the first non-empty size can be used
957 // to size the platform view.
958 return _PlatformViewPlaceHolder(onLayout: (Size size, Offset position) {
959 if (controller.awaitingCreation && !size.isEmpty) {
960 controller.create(size: size, position: position);
961 }
962 });
963 }
964 _surface ??= widget._surfaceFactory(context, controller);
965 return Focus(
966 focusNode: _focusNode,
967 onFocusChange: _handleFrameworkFocusChanged,
968 child: _surface!,
969 );
970 }
971
972 @override
973 void initState() {
974 _focusNode = FocusNode(debugLabel: 'PlatformView(id: $_id)');
975 _initialize();
976 super.initState();
977 }
978
979 @override
980 void didUpdateWidget(PlatformViewLink oldWidget) {
981 super.didUpdateWidget(oldWidget);
982
983 if (widget.viewType != oldWidget.viewType) {
984 _controller?.disposePostFrame();
985 // The _surface has to be recreated as its controller is disposed.
986 // Setting _surface to null will trigger its creation in build().
987 _surface = null;
988 _initialize();
989 }
990 }
991
992 void _initialize() {
993 _id = platformViewsRegistry.getNextPlatformViewId();
994 _controller = widget._onCreatePlatformView(
995 PlatformViewCreationParams._(
996 id: _id!,
997 viewType: widget.viewType,
998 onPlatformViewCreated: _onPlatformViewCreated,
999 onFocusChanged: _handlePlatformFocusChanged,
1000 ),
1001 );
1002 }
1003
1004 void _onPlatformViewCreated(int id) {
1005 if (mounted) {
1006 setState(() {
1007 _platformViewCreated = true;
1008 });
1009 }
1010 }
1011
1012 void _handleFrameworkFocusChanged(bool isFocused) {
1013 if (!isFocused) {
1014 _controller?.clearFocus();
1015 }
1016 SystemChannels.textInput.invokeMethod<void>(
1017 'TextInput.setPlatformViewClient',
1018 <String, dynamic>{'platformViewId': _id},
1019 );
1020 }
1021
1022 void _handlePlatformFocusChanged(bool isFocused) {
1023 if (isFocused) {
1024 _focusNode!.requestFocus();
1025 }
1026 }
1027
1028 @override
1029 void dispose() {
1030 _controller?.dispose();
1031 _controller = null;
1032 _focusNode?.dispose();
1033 _focusNode = null;
1034 super.dispose();
1035 }
1036}
1037
1038/// Integrates a platform view with Flutter's compositor, touch, and semantics subsystems.
1039///
1040/// The compositor integration is done by adding a [PlatformViewLayer] to the layer tree. [PlatformViewSurface]
1041/// isn't supported on all platforms (e.g on Android platform views can be composited by using a [TextureLayer] or
1042/// [AndroidViewSurface]).
1043/// Custom Flutter embedders can support [PlatformViewLayer]s by implementing a SystemCompositor.
1044///
1045/// The widget fills all available space, the parent of this object must provide bounded layout
1046/// constraints.
1047///
1048/// If the associated platform view is not created the [PlatformViewSurface] does not paint any contents.
1049///
1050/// See also:
1051///
1052/// * [AndroidView] which embeds an Android platform view in the widget hierarchy using a [TextureLayer].
1053/// * [UiKitView] which embeds an iOS platform view in the widget hierarchy.
1054// TODO(amirh): Link to the embedder's system compositor documentation once available.
1055class PlatformViewSurface extends LeafRenderObjectWidget {
1056
1057 /// Construct a [PlatformViewSurface].
1058 const PlatformViewSurface({
1059 super.key,
1060 required this.controller,
1061 required this.hitTestBehavior,
1062 required this.gestureRecognizers,
1063 });
1064
1065 /// The controller for the platform view integrated by this [PlatformViewSurface].
1066 ///
1067 /// [PlatformViewController] is used for dispatching touch events to the platform view.
1068 /// [PlatformViewController.viewId] identifies the platform view whose contents are painted by this widget.
1069 final PlatformViewController controller;
1070
1071 /// Which gestures should be forwarded to the PlatformView.
1072 ///
1073 /// {@macro flutter.widgets.AndroidView.gestureRecognizers.descHead}
1074 ///
1075 /// For example, with the following setup vertical drags will not be dispatched to the platform view
1076 /// as the vertical drag gesture is claimed by the parent [GestureDetector].
1077 ///
1078 /// ```dart
1079 /// GestureDetector(
1080 /// onVerticalDragStart: (DragStartDetails details) { },
1081 /// child: PlatformViewSurface(
1082 /// gestureRecognizers: const <Factory<OneSequenceGestureRecognizer>>{},
1083 /// controller: _controller,
1084 /// hitTestBehavior: PlatformViewHitTestBehavior.opaque,
1085 /// ),
1086 /// )
1087 /// ```
1088 ///
1089 /// To get the [PlatformViewSurface] to claim the vertical drag gestures we can pass a vertical drag
1090 /// gesture recognizer factory in [gestureRecognizers] e.g:
1091 ///
1092 /// ```dart
1093 /// GestureDetector(
1094 /// onVerticalDragStart: (DragStartDetails details) { },
1095 /// child: SizedBox(
1096 /// width: 200.0,
1097 /// height: 100.0,
1098 /// child: PlatformViewSurface(
1099 /// gestureRecognizers: <Factory<OneSequenceGestureRecognizer>>{
1100 /// Factory<OneSequenceGestureRecognizer>(
1101 /// () => EagerGestureRecognizer(),
1102 /// ),
1103 /// },
1104 /// controller: _controller,
1105 /// hitTestBehavior: PlatformViewHitTestBehavior.opaque,
1106 /// ),
1107 /// ),
1108 /// )
1109 /// ```
1110 ///
1111 /// {@macro flutter.widgets.AndroidView.gestureRecognizers.descFoot}
1112 // We use OneSequenceGestureRecognizers as they support gesture arena teams.
1113 // TODO(amirh): get a list of GestureRecognizers here.
1114 // https://github.com/flutter/flutter/issues/20953
1115 final Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers;
1116
1117 /// {@macro flutter.widgets.AndroidView.hitTestBehavior}
1118 final PlatformViewHitTestBehavior hitTestBehavior;
1119
1120 @override
1121 RenderObject createRenderObject(BuildContext context) {
1122 return PlatformViewRenderBox(controller: controller, gestureRecognizers: gestureRecognizers, hitTestBehavior: hitTestBehavior);
1123 }
1124
1125 @override
1126 void updateRenderObject(BuildContext context, PlatformViewRenderBox renderObject) {
1127 renderObject
1128 ..controller = controller
1129 ..hitTestBehavior = hitTestBehavior
1130 ..updateGestureRecognizers(gestureRecognizers);
1131 }
1132}
1133
1134/// Integrates an Android view with Flutter's compositor, touch, and semantics subsystems.
1135///
1136/// The compositor integration is done by adding a [TextureLayer] to the layer tree.
1137///
1138/// The parent of this object must provide bounded layout constraints.
1139///
1140/// If the associated platform view is not created, the [AndroidViewSurface] does not paint any contents.
1141///
1142/// When possible, you may want to use [AndroidView] directly, since it requires less boilerplate code
1143/// than [AndroidViewSurface], and there's no difference in performance, or other trade-off(s).
1144///
1145/// See also:
1146///
1147/// * [AndroidView] which embeds an Android platform view in the widget hierarchy.
1148/// * [UiKitView] which embeds an iOS platform view in the widget hierarchy.
1149class AndroidViewSurface extends StatefulWidget {
1150 /// Construct an `AndroidPlatformViewSurface`.
1151 const AndroidViewSurface({
1152 super.key,
1153 required this.controller,
1154 required this.hitTestBehavior,
1155 required this.gestureRecognizers,
1156 });
1157
1158 /// The controller for the platform view integrated by this [AndroidViewSurface].
1159 ///
1160 /// See [PlatformViewSurface.controller] for details.
1161 final AndroidViewController controller;
1162
1163 /// Which gestures should be forwarded to the PlatformView.
1164 ///
1165 /// See [PlatformViewSurface.gestureRecognizers] for details.
1166 final Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers;
1167
1168 /// {@macro flutter.widgets.AndroidView.hitTestBehavior}
1169 final PlatformViewHitTestBehavior hitTestBehavior;
1170
1171 @override
1172 State<StatefulWidget> createState() {
1173 return _AndroidViewSurfaceState();
1174 }
1175}
1176
1177class _AndroidViewSurfaceState extends State<AndroidViewSurface> {
1178 @override
1179 void initState() {
1180 super.initState();
1181 if (!widget.controller.isCreated) {
1182 // Schedule a rebuild once creation is complete and the final display
1183 // type is known.
1184 widget.controller.addOnPlatformViewCreatedListener(_onPlatformViewCreated);
1185 }
1186 }
1187
1188 @override
1189 void dispose() {
1190 widget.controller.removeOnPlatformViewCreatedListener(_onPlatformViewCreated);
1191 super.dispose();
1192 }
1193
1194 @override
1195 Widget build(BuildContext context) {
1196 if (widget.controller.requiresViewComposition) {
1197 return _PlatformLayerBasedAndroidViewSurface(
1198 controller: widget.controller,
1199 hitTestBehavior: widget.hitTestBehavior,
1200 gestureRecognizers: widget.gestureRecognizers,
1201 );
1202 } else {
1203 return _TextureBasedAndroidViewSurface(
1204 controller: widget.controller,
1205 hitTestBehavior: widget.hitTestBehavior,
1206 gestureRecognizers: widget.gestureRecognizers,
1207 );
1208 }
1209 }
1210
1211 void _onPlatformViewCreated(int _) {
1212 // Trigger a re-build based on the current controller state.
1213 setState(() {});
1214 }
1215}
1216
1217// Displays an Android platform view via GL texture.
1218class _TextureBasedAndroidViewSurface extends PlatformViewSurface {
1219 const _TextureBasedAndroidViewSurface({
1220 required AndroidViewController super.controller,
1221 required super.hitTestBehavior,
1222 required super.gestureRecognizers,
1223 });
1224
1225 @override
1226 RenderObject createRenderObject(BuildContext context) {
1227 final AndroidViewController viewController = controller as AndroidViewController;
1228 // Use GL texture based composition.
1229 // App should use GL texture unless they require to embed a SurfaceView.
1230 final RenderAndroidView renderBox = RenderAndroidView(
1231 viewController: viewController,
1232 gestureRecognizers: gestureRecognizers,
1233 hitTestBehavior: hitTestBehavior,
1234 );
1235 viewController.pointTransformer =
1236 (Offset position) => renderBox.globalToLocal(position);
1237 return renderBox;
1238 }
1239}
1240
1241class _PlatformLayerBasedAndroidViewSurface extends PlatformViewSurface {
1242 const _PlatformLayerBasedAndroidViewSurface({
1243 required AndroidViewController super.controller,
1244 required super.hitTestBehavior,
1245 required super.gestureRecognizers,
1246 });
1247
1248 @override
1249 RenderObject createRenderObject(BuildContext context) {
1250 final AndroidViewController viewController = controller as AndroidViewController;
1251 final PlatformViewRenderBox renderBox =
1252 super.createRenderObject(context) as PlatformViewRenderBox;
1253 viewController.pointTransformer =
1254 (Offset position) => renderBox.globalToLocal(position);
1255 return renderBox;
1256 }
1257}
1258
1259/// A callback used to notify the size of the platform view placeholder.
1260/// This size is the initial size of the platform view.
1261typedef _OnLayoutCallback = void Function(Size size, Offset position);
1262
1263/// A [RenderBox] that notifies its size to the owner after a layout.
1264class _PlatformViewPlaceholderBox extends RenderConstrainedBox {
1265 _PlatformViewPlaceholderBox({
1266 required this.onLayout,
1267 }) : super(additionalConstraints: const BoxConstraints.tightFor(
1268 width: double.infinity,
1269 height: double.infinity,
1270 ));
1271
1272 _OnLayoutCallback onLayout;
1273
1274 @override
1275 void performLayout() {
1276 super.performLayout();
1277 // A call to `localToGlobal` requires waiting for a frame to render first.
1278 SchedulerBinding.instance.addPostFrameCallback((_) {
1279 onLayout(size, localToGlobal(Offset.zero));
1280 }, debugLabel: 'PlatformViewPlaceholderBox.onLayout');
1281 }
1282}
1283
1284/// When a platform view is in the widget hierarchy, this widget is used to capture
1285/// the size of the platform view after the first layout.
1286/// This placeholder is basically a [SizedBox.expand] with a [onLayout] callback to
1287/// notify the size of the render object to its parent.
1288class _PlatformViewPlaceHolder extends SingleChildRenderObjectWidget {
1289 const _PlatformViewPlaceHolder({
1290 required this.onLayout,
1291 });
1292
1293 final _OnLayoutCallback onLayout;
1294
1295 @override
1296 _PlatformViewPlaceholderBox createRenderObject(BuildContext context) {
1297 return _PlatformViewPlaceholderBox(onLayout: onLayout);
1298 }
1299
1300 @override
1301 void updateRenderObject(BuildContext context, _PlatformViewPlaceholderBox renderObject) {
1302 renderObject.onLayout = onLayout;
1303 }
1304}
1305
1306extension on PlatformViewController {
1307 /// Disposes the controller in a post-frame callback, to allow other widgets to
1308 /// remove their listeners before the controller is disposed.
1309 void disposePostFrame() {
1310 SchedulerBinding.instance.addPostFrameCallback((_) {
1311 dispose();
1312 }, debugLabel: 'PlatformViewController.dispose');
1313 }
1314}
1315