platform_view.dart 37.8 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4 5
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:flutter/foundation.dart';
6
import 'package:flutter/gestures.dart';
7 8 9
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';

10 11
import 'basic.dart';
import 'debug.dart';
12 13
import 'focus_manager.dart';
import 'focus_scope.dart';
14 15 16 17
import 'framework.dart';

/// Embeds an Android view in the Widget hierarchy.
///
18 19
/// Requires Android API level 20 or greater.
///
20 21 22 23 24 25
/// Embedding Android views is an expensive operation and should be avoided when a Flutter
/// equivalent is possible.
///
/// The embedded Android view is painted just like any other Flutter widget and transformations
/// apply to it as well.
///
26
/// {@template flutter.widgets.AndroidView.layout}
27
/// The widget fills all available space, the parent of this object must provide bounded layout
28
/// constraints.
29
/// {@endtemplate}
30
///
31
/// {@template flutter.widgets.AndroidView.gestures}
32
/// The widget participates in Flutter's gesture arenas, and dispatches touch events to the
33 34 35
/// platform view iff it won the arena. Specific gestures that should be dispatched to the platform
/// view can be specified in the `gestureRecognizers` constructor parameter. If
/// the set of gesture recognizers is empty, a gesture will be dispatched to the platform
36
/// view iff it was not claimed by any other gesture recognizer.
37
/// {@endtemplate}
38
///
39 40 41 42 43 44 45
/// The Android view object is created using a [PlatformViewFactory](/javadoc/io/flutter/plugin/platform/PlatformViewFactory.html).
/// 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-).
///
/// Registration is typically done in the plugin's registerWith method, e.g:
///
/// ```java
///   public static void registerWith(Registrar registrar) {
46
///     registrar.platformViewRegistry().registerViewFactory("webview", WebViewFactory(registrar.messenger()));
47 48 49
///   }
/// ```
///
50
/// {@template flutter.widgets.AndroidView.lifetime}
51
/// The platform view's lifetime is the same as the lifetime of the [State] object for this widget.
52 53
/// When the [State] is disposed the platform view (and auxiliary resources) are lazily
/// released (some resources are immediately released and some by platform garbage collector).
54
/// A stateful widget's state is disposed when the widget is removed from the tree or when it is
55 56
/// moved within the tree. If the stateful widget has a key and it's only moved relative to its siblings,
/// or it has a [GlobalKey] and it's moved within the tree, it will not be disposed.
57
/// {@endtemplate}
58 59 60
class AndroidView extends StatefulWidget {
  /// Creates a widget that embeds an Android view.
  ///
61
  /// {@template flutter.widgets.AndroidView.constructorArgs}
62
  /// The `viewType` and `hitTestBehavior` parameters must not be null.
63
  /// If `creationParams` is not null then `creationParamsCodec` must not be null.
64
  /// {@endtemplate}
65
  const AndroidView({
66 67
    Key? key,
    required this.viewType,
68 69
    this.onPlatformViewCreated,
    this.hitTestBehavior = PlatformViewHitTestBehavior.opaque,
70
    this.layoutDirection,
71
    this.gestureRecognizers,
72
    this.creationParams,
73
    this.creationParamsCodec,
74
    this.clipBehavior = Clip.hardEdge,
75
  }) : assert(viewType != null),
76
       assert(hitTestBehavior != null),
77
       assert(creationParams == null || creationParamsCodec != null),
78
       assert(clipBehavior != null),
79 80 81
       super(key: key);

  /// The unique identifier for Android view type to be embedded by this widget.
82
  ///
83 84 85
  /// A [PlatformViewFactory](/javadoc/io/flutter/plugin/platform/PlatformViewFactory.html)
  /// for this type must have been registered.
  ///
86 87 88
  /// See also:
  ///
  ///  * [AndroidView] for an example of registering a platform view factory.
89 90
  final String viewType;

91
  /// {@template flutter.widgets.AndroidView.onPlatformViewCreated}
92
  /// Callback to invoke after the platform view has been created.
93 94
  ///
  /// May be null.
95
  /// {@endtemplate}
96
  final PlatformViewCreatedCallback? onPlatformViewCreated;
97

98
  /// {@template flutter.widgets.AndroidView.hitTestBehavior}
99 100 101
  /// How this widget should behave during hit testing.
  ///
  /// This defaults to [PlatformViewHitTestBehavior.opaque].
102
  /// {@endtemplate}
103 104
  final PlatformViewHitTestBehavior hitTestBehavior;

105
  /// {@template flutter.widgets.AndroidView.layoutDirection}
106 107 108
  /// The text direction to use for the embedded view.
  ///
  /// If this is null, the ambient [Directionality] is used instead.
109
  /// {@endtemplate}
110
  final TextDirection? layoutDirection;
111

112 113
  /// Which gestures should be forwarded to the Android view.
  ///
114
  /// {@template flutter.widgets.AndroidView.gestureRecognizers.descHead}
115 116
  /// The gesture recognizers built by factories in this set participate in the gesture arena for
  /// each pointer that was put down on the widget. If any of these recognizers win the
117
  /// gesture arena, the entire pointer event sequence starting from the pointer down event
118
  /// will be dispatched to the platform view.
119
  ///
120
  /// When null, an empty set of gesture recognizer factories is used, in which case a pointer event sequence
121 122
  /// will only be dispatched to the platform view if no other member of the arena claimed it.
  /// {@endtemplate}
123
  ///
124 125
  /// For example, with the following setup vertical drags will not be dispatched to the Android
  /// view as the vertical drag gesture is claimed by the parent [GestureDetector].
126
  ///
127 128 129 130 131 132 133 134
  /// ```dart
  /// GestureDetector(
  ///   onVerticalDragStart: (DragStartDetails d) {},
  ///   child: AndroidView(
  ///     viewType: 'webview',
  ///   ),
  /// )
  /// ```
135
  ///
136
  /// To get the [AndroidView] to claim the vertical drag gestures we can pass a vertical drag
137
  /// gesture recognizer factory in [gestureRecognizers] e.g:
138
  ///
139 140
  /// ```dart
  /// GestureDetector(
141
  ///   onVerticalDragStart: (DragStartDetails details) {},
142 143 144 145 146
  ///   child: SizedBox(
  ///     width: 200.0,
  ///     height: 100.0,
  ///     child: AndroidView(
  ///       viewType: 'webview',
147 148 149 150 151
  ///       gestureRecognizers: <Factory<OneSequenceGestureRecognizer>>[
  ///         new Factory<OneSequenceGestureRecognizer>(
  ///           () => new EagerGestureRecognizer(),
  ///         ),
  ///       ].toSet(),
152 153 154 155 156
  ///     ),
  ///   ),
  /// )
  /// ```
  ///
157
  /// {@template flutter.widgets.AndroidView.gestureRecognizers.descFoot}
158
  /// A platform view can be configured to consume all pointers that were put down in its bounds
159 160 161 162 163 164 165
  /// by passing a factory for an [EagerGestureRecognizer] in [gestureRecognizers].
  /// [EagerGestureRecognizer] is a special gesture recognizer that immediately claims the gesture
  /// after a pointer down event.
  ///
  /// The `gestureRecognizers` property must not contain more than one factory with the same [Factory.type].
  ///
  /// Changing `gestureRecognizers` results in rejection of any active gesture arenas (if the
166 167
  /// platform view is actively participating in an arena).
  /// {@endtemplate}
168 169 170
  // We use OneSequenceGestureRecognizers as they support gesture arena teams.
  // TODO(amirh): get a list of GestureRecognizers here.
  // https://github.com/flutter/flutter/issues/20953
171
  final Set<Factory<OneSequenceGestureRecognizer>>? gestureRecognizers;
172

173 174 175 176 177 178 179 180 181 182 183
  /// Passed as the args argument of [PlatformViewFactory#create](/javadoc/io/flutter/plugin/platform/PlatformViewFactory.html#create-android.content.Context-int-java.lang.Object-)
  ///
  /// This can be used by plugins to pass constructor parameters to the embedded Android view.
  final dynamic creationParams;

  /// The codec used to encode `creationParams` before sending it to the
  /// 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-).
  ///
  /// This is typically one of: [StandardMessageCodec], [JSONMessageCodec], [StringCodec], or [BinaryCodec].
  ///
  /// This must not be null if [creationParams] is not null.
184
  final MessageCodec<dynamic>? creationParamsCodec;
185

186
  /// {@macro flutter.material.Material.clipBehavior}
187 188 189 190
  ///
  /// Defaults to [Clip.hardEdge], and must not be null.
  final Clip clipBehavior;

191
  @override
192 193 194 195
  State<AndroidView> createState() => _AndroidViewState();
}

// TODO(amirh): describe the embedding mechanism.
196
// TODO(ychris): remove the documentation for conic path not supported once https://github.com/flutter/flutter/issues/35062 is resolved.
197 198
/// Embeds an iOS view in the Widget hierarchy.
///
199
/// {@macro flutter.rendering.RenderUiKitView}
200 201 202 203
///
/// Embedding iOS views is an expensive operation and should be avoided when a Flutter
/// equivalent is possible.
///
204
/// {@macro flutter.widgets.AndroidView.layout}
205
///
206
/// {@macro flutter.widgets.AndroidView.gestures}
207
///
208
/// {@macro flutter.widgets.AndroidView.lifetime}
209 210 211
///
/// Construction of UIViews is done asynchronously, before the UIView is ready this widget paints
/// nothing while maintaining the same layout constraints.
212 213 214
///
/// If a conic path clipping is applied to a UIKitView,
/// a quad path is used to approximate the clip due to limitation of Quartz.
215 216 217
class UiKitView extends StatefulWidget {
  /// Creates a widget that embeds an iOS view.
  ///
218
  /// {@macro flutter.widgets.AndroidView.constructorArgs}
219
  const UiKitView({
220 221
    Key? key,
    required this.viewType,
222 223 224
    this.onPlatformViewCreated,
    this.hitTestBehavior = PlatformViewHitTestBehavior.opaque,
    this.layoutDirection,
225 226
    this.creationParams,
    this.creationParamsCodec,
227
    this.gestureRecognizers,
228
  }) : assert(viewType != null),
229 230 231
       assert(hitTestBehavior != null),
       assert(creationParams == null || creationParamsCodec != null),
       super(key: key);
232

Chris Bracken's avatar
Chris Bracken committed
233
  // TODO(amirh): reference the iOS API doc once available.
234 235 236 237 238
  /// The unique identifier for iOS view type to be embedded by this widget.
  ///
  /// A PlatformViewFactory for this type must have been registered.
  final String viewType;

239
  /// {@macro flutter.widgets.AndroidView.onPlatformViewCreated}
240
  final PlatformViewCreatedCallback? onPlatformViewCreated;
241

242
  /// {@macro flutter.widgets.AndroidView.hitTestBehavior}
243 244
  final PlatformViewHitTestBehavior hitTestBehavior;

245
  /// {@macro flutter.widgets.AndroidView.layoutDirection}
246
  final TextDirection? layoutDirection;
247

248
  /// Passed as the `arguments` argument of [-\[FlutterPlatformViewFactory createWithFrame:viewIdentifier:arguments:\]](/objcdoc/Protocols/FlutterPlatformViewFactory.html#/c:objc(pl)FlutterPlatformViewFactory(im)createWithFrame:viewIdentifier:arguments:)
249
  ///
250
  /// This can be used by plugins to pass constructor parameters to the embedded iOS view.
251 252 253
  final dynamic creationParams;

  /// The codec used to encode `creationParams` before sending it to the
254
  /// platform side. It should match the codec returned by [-\[FlutterPlatformViewFactory createArgsCodec:\]](/objcdoc/Protocols/FlutterPlatformViewFactory.html#/c:objc(pl)FlutterPlatformViewFactory(im)createArgsCodec)
255 256 257 258
  ///
  /// This is typically one of: [StandardMessageCodec], [JSONMessageCodec], [StringCodec], or [BinaryCodec].
  ///
  /// This must not be null if [creationParams] is not null.
259
  final MessageCodec<dynamic>? creationParamsCodec;
260

261 262
  /// Which gestures should be forwarded to the UIKit view.
  ///
263
  /// {@macro flutter.widgets.AndroidView.gestureRecognizers.descHead}
264 265 266
  ///
  /// For example, with the following setup vertical drags will not be dispatched to the UIKit
  /// view as the vertical drag gesture is claimed by the parent [GestureDetector].
267
  ///
268 269 270 271 272 273 274 275
  /// ```dart
  /// GestureDetector(
  ///   onVerticalDragStart: (DragStartDetails details) {},
  ///   child: UiKitView(
  ///     viewType: 'webview',
  ///   ),
  /// )
  /// ```
276
  ///
277 278
  /// To get the [UiKitView] to claim the vertical drag gestures we can pass a vertical drag
  /// gesture recognizer factory in [gestureRecognizers] e.g:
279
  ///
280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297
  /// ```dart
  /// GestureDetector(
  ///   onVerticalDragStart: (DragStartDetails details) {},
  ///   child: SizedBox(
  ///     width: 200.0,
  ///     height: 100.0,
  ///     child: UiKitView(
  ///       viewType: 'webview',
  ///       gestureRecognizers: <Factory<OneSequenceGestureRecognizer>>[
  ///         new Factory<OneSequenceGestureRecognizer>(
  ///           () => new EagerGestureRecognizer(),
  ///         ),
  ///       ].toSet(),
  ///     ),
  ///   ),
  /// )
  /// ```
  ///
298
  /// {@macro flutter.widgets.AndroidView.gestureRecognizers.descFoot}
299 300 301
  // We use OneSequenceGestureRecognizers as they support gesture arena teams.
  // TODO(amirh): get a list of GestureRecognizers here.
  // https://github.com/flutter/flutter/issues/20953
302
  final Set<Factory<OneSequenceGestureRecognizer>>? gestureRecognizers;
303

304 305
  @override
  State<UiKitView> createState() => _UiKitViewState();
306 307
}

308 309 310 311 312 313 314 315 316 317 318 319
/// Embeds an HTML element in the Widget hierarchy in Flutter Web.
///
/// *NOTE*: This only works in Flutter Web. To embed web content on other
/// platforms, consider using the `flutter_webview` plugin.
///
/// Embedding HTML is an expensive operation and should be avoided when a
/// Flutter equivalent is possible.
///
/// The embedded HTML is painted just like any other Flutter widget and
/// transformations apply to it as well. This widget should only be used in
/// Flutter Web.
///
320
/// {@macro flutter.widgets.AndroidView.layout}
321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339
///
/// Due to security restrictions with cross-origin `<iframe>` elements, Flutter
/// cannot dispatch pointer events to an HTML view. If an `<iframe>` is the
/// target of an event, the window containing the `<iframe>` is not notified
/// of the event. In particular, this means that any pointer events which land
/// on an `<iframe>` will not be seen by Flutter, and so the HTML view cannot
/// participate in gesture detection with other widgets.
///
/// The way we enable accessibility on Flutter for web is to have a full-page
/// button which waits for a double tap. Placing this full-page button in front
/// of the scene would cause platform views not to receive pointer events. The
/// tradeoff is that by placing the scene in front of the semantics placeholder
/// will cause platform views to block pointer events from reaching the
/// placeholder. This means that in order to enable accessibility, you must
/// double tap the app *outside of a platform view*. As a consequence, a
/// full-screen platform view will make it impossible to enable accessibility.
/// Make sure that your HTML views are sized no larger than necessary, or you
/// may cause difficulty for users trying to enable accessibility.
///
340
/// {@macro flutter.widgets.AndroidView.lifetime}
341 342 343 344 345
class HtmlElementView extends StatelessWidget {
  /// Creates a platform view for Flutter Web.
  ///
  /// `viewType` identifies the type of platform view to create.
  const HtmlElementView({
346 347
    Key? key,
    required this.viewType,
348
    this.onPlatformViewCreated,
349 350 351 352 353 354 355 356 357
  }) : assert(viewType != null),
       assert(kIsWeb, 'HtmlElementView is only available on Flutter Web.'),
       super(key: key);

  /// The unique identifier for the HTML view type to be embedded by this widget.
  ///
  /// A PlatformViewFactory for this type must have been registered.
  final String viewType;

358 359 360 361 362
  /// Callback to invoke after the platform view has been created.
  ///
  /// May be null.
  final PlatformViewCreatedCallback? onPlatformViewCreated;

363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380
  @override
  Widget build(BuildContext context) {
    return PlatformViewLink(
      viewType: viewType,
      onCreatePlatformView: _createHtmlElementView,
      surfaceFactory: (BuildContext context, PlatformViewController controller) {
        return PlatformViewSurface(
          controller: controller,
          gestureRecognizers: const <Factory<OneSequenceGestureRecognizer>>{},
          hitTestBehavior: PlatformViewHitTestBehavior.opaque,
        );
      },
    );
  }

  /// Creates the controller and kicks off its initialization.
  _HtmlElementViewController _createHtmlElementView(PlatformViewCreationParams params) {
    final _HtmlElementViewController controller = _HtmlElementViewController(params.id, viewType);
381 382 383 384
    controller._initialize().then((_) {
      params.onPlatformViewCreated(params.id);
      onPlatformViewCreated?.call(params.id);
    });
385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414
    return controller;
  }
}

class _HtmlElementViewController extends PlatformViewController {
  _HtmlElementViewController(
    this.viewId,
    this.viewType,
  );

  @override
  final int viewId;

  /// The unique identifier for the HTML view type to be embedded by this widget.
  ///
  /// A PlatformViewFactory for this type must have been registered.
  final String viewType;

  bool _initialized = false;

  Future<void> _initialize() async {
    final Map<String, dynamic> args = <String, dynamic>{
      'id': viewId,
      'viewType': viewType,
    };
    await SystemChannels.platform_views.invokeMethod<void>('create', args);
    _initialized = true;
  }

  @override
415
  Future<void> clearFocus() async {
416 417 418 419 420
    // Currently this does nothing on Flutter Web.
    // TODO(het): Implement this. See https://github.com/flutter/flutter/issues/39496
  }

  @override
421
  Future<void> dispatchPointerEvent(PointerEvent event) async {
422 423 424 425 426
    // We do not dispatch pointer events to HTML views because they may contain
    // cross-origin iframes, which only accept user-generated events.
  }

  @override
427
  Future<void> dispose() async {
428
    if (_initialized) {
429
      await SystemChannels.platform_views.invokeMethod<void>('dispose', viewId);
430 431 432 433
    }
  }
}

434
class _AndroidViewState extends State<AndroidView> {
435 436 437
  int? _id;
  late AndroidViewController _controller;
  TextDirection? _layoutDirection;
438
  bool _initialized = false;
439
  FocusNode? _focusNode;
440

441
  static final Set<Factory<OneSequenceGestureRecognizer>> _emptyRecognizersSet =
442
    <Factory<OneSequenceGestureRecognizer>>{};
443

444 445
  @override
  Widget build(BuildContext context) {
446 447
    return Focus(
      focusNode: _focusNode,
448
      onFocusChange: _onFocusChange,
449
      child: _AndroidPlatformView(
450
        controller: _controller,
451
        hitTestBehavior: widget.hitTestBehavior,
452
        gestureRecognizers: widget.gestureRecognizers ?? _emptyRecognizersSet,
453
        clipBehavior: widget.clipBehavior,
454
      ),
455
    );
456 457
  }

458 459 460 461 462
  void _initializeOnce() {
    if (_initialized) {
      return;
    }
    _initialized = true;
463
    _createNewAndroidView();
464
    _focusNode = FocusNode(debugLabel: 'AndroidView(id: $_id)');
465 466
  }

467 468 469 470 471 472 473
  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    final TextDirection newLayoutDirection = _findLayoutDirection();
    final bool didChangeLayoutDirection = _layoutDirection != newLayoutDirection;
    _layoutDirection = newLayoutDirection;

474
    _initializeOnce();
475 476 477
    if (didChangeLayoutDirection) {
      // The native view will update asynchronously, in the meantime we don't want
      // to block the framework. (so this is intentionally not awaiting).
478
      _controller.setLayoutDirection(_layoutDirection!);
479 480 481
    }
  }

482 483 484
  @override
  void didUpdateWidget(AndroidView oldWidget) {
    super.didUpdateWidget(oldWidget);
485 486 487 488 489 490 491 492

    final TextDirection newLayoutDirection = _findLayoutDirection();
    final bool didChangeLayoutDirection = _layoutDirection != newLayoutDirection;
    _layoutDirection = newLayoutDirection;

    if (widget.viewType != oldWidget.viewType) {
      _controller.dispose();
      _createNewAndroidView();
493
      return;
494 495 496
    }

    if (didChangeLayoutDirection) {
497
      _controller.setLayoutDirection(_layoutDirection!);
498 499 500 501 502
    }
  }

  TextDirection _findLayoutDirection() {
    assert(widget.layoutDirection != null || debugCheckHasDirectionality(context));
503
    return widget.layoutDirection ?? Directionality.of(context);
504 505 506 507 508 509 510 511 512 513 514
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  void _createNewAndroidView() {
    _id = platformViewsRegistry.getNextPlatformViewId();
    _controller = PlatformViewsService.initAndroidView(
515
      id: _id!,
516
      viewType: widget.viewType,
517
      layoutDirection: _layoutDirection!,
518 519
      creationParams: widget.creationParams,
      creationParamsCodec: widget.creationParamsCodec,
520
      onFocus: () {
521
        _focusNode!.requestFocus();
522
      },
523
    );
524
    if (widget.onPlatformViewCreated != null) {
525
      _controller.addOnPlatformViewCreatedListener(widget.onPlatformViewCreated!);
526
    }
527
  }
528 529 530 531 532 533 534

  void _onFocusChange(bool isFocused) {
    if (!_controller.isCreated) {
      return;
    }
    if (!isFocused) {
      _controller.clearFocus().catchError((dynamic e) {
535 536 537 538 539 540 541 542 543
        if (e is MissingPluginException) {
          // We land the framework part of Android platform views keyboard
          // support before the engine part. There will be a commit range where
          // clearFocus isn't implemented in the engine. When that happens we
          // just swallow the error here. Once the engine part is rolled to the
          // framework I'll remove this.
          // TODO(amirh): remove this once the engine's clearFocus is rolled.
          return;
        }
544 545 546 547 548
      });
      return;
    }
    SystemChannels.textInput.invokeMethod<void>(
      'TextInput.setPlatformViewClient',
549
      <String, dynamic>{'platformViewId': _id, 'usesVirtualDisplay': true},
550 551 552 553 554 555 556 557 558 559 560 561
    ).catchError((dynamic e) {
      if (e is MissingPluginException) {
        // We land the framework part of Android platform views keyboard
        // support before the engine part. There will be a commit range where
        // setPlatformViewClient isn't implemented in the engine. When that
        // happens we just swallow the error here. Once the engine part is
        // rolled to the framework I'll remove this.
        // TODO(amirh): remove this once the engine's clearFocus is rolled.
        return;
      }
    });
  }
562 563
}

564
class _UiKitViewState extends State<UiKitView> {
565 566
  UiKitViewController? _controller;
  TextDirection? _layoutDirection;
567 568 569
  bool _initialized = false;

  static final Set<Factory<OneSequenceGestureRecognizer>> _emptyRecognizersSet =
570
    <Factory<OneSequenceGestureRecognizer>>{};
571 572 573 574 575 576 577

  @override
  Widget build(BuildContext context) {
    if (_controller == null) {
      return const SizedBox.expand();
    }
    return _UiKitPlatformView(
578
      controller: _controller!,
579
      hitTestBehavior: widget.hitTestBehavior,
580
      gestureRecognizers: widget.gestureRecognizers ?? _emptyRecognizersSet,
581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602
    );
  }

  void _initializeOnce() {
    if (_initialized) {
      return;
    }
    _initialized = true;
    _createNewUiKitView();
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    final TextDirection newLayoutDirection = _findLayoutDirection();
    final bool didChangeLayoutDirection = _layoutDirection != newLayoutDirection;
    _layoutDirection = newLayoutDirection;

    _initializeOnce();
    if (didChangeLayoutDirection) {
      // The native view will update asynchronously, in the meantime we don't want
      // to block the framework. (so this is intentionally not awaiting).
603
      _controller?.setLayoutDirection(_layoutDirection!);
604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621
    }
  }

  @override
  void didUpdateWidget(UiKitView oldWidget) {
    super.didUpdateWidget(oldWidget);

    final TextDirection newLayoutDirection = _findLayoutDirection();
    final bool didChangeLayoutDirection = _layoutDirection != newLayoutDirection;
    _layoutDirection = newLayoutDirection;

    if (widget.viewType != oldWidget.viewType) {
      _controller?.dispose();
      _createNewUiKitView();
      return;
    }

    if (didChangeLayoutDirection) {
622
      _controller?.setLayoutDirection(_layoutDirection!);
623 624 625 626 627
    }
  }

  TextDirection _findLayoutDirection() {
    assert(widget.layoutDirection != null || debugCheckHasDirectionality(context));
628
    return widget.layoutDirection ?? Directionality.of(context);
629 630 631 632 633 634 635 636 637
  }

  @override
  void dispose() {
    _controller?.dispose();
    super.dispose();
  }

  Future<void> _createNewUiKitView() async {
638
    final int id = platformViewsRegistry.getNextPlatformViewId();
639
    final UiKitViewController controller = await PlatformViewsService.initUiKitView(
640
      id: id,
641
      viewType: widget.viewType,
642
      layoutDirection: _layoutDirection!,
643 644
      creationParams: widget.creationParams,
      creationParamsCodec: widget.creationParamsCodec,
645 646 647 648 649
    );
    if (!mounted) {
      controller.dispose();
      return;
    }
650
    widget.onPlatformViewCreated?.call(id);
651 652 653 654
    setState(() { _controller = controller; });
  }
}

655 656
class _AndroidPlatformView extends LeafRenderObjectWidget {
  const _AndroidPlatformView({
657 658 659 660
    Key? key,
    required this.controller,
    required this.hitTestBehavior,
    required this.gestureRecognizers,
661
    this.clipBehavior = Clip.hardEdge,
662
  }) : assert(controller != null),
663
       assert(hitTestBehavior != null),
664
       assert(gestureRecognizers != null),
665
       assert(clipBehavior != null),
666 667 668
       super(key: key);

  final AndroidViewController controller;
669
  final PlatformViewHitTestBehavior hitTestBehavior;
670
  final Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers;
671
  final Clip clipBehavior;
672 673 674

  @override
  RenderObject createRenderObject(BuildContext context) =>
675
      RenderAndroidView(
676 677 678
        viewController: controller,
        hitTestBehavior: hitTestBehavior,
        gestureRecognizers: gestureRecognizers,
679
        clipBehavior: clipBehavior,
680
      );
681 682 683 684

  @override
  void updateRenderObject(BuildContext context, RenderAndroidView renderObject) {
    renderObject.viewController = controller;
685
    renderObject.hitTestBehavior = hitTestBehavior;
686
    renderObject.updateGestureRecognizers(gestureRecognizers);
687
    renderObject.clipBehavior = clipBehavior;
688 689
  }
}
690 691 692

class _UiKitPlatformView extends LeafRenderObjectWidget {
  const _UiKitPlatformView({
693 694 695 696
    Key? key,
    required this.controller,
    required this.hitTestBehavior,
    required this.gestureRecognizers,
697
  }) : assert(controller != null),
698
       assert(hitTestBehavior != null),
699
       assert(gestureRecognizers != null),
700 701
       super(key: key);

702
  final UiKitViewController controller;
703
  final PlatformViewHitTestBehavior hitTestBehavior;
704
  final Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers;
705 706 707 708

  @override
  RenderObject createRenderObject(BuildContext context) {
    return RenderUiKitView(
709
      viewController: controller,
710
      hitTestBehavior: hitTestBehavior,
711
      gestureRecognizers: gestureRecognizers,
712 713 714 715 716
    );
  }

  @override
  void updateRenderObject(BuildContext context, RenderUiKitView renderObject) {
717
    renderObject.viewController = controller;
718
    renderObject.hitTestBehavior = hitTestBehavior;
719
    renderObject.updateGestureRecognizers(gestureRecognizers);
720 721
  }
}
722

723 724
/// The parameters used to create a [PlatformViewController].
///
725 726 727
/// See also:
///
///  * [CreatePlatformViewCallback] which uses this object to create a [PlatformViewController].
728 729 730
class PlatformViewCreationParams {

  const PlatformViewCreationParams._({
731 732 733 734
    required this.id,
    required this.viewType,
    required this.onPlatformViewCreated,
    required this.onFocusChanged,
735 736 737 738 739 740 741 742
  }) : assert(id != null),
       assert(onPlatformViewCreated != null);

  /// The unique identifier for the new platform view.
  ///
  /// [PlatformViewController.viewId] should match this id.
  final int id;

743 744 745 746 747 748
  /// The unique identifier for the type of platform view to be embedded.
  ///
  /// This viewType is used to tell the platform which type of view to
  /// associate with the [id].
  final String viewType;

749 750
  /// Callback invoked after the platform view has been created.
  final PlatformViewCreatedCallback onPlatformViewCreated;
751 752 753 754 755

  /// Callback invoked when the platform view's focus is changed on the platform side.
  ///
  /// The value is true when the platform view gains focus and false when it loses focus.
  final ValueChanged<bool> onFocusChanged;
756 757 758 759 760 761 762
}

/// A factory for a surface presenting a platform view as part of the widget hierarchy.
///
/// The returned widget should present the platform view associated with `controller`.
///
/// See also:
763 764
///
///  * [PlatformViewSurface], a common widget for presenting platform views.
765 766 767 768
typedef PlatformViewSurfaceFactory = Widget Function(BuildContext context, PlatformViewController controller);

/// Constructs a [PlatformViewController].
///
769
/// The [PlatformViewController.viewId] field of the created controller must match the value of the
770 771
/// params [PlatformViewCreationParams.id] field.
///
772 773 774
/// See also:
///
///  * [PlatformViewLink], which links a platform view with the Flutter framework.
775
typedef CreatePlatformViewCallback = PlatformViewController Function(PlatformViewCreationParams params);
776 777 778 779 780 781

/// Links a platform view with the Flutter framework.
///
/// Provides common functionality for embedding a platform view (e.g an android.view.View on Android)
/// with the Flutter framework.
///
782
/// {@macro flutter.widgets.AndroidView.lifetime}
783 784 785 786 787 788 789 790
///
/// To implement a new platform view widget, return this widget in the `build` method.
/// For example:
/// ```dart
/// class FooPlatformView extends StatelessWidget {
///   @override
///   Widget build(BuildContext context) {
///     return PlatformViewLink(
791
///       viewType: 'webview',
792
///       onCreatePlatformView: createFooWebView,
793 794 795 796 797 798 799 800 801 802 803 804
///       surfaceFactory: (BuildContext context, PlatformViewController controller) {
///        return PlatformViewSurface(
///            gestureRecognizers: gestureRecognizers,
///            controller: controller,
///            hitTestBehavior: PlatformViewHitTestBehavior.opaque,
///        );
///       },
///    );
///   }
/// }
/// ```
///
805 806
/// The `surfaceFactory` and the `onCreatePlatformView` are only called when the
/// state of this widget is initialized, or when the `viewType` changes.
807 808 809 810
class PlatformViewLink extends StatefulWidget {

  /// Construct a [PlatformViewLink] widget.
  ///
811
  /// The `surfaceFactory` and the `onCreatePlatformView` must not be null.
812 813
  ///
  /// See also:
814 815 816
  ///
  ///  * [PlatformViewSurface] for details on the widget returned by `surfaceFactory`.
  ///  * [PlatformViewCreationParams] for how each parameter can be used when implementing `createPlatformView`.
817
  const PlatformViewLink({
818 819 820 821
    Key? key,
    required PlatformViewSurfaceFactory surfaceFactory,
    required CreatePlatformViewCallback onCreatePlatformView,
    required this.viewType,
822
    }) : assert(surfaceFactory != null),
823 824 825 826 827
         assert(onCreatePlatformView != null),
         assert(viewType != null),
         _surfaceFactory = surfaceFactory,
         _onCreatePlatformView = onCreatePlatformView,
         super(key: key);
828 829 830


  final PlatformViewSurfaceFactory _surfaceFactory;
831
  final CreatePlatformViewCallback _onCreatePlatformView;
832

833 834 835 836 837
  /// The unique identifier for the view type to be embedded.
  ///
  /// Typically, this viewType has already been registered on the platform side.
  final String viewType;

838 839 840 841 842
  @override
  State<StatefulWidget> createState() => _PlatformViewLinkState();
}

class _PlatformViewLinkState extends State<PlatformViewLink> {
843 844
  int? _id;
  PlatformViewController? _controller;
845
  bool _platformViewCreated = false;
846 847
  Widget? _surface;
  FocusNode? _focusNode;
848 849 850 851 852 853

  @override
  Widget build(BuildContext context) {
    if (!_platformViewCreated) {
      return const SizedBox.expand();
    }
854
    _surface ??= widget._surfaceFactory(context, _controller!);
855 856 857
    return Focus(
      focusNode: _focusNode,
      onFocusChange: _handleFrameworkFocusChanged,
858
      child: _surface!,
859
    );
860 861 862 863
  }

  @override
  void initState() {
864
    _focusNode = FocusNode(debugLabel: 'PlatformView(id: $_id)');
865 866 867 868
    _initialize();
    super.initState();
  }

869 870 871 872 873 874
  @override
  void didUpdateWidget(PlatformViewLink oldWidget) {
    super.didUpdateWidget(oldWidget);

    if (widget.viewType != oldWidget.viewType) {
      _controller?.dispose();
875 876 877
      // The _surface has to be recreated as its controller is disposed.
      // Setting _surface to null will trigger its creation in build().
      _surface = null;
878 879 880 881 882 883 884

      // We are about to create a new platform view.
      _platformViewCreated = false;
      _initialize();
    }
  }

885 886
  void _initialize() {
    _id = platformViewsRegistry.getNextPlatformViewId();
887 888
    _controller = widget._onCreatePlatformView(
      PlatformViewCreationParams._(
889
        id: _id!,
890 891 892
        viewType: widget.viewType,
        onPlatformViewCreated: _onPlatformViewCreated,
        onFocusChanged: _handlePlatformFocusChanged,
893 894
      ),
    );
895 896 897
  }

  void _onPlatformViewCreated(int id) {
898
    setState(() { _platformViewCreated = true; });
899 900
  }

901 902 903 904
  void _handleFrameworkFocusChanged(bool isFocused) {
    if (!isFocused) {
      _controller?.clearFocus();
    }
905 906 907 908
    SystemChannels.textInput.invokeMethod<void>(
      'TextInput.setPlatformViewClient',
      <String, dynamic>{'platformViewId': _id},
    );
909 910
  }

911
  void _handlePlatformFocusChanged(bool isFocused) {
912
    if (isFocused) {
913
      _focusNode!.requestFocus();
914 915 916
    }
  }

917 918 919
  @override
  void dispose() {
    _controller?.dispose();
920
    _controller = null;
921 922 923 924
    super.dispose();
  }
}

925 926
/// Integrates a platform view with Flutter's compositor, touch, and semantics subsystems.
///
927 928 929
/// The compositor integration is done by adding a [PlatformViewLayer] to the layer tree. [PlatformViewSurface]
/// isn't supported on all platforms (e.g on Android platform views can be composited by using a [TextureLayer] or
/// [AndroidViewSurface]).
930 931 932 933 934 935 936 937
/// Custom Flutter embedders can support [PlatformViewLayer]s by implementing a SystemCompositor.
///
/// The widget fills all available space, the parent of this object must provide bounded layout
/// constraints.
///
/// If the associated platform view is not created the [PlatformViewSurface] does not paint any contents.
///
/// See also:
938
///
939
///  * [AndroidView] which embeds an Android platform view in the widget hierarchy using a [TextureLayer].
940
///  * [UiKitView] which embeds an iOS platform view in the widget hierarchy.
941 942 943 944 945 946 947
// TODO(amirh): Link to the embedder's system compositor documentation once available.
class PlatformViewSurface extends LeafRenderObjectWidget {

  /// Construct a `PlatformViewSurface`.
  ///
  /// The [controller] must not be null.
  const PlatformViewSurface({
948 949 950 951
    Key? key,
    required this.controller,
    required this.hitTestBehavior,
    required this.gestureRecognizers,
952 953
  }) : assert(controller != null),
       assert(hitTestBehavior != null),
954 955
       assert(gestureRecognizers != null),
       super(key: key);
956 957 958 959 960 961 962

  /// The controller for the platform view integrated by this [PlatformViewSurface].
  ///
  /// [PlatformViewController] is used for dispatching touch events to the platform view.
  /// [PlatformViewController.viewId] identifies the platform view whose contents are painted by this widget.
  final PlatformViewController controller;

963 964
  /// Which gestures should be forwarded to the PlatformView.
  ///
965
  /// {@macro flutter.widgets.AndroidView.gestureRecognizers.descHead}
966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997
  ///
  /// For example, with the following setup vertical drags will not be dispatched to the platform view
  /// as the vertical drag gesture is claimed by the parent [GestureDetector].
  ///
  /// ```dart
  /// GestureDetector(
  ///   onVerticalDragStart: (DragStartDetails details) {},
  ///   child: PlatformViewSurface(
  ///   ),
  /// )
  /// ```
  ///
  /// To get the [PlatformViewSurface] to claim the vertical drag gestures we can pass a vertical drag
  /// gesture recognizer factory in [gestureRecognizers] e.g:
  ///
  /// ```dart
  /// GestureDetector(
  ///   onVerticalDragStart: (DragStartDetails details) {},
  ///   child: SizedBox(
  ///     width: 200.0,
  ///     height: 100.0,
  ///     child: PlatformViewSurface(
  ///       gestureRecognizers: <Factory<OneSequenceGestureRecognizer>>[
  ///         new Factory<OneSequenceGestureRecognizer>(
  ///           () => new EagerGestureRecognizer(),
  ///         ),
  ///       ].toSet(),
  ///     ),
  ///   ),
  /// )
  /// ```
  ///
998
  /// {@macro flutter.widgets.AndroidView.gestureRecognizers.descFoot}
999 1000 1001 1002 1003
  // We use OneSequenceGestureRecognizers as they support gesture arena teams.
  // TODO(amirh): get a list of GestureRecognizers here.
  // https://github.com/flutter/flutter/issues/20953
  final Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers;

1004
  /// {@macro flutter.widgets.AndroidView.hitTestBehavior}
1005 1006
  final PlatformViewHitTestBehavior hitTestBehavior;

1007 1008
  @override
  RenderObject createRenderObject(BuildContext context) {
1009
    return PlatformViewRenderBox(controller: controller, gestureRecognizers: gestureRecognizers, hitTestBehavior: hitTestBehavior);
1010 1011 1012 1013 1014
  }

  @override
  void updateRenderObject(BuildContext context, PlatformViewRenderBox renderObject) {
    renderObject
1015 1016 1017
      ..controller = controller
      ..hitTestBehavior = hitTestBehavior
      ..updateGestureRecognizers(gestureRecognizers);
1018 1019
  }
}
1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034

/// Integrates an Android view with Flutter's compositor, touch, and semantics subsystems.
///
/// The compositor integration is done by adding a [PlatformViewLayer] to the layer tree. [PlatformViewLayer]
/// isn't supported on all platforms. Custom Flutter embedders can support
/// [PlatformViewLayer]s by implementing a SystemCompositor.
///
/// The widget fills all available space, the parent of this object must provide bounded layout
/// constraints.
///
/// If the associated platform view is not created, the [AndroidViewSurface] does not paint any contents.
///
/// See also:
///
///  * [AndroidView] which embeds an Android platform view in the widget hierarchy using a [TextureLayer].
1035
///  * [UiKitView] which embeds an iOS platform view in the widget hierarchy.
1036 1037 1038
class AndroidViewSurface extends PlatformViewSurface {
  /// Construct an `AndroidPlatformViewSurface`.
  const AndroidViewSurface({
1039 1040 1041 1042
    Key? key,
    required AndroidViewController controller,
    required PlatformViewHitTestBehavior hitTestBehavior,
    required Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers,
1043 1044 1045 1046
  }) : assert(controller != null),
       assert(hitTestBehavior != null),
       assert(gestureRecognizers != null),
       super(
1047 1048 1049 1050 1051
         key: key,
         controller: controller,
         hitTestBehavior: hitTestBehavior,
         gestureRecognizers: gestureRecognizers,
       );
1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063

  @override
  RenderObject createRenderObject(BuildContext context) {
    final PlatformViewRenderBox renderBox =
        super.createRenderObject(context) as PlatformViewRenderBox;

    (controller as AndroidViewController).pointTransformer =
        (Offset position) => renderBox.globalToLocal(position);

    return renderBox;
  }
}