From 5acf63d35a4a8da46060f807c6005badd217588b Mon Sep 17 00:00:00 2001 From: Chris Yang Date: Thu, 15 Aug 2019 12:16:38 -0700 Subject: [PATCH] PlatformViewLink, handling creation of the PlatformViewSurface and dispose PlatformViewController (#37703) * link * review fixes * review fixes * remove extra line --- .../lib/src/services/platform_views.dart | 5 + .../lib/src/widgets/platform_view.dart | 130 ++++++++++++++++++ .../test/services/fake_platform_views.dart | 8 ++ .../test/widgets/platform_view_test.dart | 109 +++++++++++++++ 4 files changed, 252 insertions(+) diff --git a/packages/flutter/lib/src/services/platform_views.dart b/packages/flutter/lib/src/services/platform_views.dart index 3b43865ec2..b8df4b2c6e 100644 --- a/packages/flutter/lib/src/services/platform_views.dart +++ b/packages/flutter/lib/src/services/platform_views.dart @@ -729,4 +729,9 @@ abstract class PlatformViewController { /// Dispatches the `event` to the platform view. void dispatchPointerEvent(PointerEvent event); + + /// Disposes the platform view. + /// + /// The [PlatformViewController] is unusable after calling dispose. + void dispose(); } diff --git a/packages/flutter/lib/src/widgets/platform_view.dart b/packages/flutter/lib/src/widgets/platform_view.dart index 1a7b8c83ff..33cc5193f1 100644 --- a/packages/flutter/lib/src/widgets/platform_view.dart +++ b/packages/flutter/lib/src/widgets/platform_view.dart @@ -581,6 +581,136 @@ class _UiKitPlatformView extends LeafRenderObjectWidget { } } +/// The parameters used to create a [PlatformViewController]. +/// +/// See also [CreatePlatformViewController] which uses this object to create a [PlatformViewController]. +class PlatformViewCreationParams { + + const PlatformViewCreationParams._({ + @required this.id, + @required this.onPlatformViewCreated + }) : assert(id != null), + assert(onPlatformViewCreated != null); + + /// The unique identifier for the new platform view. + /// + /// [PlatformViewController.viewId] should match this id. + final int id; + + /// Callback invoked after the platform view has been created. + final PlatformViewCreatedCallback onPlatformViewCreated; +} + +/// 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: +/// * [PlatformViewSurface], a common widget for presenting platform views. +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. +/// +/// See also [PlatformViewLink.onCreate]. +typedef CreatePlatformViewController = PlatformViewController Function(PlatformViewCreationParams params); + +/// 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( +/// createCallback: createFooWebView, +/// surfaceFactory: (BuildContext context, PlatformViewController controller) { +/// return PlatformViewSurface( +/// gestureRecognizers: gestureRecognizers, +/// controller: controller, +/// hitTestBehavior: PlatformViewHitTestBehavior.opaque, +/// ); +/// }, +/// ); +/// } +/// } +/// ``` +/// +/// The `surfaceFactory` and the `createPlatformViewController` only take affect when the state of this widget is initialized. +/// If the widget is rebuilt without losing its state, `surfaceFactory` and `createPlatformViewController` are ignored. +class PlatformViewLink extends StatefulWidget { + + /// Construct a [PlatformViewLink] widget. + /// + /// The `surfaceFactory` and the `createPlatformViewController` must not be null. + /// + /// See also: + /// * [PlatformViewSurface] for details on the widget returned by `surfaceFactory`. + /// * [PlatformViewCreationParams] for how each parameter can be used when implementing `createPlatformView`. + const PlatformViewLink({ + Key key, + @required PlatformViewSurfaceFactory surfaceFactory, + @required CreatePlatformViewController createPlatformViewController, + }) : assert(surfaceFactory != null), + assert(createPlatformViewController != null), + _surfaceFactory = surfaceFactory, + _createPlatformViewController = createPlatformViewController, + super(key: key); + + + final PlatformViewSurfaceFactory _surfaceFactory; + final CreatePlatformViewController _createPlatformViewController; + + @override + State createState() => _PlatformViewLinkState(); +} + +class _PlatformViewLinkState extends State { + + int _id; + PlatformViewController _controller; + bool _platformViewCreated = false; + PlatformViewSurface _surface; + + @override + Widget build(BuildContext context) { + if (!_platformViewCreated) { + return const SizedBox.expand(); + } + _surface ??= widget._surfaceFactory(context, _controller); + return _surface; + } + + @override + void initState() { + _initialize(); + super.initState(); + } + + void _initialize() { + _id = platformViewsRegistry.getNextPlatformViewId(); + _controller = widget._createPlatformViewController(PlatformViewCreationParams._(id:_id, onPlatformViewCreated:_onPlatformViewCreated)); + } + + void _onPlatformViewCreated(int id) { + setState(() => _platformViewCreated = true); + } + + @override + void dispose() { + _controller?.dispose(); + super.dispose(); + } +} + /// 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] diff --git a/packages/flutter/test/services/fake_platform_views.dart b/packages/flutter/test/services/fake_platform_views.dart index d25783bece..84a1bde726 100644 --- a/packages/flutter/test/services/fake_platform_views.dart +++ b/packages/flutter/test/services/fake_platform_views.dart @@ -17,6 +17,8 @@ class FakePlatformViewController extends PlatformViewController { _id = id; } + bool disposed = false; + /// Events that are dispatched; List dispatchedPointerEvents = []; @@ -32,6 +34,12 @@ class FakePlatformViewController extends PlatformViewController { void clearTestingVariables() { dispatchedPointerEvents.clear(); + disposed = false; + } + + @override + void dispose() { + disposed = true; } } diff --git a/packages/flutter/test/widgets/platform_view_test.dart b/packages/flutter/test/widgets/platform_view_test.dart index e5d86532cd..9ee156082e 100644 --- a/packages/flutter/test/widgets/platform_view_test.dart +++ b/packages/flutter/test/widgets/platform_view_test.dart @@ -1932,5 +1932,114 @@ void main() { ); expect(factoryInvocationCount, 1); }); + + testWidgets('PlatformViewLink Widget init, should create a SizedBox widget before onPlatformViewCreated and a PlatformViewSurface after', (WidgetTester tester) async { + final int currentViewId = platformViewsRegistry.getNextPlatformViewId(); + int createdPlatformViewId; + + PlatformViewCreatedCallback onPlatformViewCreatedCallBack; + + final PlatformViewLink platformViewLink = PlatformViewLink(createPlatformViewController: (PlatformViewCreationParams params){ + onPlatformViewCreatedCallBack = params.onPlatformViewCreated; + createdPlatformViewId = params.id; + return FakePlatformViewController(params.id); + }, surfaceFactory: (BuildContext context, PlatformViewController controller) { + return PlatformViewSurface( + gestureRecognizers: const >{}, + controller: controller, + hitTestBehavior: PlatformViewHitTestBehavior.opaque, + ); + }); + + await tester.pumpWidget(platformViewLink); + final SizedBox sizedBox = tester.allWidgets.firstWhere((Widget widget) => widget is SizedBox); + expect(sizedBox, isNotNull); + + onPlatformViewCreatedCallBack(createdPlatformViewId); + + await tester.pump(); + + final PlatformViewSurface surface = tester.allWidgets.firstWhere((Widget widget) => widget is PlatformViewSurface); + expect(surface, isNotNull); + + expect(createdPlatformViewId, currentViewId+1); + }); + + testWidgets('PlatformViewLink Widget dispose', (WidgetTester tester) async { + FakePlatformViewController disposedController; + final PlatformViewLink platformViewLink = PlatformViewLink(createPlatformViewController: (PlatformViewCreationParams params){ + params.onPlatformViewCreated(params.id); + disposedController = FakePlatformViewController(params.id); + params.onPlatformViewCreated(params.id); + return disposedController; + }, surfaceFactory: (BuildContext context,PlatformViewController controller) { + return PlatformViewSurface( + gestureRecognizers: const >{}, + controller: controller, + hitTestBehavior: PlatformViewHitTestBehavior.opaque, + ); + }); + + await tester.pumpWidget(platformViewLink); + + await tester.pumpWidget(Container()); + + expect(disposedController.disposed, true); + }); + + testWidgets('PlatformViewLink widget survives widget tree change', (WidgetTester tester) async { + final GlobalKey key = GlobalKey(); + final int currentViewId = platformViewsRegistry.getNextPlatformViewId(); + final List ids = []; + + FakePlatformViewController controller; + + PlatformViewLink createPlatformViewLink() { + return PlatformViewLink( + key: key, + createPlatformViewController: (PlatformViewCreationParams params){ + ids.add(params.id); + controller = FakePlatformViewController(params.id); + params.onPlatformViewCreated(params.id); + return controller; + }, + surfaceFactory: (BuildContext context, PlatformViewController controller) { + return PlatformViewSurface( + gestureRecognizers: const >{}, + controller: controller, + hitTestBehavior: PlatformViewHitTestBehavior.opaque, + ); + }, + ); + } + await tester.pumpWidget( + Center( + child: SizedBox( + width: 200.0, + height: 100.0, + child: createPlatformViewLink(), + ), + ), + ); + + await tester.pumpWidget( + Center( + child: Container( + child: SizedBox( + width: 200.0, + height: 100.0, + child: createPlatformViewLink(), + ), + ), + ), + ); + + expect( + ids, + unorderedEquals([ + currentViewId+1, + ]), + ); + }); }); }