platform_view.dart 35.2 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 27
/// {@template flutter.widgets.platformViews.layout}
/// The widget fills all available space, the parent of this object must provide bounded layout
28
/// constraints.
29
/// {@endtemplate}
30
///
31 32 33 34 35
/// {@template flutter.widgets.platformViews.gestures}
/// The widget participates in Flutter's [GestureArena]s, and dispatches touch events to the
/// 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 51
/// {@template flutter.widgets.platformViews.lifetime}
/// 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.platformViews.constructorParams}
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
  }) : assert(viewType != null),
75
       assert(hitTestBehavior != null),
76
       assert(creationParams == null || creationParamsCodec != null),
77 78 79
       super(key: key);

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

89 90
  /// {@template flutter.widgets.platformViews.createdParam}
  /// Callback to invoke after the platform view has been created.
91 92
  ///
  /// May be null.
93
  /// {@endtemplate}
94
  final PlatformViewCreatedCallback onPlatformViewCreated;
95

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

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

110 111
  /// Which gestures should be forwarded to the Android view.
  ///
112
  /// {@template flutter.widgets.platformViews.gestureRecognizersDescHead}
113 114
  /// 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
115
  /// gesture arena, the entire pointer event sequence starting from the pointer down event
116
  /// will be dispatched to the platform view.
117
  ///
118
  /// When null, an empty set of gesture recognizer factories is used, in which case a pointer event sequence
119 120
  /// will only be dispatched to the platform view if no other member of the arena claimed it.
  /// {@endtemplate}
121
  ///
122 123
  /// 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].
124
  ///
125 126 127 128 129 130 131 132
  /// ```dart
  /// GestureDetector(
  ///   onVerticalDragStart: (DragStartDetails d) {},
  ///   child: AndroidView(
  ///     viewType: 'webview',
  ///   ),
  /// )
  /// ```
133
  ///
134
  /// To get the [AndroidView] to claim the vertical drag gestures we can pass a vertical drag
135
  /// gesture recognizer factory in [gestureRecognizers] e.g:
136
  ///
137 138
  /// ```dart
  /// GestureDetector(
139
  ///   onVerticalDragStart: (DragStartDetails details) {},
140 141 142 143 144
  ///   child: SizedBox(
  ///     width: 200.0,
  ///     height: 100.0,
  ///     child: AndroidView(
  ///       viewType: 'webview',
145 146 147 148 149
  ///       gestureRecognizers: <Factory<OneSequenceGestureRecognizer>>[
  ///         new Factory<OneSequenceGestureRecognizer>(
  ///           () => new EagerGestureRecognizer(),
  ///         ),
  ///       ].toSet(),
150 151 152 153 154
  ///     ),
  ///   ),
  /// )
  /// ```
  ///
155 156
  /// {@template flutter.widgets.platformViews.gestureRecognizersDescFoot}
  /// A platform view can be configured to consume all pointers that were put down in its bounds
157 158 159 160 161 162 163
  /// 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
164 165
  /// platform view is actively participating in an arena).
  /// {@endtemplate}
166 167 168
  // We use OneSequenceGestureRecognizers as they support gesture arena teams.
  // TODO(amirh): get a list of GestureRecognizers here.
  // https://github.com/flutter/flutter/issues/20953
169
  final Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers;
170

171 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.
  final MessageCodec<dynamic> creationParamsCodec;

184
  @override
185 186 187 188
  State<AndroidView> createState() => _AndroidViewState();
}

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

Chris Bracken's avatar
Chris Bracken committed
226
  // TODO(amirh): reference the iOS API doc once available.
227 228 229 230 231 232 233 234 235 236 237 238 239 240
  /// 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;

  /// {@macro flutter.widgets.platformViews.createdParam}
  final PlatformViewCreatedCallback onPlatformViewCreated;

  /// {@macro flutter.widgets.platformViews.hittestParam}
  final PlatformViewHitTestBehavior hitTestBehavior;

  /// {@macro flutter.widgets.platformViews.directionParam}
  final TextDirection layoutDirection;

241
  /// Passed as the `arguments` argument of [-\[FlutterPlatformViewFactory createWithFrame:viewIdentifier:arguments:\]](/objcdoc/Protocols/FlutterPlatformViewFactory.html#/c:objc(pl)FlutterPlatformViewFactory(im)createWithFrame:viewIdentifier:arguments:)
242
  ///
243
  /// This can be used by plugins to pass constructor parameters to the embedded iOS view.
244 245 246
  final dynamic creationParams;

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

254 255 256 257 258 259
  /// Which gestures should be forwarded to the UIKit view.
  ///
  /// {@macro flutter.widgets.platformViews.gestureRecognizersDescHead}
  ///
  /// 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].
260
  ///
261 262 263 264 265 266 267 268
  /// ```dart
  /// GestureDetector(
  ///   onVerticalDragStart: (DragStartDetails details) {},
  ///   child: UiKitView(
  ///     viewType: 'webview',
  ///   ),
  /// )
  /// ```
269
  ///
270 271
  /// To get the [UiKitView] to claim the vertical drag gestures we can pass a vertical drag
  /// gesture recognizer factory in [gestureRecognizers] e.g:
272
  ///
273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296
  /// ```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(),
  ///     ),
  ///   ),
  /// )
  /// ```
  ///
  /// {@macro flutter.widgets.platformViews.gestureRecognizersDescFoot}
  // 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;

297 298
  @override
  State<UiKitView> createState() => _UiKitViewState();
299 300
}

301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 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 415 416 417 418
/// 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.
///
/// {@macro flutter.widgets.platformViews.layout}
///
/// 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.
///
/// {@macro flutter.widgets.platformViews.lifetime}
class HtmlElementView extends StatelessWidget {
  /// Creates a platform view for Flutter Web.
  ///
  /// `viewType` identifies the type of platform view to create.
  const HtmlElementView({
    Key key,
    @required this.viewType,
  }) : 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;

  @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);
    controller._initialize().then((_) { params.onPlatformViewCreated(params.id); });
    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
  void clearFocus() {
    // Currently this does nothing on Flutter Web.
    // TODO(het): Implement this. See https://github.com/flutter/flutter/issues/39496
  }

  @override
  void dispatchPointerEvent(PointerEvent event) {
    // We do not dispatch pointer events to HTML views because they may contain
    // cross-origin iframes, which only accept user-generated events.
  }

  @override
  void dispose() {
    if (_initialized) {
      // Asynchronously dispose this view.
      SystemChannels.platform_views.invokeMethod<void>('dispose', viewId);
    }
  }
}

419 420 421
class _AndroidViewState extends State<AndroidView> {
  int _id;
  AndroidViewController _controller;
422 423
  TextDirection _layoutDirection;
  bool _initialized = false;
424
  FocusNode _focusNode;
425

426
  static final Set<Factory<OneSequenceGestureRecognizer>> _emptyRecognizersSet =
427
    <Factory<OneSequenceGestureRecognizer>>{};
428

429 430
  @override
  Widget build(BuildContext context) {
431 432
    return Focus(
      focusNode: _focusNode,
433
      onFocusChange: _onFocusChange,
434
      child: _AndroidPlatformView(
435
        controller: _controller,
436
        hitTestBehavior: widget.hitTestBehavior,
437
        gestureRecognizers: widget.gestureRecognizers ?? _emptyRecognizersSet,
438
      ),
439
    );
440 441
  }

442 443 444 445 446
  void _initializeOnce() {
    if (_initialized) {
      return;
    }
    _initialized = true;
447
    _createNewAndroidView();
448
    _focusNode = FocusNode(debugLabel: 'AndroidView(id: $_id)');
449 450
  }

451 452 453 454 455 456 457
  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    final TextDirection newLayoutDirection = _findLayoutDirection();
    final bool didChangeLayoutDirection = _layoutDirection != newLayoutDirection;
    _layoutDirection = newLayoutDirection;

458
    _initializeOnce();
459 460 461 462 463 464 465
    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).
      _controller.setLayoutDirection(_layoutDirection);
    }
  }

466 467 468
  @override
  void didUpdateWidget(AndroidView oldWidget) {
    super.didUpdateWidget(oldWidget);
469 470 471 472 473 474 475 476

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

    if (widget.viewType != oldWidget.viewType) {
      _controller.dispose();
      _createNewAndroidView();
477
      return;
478 479 480 481 482 483 484 485 486 487
    }

    if (didChangeLayoutDirection) {
      _controller.setLayoutDirection(_layoutDirection);
    }
  }

  TextDirection _findLayoutDirection() {
    assert(widget.layoutDirection != null || debugCheckHasDirectionality(context));
    return widget.layoutDirection ?? Directionality.of(context);
488 489 490 491 492 493 494 495 496 497 498
  }

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

  void _createNewAndroidView() {
    _id = platformViewsRegistry.getNextPlatformViewId();
    _controller = PlatformViewsService.initAndroidView(
499 500 501 502 503
      id: _id,
      viewType: widget.viewType,
      layoutDirection: _layoutDirection,
      creationParams: widget.creationParams,
      creationParamsCodec: widget.creationParamsCodec,
504 505
      onFocus: () {
        _focusNode.requestFocus();
506
      },
507
    );
508 509 510
    if (widget.onPlatformViewCreated != null) {
      _controller.addOnPlatformViewCreatedListener(widget.onPlatformViewCreated);
    }
511
  }
512 513 514 515 516 517 518

  void _onFocusChange(bool isFocused) {
    if (!_controller.isCreated) {
      return;
    }
    if (!isFocused) {
      _controller.clearFocus().catchError((dynamic e) {
519 520 521 522 523 524 525 526 527
        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;
        }
528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545
      });
      return;
    }
    SystemChannels.textInput.invokeMethod<void>(
      'TextInput.setPlatformViewClient',
      _id,
    ).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;
      }
    });
  }
546 547
}

548 549 550 551 552 553
class _UiKitViewState extends State<UiKitView> {
  UiKitViewController _controller;
  TextDirection _layoutDirection;
  bool _initialized = false;

  static final Set<Factory<OneSequenceGestureRecognizer>> _emptyRecognizersSet =
554
    <Factory<OneSequenceGestureRecognizer>>{};
555 556 557 558 559 560 561

  @override
  Widget build(BuildContext context) {
    if (_controller == null) {
      return const SizedBox.expand();
    }
    return _UiKitPlatformView(
562
      controller: _controller,
563
      hitTestBehavior: widget.hitTestBehavior,
564
      gestureRecognizers: widget.gestureRecognizers ?? _emptyRecognizersSet,
565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621
    );
  }

  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).
      _controller?.setLayoutDirection(_layoutDirection);
    }
  }

  @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) {
      _controller?.setLayoutDirection(_layoutDirection);
    }
  }

  TextDirection _findLayoutDirection() {
    assert(widget.layoutDirection != null || debugCheckHasDirectionality(context));
    return widget.layoutDirection ?? Directionality.of(context);
  }

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

  Future<void> _createNewUiKitView() async {
622
    final int id = platformViewsRegistry.getNextPlatformViewId();
623
    final UiKitViewController controller = await PlatformViewsService.initUiKitView(
624
      id: id,
625 626
      viewType: widget.viewType,
      layoutDirection: _layoutDirection,
627 628
      creationParams: widget.creationParams,
      creationParamsCodec: widget.creationParamsCodec,
629 630 631 632 633 634
    );
    if (!mounted) {
      controller.dispose();
      return;
    }
    if (widget.onPlatformViewCreated != null) {
635
      widget.onPlatformViewCreated(id);
636 637 638 639 640
    }
    setState(() { _controller = controller; });
  }
}

641 642 643 644
class _AndroidPlatformView extends LeafRenderObjectWidget {
  const _AndroidPlatformView({
    Key key,
    @required this.controller,
645
    @required this.hitTestBehavior,
646
    @required this.gestureRecognizers,
647
  }) : assert(controller != null),
648
       assert(hitTestBehavior != null),
649
       assert(gestureRecognizers != null),
650 651 652
       super(key: key);

  final AndroidViewController controller;
653
  final PlatformViewHitTestBehavior hitTestBehavior;
654
  final Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers;
655 656 657

  @override
  RenderObject createRenderObject(BuildContext context) =>
658
      RenderAndroidView(
659 660 661 662
        viewController: controller,
        hitTestBehavior: hitTestBehavior,
        gestureRecognizers: gestureRecognizers,
      );
663 664 665 666

  @override
  void updateRenderObject(BuildContext context, RenderAndroidView renderObject) {
    renderObject.viewController = controller;
667
    renderObject.hitTestBehavior = hitTestBehavior;
668
    renderObject.updateGestureRecognizers(gestureRecognizers);
669 670
  }
}
671 672 673 674

class _UiKitPlatformView extends LeafRenderObjectWidget {
  const _UiKitPlatformView({
    Key key,
675
    @required this.controller,
676
    @required this.hitTestBehavior,
677 678
    @required this.gestureRecognizers,
  }) : assert(controller != null),
679
       assert(hitTestBehavior != null),
680
       assert(gestureRecognizers != null),
681 682
       super(key: key);

683
  final UiKitViewController controller;
684
  final PlatformViewHitTestBehavior hitTestBehavior;
685
  final Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers;
686 687 688 689

  @override
  RenderObject createRenderObject(BuildContext context) {
    return RenderUiKitView(
690
      viewController: controller,
691
      hitTestBehavior: hitTestBehavior,
692
      gestureRecognizers: gestureRecognizers,
693 694 695 696 697
    );
  }

  @override
  void updateRenderObject(BuildContext context, RenderUiKitView renderObject) {
698
    renderObject.viewController = controller;
699
    renderObject.hitTestBehavior = hitTestBehavior;
700
    renderObject.updateGestureRecognizers(gestureRecognizers);
701 702
  }
}
703

704 705
/// The parameters used to create a [PlatformViewController].
///
706 707 708
/// See also:
///
///  * [CreatePlatformViewCallback] which uses this object to create a [PlatformViewController].
709 710 711 712
class PlatformViewCreationParams {

  const PlatformViewCreationParams._({
    @required this.id,
713
    @required this.viewType,
714 715
    @required this.onPlatformViewCreated,
    @required this.onFocusChanged,
716 717 718 719 720 721 722 723
  }) : assert(id != null),
       assert(onPlatformViewCreated != null);

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

724 725 726 727 728 729
  /// 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;

730 731
  /// Callback invoked after the platform view has been created.
  final PlatformViewCreatedCallback onPlatformViewCreated;
732 733 734 735 736

  /// 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;
737 738 739 740 741 742 743
}

/// 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:
744 745
///
///  * [PlatformViewSurface], a common widget for presenting platform views.
746 747 748 749 750 751 752
typedef PlatformViewSurfaceFactory = Widget Function(BuildContext context, PlatformViewController controller);

/// Constructs a [PlatformViewController].
///
/// The [PlatformViewController.id] field of the created controller must match the value of the
/// params [PlatformViewCreationParams.id] field.
///
753 754 755
/// See also:
///
///  * [PlatformViewLink], which links a platform view with the Flutter framework.
756
typedef CreatePlatformViewCallback = PlatformViewController Function(PlatformViewCreationParams params);
757 758 759 760 761 762 763 764 765 766 767 768 769 770 771

/// 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.
///
/// {@macro flutter.widgets.platformViews.lifetime}
///
/// 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(
772
///       viewType: 'webview',
773
///       onCreatePlatformView: createFooWebView,
774 775 776 777 778 779 780 781 782 783 784 785
///       surfaceFactory: (BuildContext context, PlatformViewController controller) {
///        return PlatformViewSurface(
///            gestureRecognizers: gestureRecognizers,
///            controller: controller,
///            hitTestBehavior: PlatformViewHitTestBehavior.opaque,
///        );
///       },
///    );
///   }
/// }
/// ```
///
786 787
/// The `surfaceFactory` and the `onCreatePlatformView` are only called when the
/// state of this widget is initialized, or when the `viewType` changes.
788 789 790 791
class PlatformViewLink extends StatefulWidget {

  /// Construct a [PlatformViewLink] widget.
  ///
792
  /// The `surfaceFactory` and the `onCreatePlatformView` must not be null.
793 794
  ///
  /// See also:
795 796 797
  ///
  ///  * [PlatformViewSurface] for details on the widget returned by `surfaceFactory`.
  ///  * [PlatformViewCreationParams] for how each parameter can be used when implementing `createPlatformView`.
798 799 800
  const PlatformViewLink({
    Key key,
    @required PlatformViewSurfaceFactory surfaceFactory,
801
    @required CreatePlatformViewCallback onCreatePlatformView,
802
    @required this.viewType,
803
    }) : assert(surfaceFactory != null),
804 805 806 807 808
         assert(onCreatePlatformView != null),
         assert(viewType != null),
         _surfaceFactory = surfaceFactory,
         _onCreatePlatformView = onCreatePlatformView,
         super(key: key);
809 810 811


  final PlatformViewSurfaceFactory _surfaceFactory;
812
  final CreatePlatformViewCallback _onCreatePlatformView;
813

814 815 816 817 818
  /// The unique identifier for the view type to be embedded.
  ///
  /// Typically, this viewType has already been registered on the platform side.
  final String viewType;

819 820 821 822 823 824 825 826 827
  @override
  State<StatefulWidget> createState() => _PlatformViewLinkState();
}

class _PlatformViewLinkState extends State<PlatformViewLink> {

  int _id;
  PlatformViewController _controller;
  bool _platformViewCreated = false;
828
  Widget _surface;
829
  FocusNode _focusNode;
830 831 832 833 834 835 836

  @override
  Widget build(BuildContext context) {
    if (!_platformViewCreated) {
      return const SizedBox.expand();
    }
    _surface ??= widget._surfaceFactory(context, _controller);
837 838 839 840 841
    return Focus(
      focusNode: _focusNode,
      onFocusChange: _handleFrameworkFocusChanged,
      child: _surface,
    );
842 843 844 845
  }

  @override
  void initState() {
846
    _focusNode = FocusNode(debugLabel: 'PlatformView(id: $_id)',);
847 848 849 850
    _initialize();
    super.initState();
  }

851 852 853 854 855 856
  @override
  void didUpdateWidget(PlatformViewLink oldWidget) {
    super.didUpdateWidget(oldWidget);

    if (widget.viewType != oldWidget.viewType) {
      _controller?.dispose();
857 858 859
      // The _surface has to be recreated as its controller is disposed.
      // Setting _surface to null will trigger its creation in build().
      _surface = null;
860 861 862 863 864 865 866

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

867 868
  void _initialize() {
    _id = platformViewsRegistry.getNextPlatformViewId();
869 870
    _controller = widget._onCreatePlatformView(
      PlatformViewCreationParams._(
871 872 873 874
        id: _id,
        viewType: widget.viewType,
        onPlatformViewCreated: _onPlatformViewCreated,
        onFocusChanged: _handlePlatformFocusChanged,
875 876
      ),
    );
877 878 879
  }

  void _onPlatformViewCreated(int id) {
880
    setState(() { _platformViewCreated = true; });
881 882
  }

883 884 885 886 887 888 889 890 891 892 893 894
  void _handleFrameworkFocusChanged(bool isFocused) {
    if (!isFocused) {
      _controller?.clearFocus();
    }
  }

  void _handlePlatformFocusChanged(bool isFocused){
    if (isFocused) {
      _focusNode.requestFocus();
    }
  }

895 896 897
  @override
  void dispose() {
    _controller?.dispose();
898
    _controller = null;
899 900 901 902
    super.dispose();
  }
}

903 904 905 906 907 908 909 910 911 912 913 914
/// Integrates a platform 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 (e.g on Android platform views are composited using a [TextureLayer]).
/// 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:
915 916 917
///
///  * [AndroidView] which embeds an Android platform view in the widget hierarchy.
///  * [UIKitView] which embeds an iOS platform view in the widget hierarchy.
918 919 920 921 922 923 924
// 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({
925
    Key key,
926
    @required this.controller,
927 928 929 930
    @required this.hitTestBehavior,
    @required this.gestureRecognizers,
  }) : assert(controller != null),
       assert(hitTestBehavior != null),
931 932
       assert(gestureRecognizers != null),
       super(key: key);
933 934 935 936 937 938 939

  /// 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;

940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983
  /// Which gestures should be forwarded to the PlatformView.
  ///
  /// {@macro flutter.widgets.platformViews.gestureRecognizersDescHead}
  ///
  /// 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(),
  ///     ),
  ///   ),
  /// )
  /// ```
  ///
  /// {@macro flutter.widgets.platformViews.gestureRecognizersDescFoot}
  // 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;

  /// {@macro flutter.widgets.platformViews.hittestParam}
  final PlatformViewHitTestBehavior hitTestBehavior;

984 985
  @override
  RenderObject createRenderObject(BuildContext context) {
986
    return PlatformViewRenderBox(controller: controller, gestureRecognizers: gestureRecognizers, hitTestBehavior: hitTestBehavior);
987 988 989 990 991
  }

  @override
  void updateRenderObject(BuildContext context, PlatformViewRenderBox renderObject) {
    renderObject
992 993 994
      ..controller = controller
      ..hitTestBehavior = hitTestBehavior
      ..updateGestureRecognizers(gestureRecognizers);
995 996
  }
}