Fix Android platform view creation flow (#109232)
This commit is contained in:
@@ -179,17 +179,9 @@ class RenderAndroidView extends PlatformViewRenderBox {
|
||||
Size targetSize;
|
||||
do {
|
||||
targetSize = size;
|
||||
if (_viewController.isCreated) {
|
||||
_currentTextureSize = await _viewController.setSize(targetSize);
|
||||
if (_isDisposed) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
await _viewController.create(size: targetSize);
|
||||
if (_isDisposed) {
|
||||
return;
|
||||
}
|
||||
_currentTextureSize = targetSize;
|
||||
_currentTextureSize = await _viewController.setSize(targetSize);
|
||||
if (_isDisposed) {
|
||||
return;
|
||||
}
|
||||
// We've resized the platform view to targetSize, but it is possible that
|
||||
// while we were resizing the render object's size was changed again.
|
||||
|
||||
@@ -764,17 +764,33 @@ abstract class AndroidViewController extends PlatformViewController {
|
||||
Future<void> _sendDisposeMessage();
|
||||
|
||||
/// Sends the message to create the platform view with an initial [size].
|
||||
Future<void> _sendCreateMessage({Size? size});
|
||||
///
|
||||
/// Returns true if the view was actually created. In some cases (e.g.,
|
||||
/// trying to create a texture-based view with a null size) creation will
|
||||
/// fail and need to be re-attempted later.
|
||||
Future<bool> _sendCreateMessage({Size? size});
|
||||
|
||||
/// Sends the message to resize the platform view to [size].
|
||||
Future<Size> _sendResizeMessage(Size size);
|
||||
|
||||
@override
|
||||
bool get awaitingCreation => _state == _AndroidViewState.waitingForSize;
|
||||
|
||||
@override
|
||||
Future<void> create({Size? size}) async {
|
||||
assert(_state != _AndroidViewState.disposed, 'trying to create a disposed Android view');
|
||||
|
||||
await _sendCreateMessage(size: size);
|
||||
assert(_state == _AndroidViewState.waitingForSize, 'Android view is already sized. View id: $viewId');
|
||||
_state = _AndroidViewState.creating;
|
||||
final bool created = await _sendCreateMessage(size: size);
|
||||
|
||||
_state = _AndroidViewState.created;
|
||||
for (final PlatformViewCreatedCallback callback in _platformViewCreatedCallbacks) {
|
||||
callback(viewId);
|
||||
if (created) {
|
||||
_state = _AndroidViewState.created;
|
||||
for (final PlatformViewCreatedCallback callback in _platformViewCreatedCallbacks) {
|
||||
callback(viewId);
|
||||
}
|
||||
} else {
|
||||
_state = _AndroidViewState.waitingForSize;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -792,7 +808,17 @@ abstract class AndroidViewController extends PlatformViewController {
|
||||
///
|
||||
/// As a result, consumers are expected to clip the texture using [size], while using
|
||||
/// the return value to size the texture.
|
||||
Future<Size> setSize(Size size);
|
||||
Future<Size> setSize(Size size) async {
|
||||
assert(_state != _AndroidViewState.disposed, 'Android view is disposed. View id: $viewId');
|
||||
if (_state == _AndroidViewState.waitingForSize) {
|
||||
// Either `create` hasn't been called, or it couldn't run due to missing
|
||||
// size information, so create the view now.
|
||||
await create(size: size);
|
||||
return size;
|
||||
} else {
|
||||
return _sendResizeMessage(size);
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the offset of the platform view.
|
||||
///
|
||||
@@ -972,7 +998,7 @@ class ExpensiveAndroidViewController extends AndroidViewController {
|
||||
}) : super._();
|
||||
|
||||
@override
|
||||
Future<void> _sendCreateMessage({Size? size}) {
|
||||
Future<bool> _sendCreateMessage({Size? size}) async {
|
||||
final Map<String, dynamic> args = <String, dynamic>{
|
||||
'id': viewId,
|
||||
'viewType': _viewType,
|
||||
@@ -988,7 +1014,8 @@ class ExpensiveAndroidViewController extends AndroidViewController {
|
||||
paramsByteData.lengthInBytes,
|
||||
);
|
||||
}
|
||||
return SystemChannels.platform_views.invokeMethod<void>('create', args);
|
||||
await SystemChannels.platform_views.invokeMethod<void>('create', args);
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -1005,7 +1032,7 @@ class ExpensiveAndroidViewController extends AndroidViewController {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Size> setSize(Size size) {
|
||||
Future<Size> _sendResizeMessage(Size size) {
|
||||
throw UnimplementedError('Not supported for $SurfaceAndroidViewController.');
|
||||
}
|
||||
|
||||
@@ -1044,8 +1071,7 @@ class TextureAndroidViewController extends AndroidViewController {
|
||||
Offset _off = Offset.zero;
|
||||
|
||||
@override
|
||||
Future<Size> setSize(Size size) async {
|
||||
assert(_state != _AndroidViewState.disposed, 'Android view is disposed. View id: $viewId');
|
||||
Future<Size> _sendResizeMessage(Size size) async {
|
||||
assert(_state != _AndroidViewState.waitingForSize, 'Android view must have an initial size. View id: $viewId');
|
||||
assert(size != null);
|
||||
assert(!size.isEmpty);
|
||||
@@ -1064,16 +1090,6 @@ class TextureAndroidViewController extends AndroidViewController {
|
||||
return Size(meta!['width']! as double, meta['height']! as double);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> create({Size? size}) async {
|
||||
if (size == null) {
|
||||
return;
|
||||
}
|
||||
assert(_state == _AndroidViewState.waitingForSize, 'Android view is already sized. View id: $viewId');
|
||||
assert(!size.isEmpty);
|
||||
return super.create(size: size);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> setOffset(Offset off) async {
|
||||
if (off == _off) {
|
||||
@@ -1100,9 +1116,9 @@ class TextureAndroidViewController extends AndroidViewController {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> _sendCreateMessage({Size? size}) async {
|
||||
Future<bool> _sendCreateMessage({Size? size}) async {
|
||||
if (size == null) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
assert(!size.isEmpty, 'trying to create $TextureAndroidViewController without setting a valid size.');
|
||||
@@ -1123,6 +1139,7 @@ class TextureAndroidViewController extends AndroidViewController {
|
||||
);
|
||||
}
|
||||
_textureId = await SystemChannels.platform_views.invokeMethod<int>('create', args);
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -1219,6 +1236,15 @@ abstract class PlatformViewController {
|
||||
/// * [PlatformViewsRegistry], which is a helper for managing platform view IDs.
|
||||
int get viewId;
|
||||
|
||||
/// True if [create] has not been successfully called the platform view.
|
||||
///
|
||||
/// This can indicate either that [create] was never called, or that [create]
|
||||
/// was deferred for implementation-specific reasons.
|
||||
///
|
||||
/// A `false` return value does not necessarily indicate that the [Future]
|
||||
/// returned by [create] has completed, only that creation has been started.
|
||||
bool get awaitingCreation => false;
|
||||
|
||||
/// Dispatches the `event` to the platform view.
|
||||
Future<void> dispatchPointerEvent(PointerEvent event);
|
||||
|
||||
|
||||
@@ -871,14 +871,20 @@ class _PlatformViewLinkState extends State<PlatformViewLink> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (_controller == null) {
|
||||
final PlatformViewController? controller = _controller;
|
||||
if (controller == null) {
|
||||
return const SizedBox.expand();
|
||||
}
|
||||
if (!_platformViewCreated) {
|
||||
// Depending on the platform, the initial size can be used to size the platform view.
|
||||
return _PlatformViewPlaceHolder(onLayout: (Size size) => _controller!.create(size: size));
|
||||
// Depending on the implementation, the initial size can be used to size
|
||||
// the platform view.
|
||||
return _PlatformViewPlaceHolder(onLayout: (Size size) {
|
||||
if (controller.awaitingCreation) {
|
||||
controller.create(size: size);
|
||||
}
|
||||
});
|
||||
}
|
||||
_surface ??= widget._surfaceFactory(context, _controller!);
|
||||
_surface ??= widget._surfaceFactory(context, controller);
|
||||
return Focus(
|
||||
focusNode: _focusNode,
|
||||
onFocusChange: _handleFrameworkFocusChanged,
|
||||
|
||||
@@ -45,11 +45,16 @@ class FakePlatformViewController extends PlatformViewController {
|
||||
}
|
||||
|
||||
class FakeAndroidViewController implements AndroidViewController {
|
||||
FakeAndroidViewController(this.viewId);
|
||||
FakeAndroidViewController(this.viewId, {this.requiresSize = false});
|
||||
|
||||
bool disposed = false;
|
||||
bool focusCleared = false;
|
||||
bool created = false;
|
||||
// If true, [create] won't be considered to have been called successfully
|
||||
// unless it includes a size.
|
||||
bool requiresSize;
|
||||
|
||||
bool _createCalledSuccessfully = false;
|
||||
|
||||
/// Events that are dispatched.
|
||||
List<PointerEvent> dispatchedPointerEvents = <PointerEvent>[];
|
||||
@@ -92,6 +97,9 @@ class FakeAndroidViewController implements AndroidViewController {
|
||||
@override
|
||||
int get textureId => 0;
|
||||
|
||||
@override
|
||||
bool get awaitingCreation => !_createCalledSuccessfully;
|
||||
|
||||
@override
|
||||
bool get isCreated => created;
|
||||
|
||||
@@ -114,7 +122,10 @@ class FakeAndroidViewController implements AndroidViewController {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> create({Size? size}) async {}
|
||||
Future<void> create({Size? size}) async {
|
||||
assert(!_createCalledSuccessfully);
|
||||
_createCalledSuccessfully = size != null || !requiresSize;
|
||||
}
|
||||
|
||||
@override
|
||||
List<PlatformViewCreatedCallback> get createdCallbacks => <PlatformViewCreatedCallback>[];
|
||||
|
||||
@@ -1026,6 +1026,7 @@ void main() {
|
||||
|
||||
containerFocusNode.requestFocus();
|
||||
|
||||
viewsController.createCompleter!.complete();
|
||||
await tester.pump();
|
||||
|
||||
expect(containerFocusNode.hasFocus, isTrue);
|
||||
@@ -2423,7 +2424,110 @@ void main() {
|
||||
onCreatePlatformView: (PlatformViewCreationParams params) {
|
||||
onPlatformViewCreatedCallBack = params.onPlatformViewCreated;
|
||||
createdPlatformViewId = params.id;
|
||||
return FakePlatformViewController(params.id);
|
||||
return FakePlatformViewController(params.id)..create();
|
||||
},
|
||||
surfaceFactory: (BuildContext context, PlatformViewController controller) {
|
||||
return PlatformViewSurface(
|
||||
gestureRecognizers: const <Factory<OneSequenceGestureRecognizer>>{},
|
||||
controller: controller,
|
||||
hitTestBehavior: PlatformViewHitTestBehavior.opaque,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
await tester.pumpWidget(platformViewLink);
|
||||
|
||||
expect(
|
||||
tester.allWidgets.map((Widget widget) => widget.runtimeType.toString()).toList(),
|
||||
equals(<String>['PlatformViewLink', '_PlatformViewPlaceHolder']),
|
||||
);
|
||||
|
||||
onPlatformViewCreatedCallBack(createdPlatformViewId);
|
||||
|
||||
await tester.pump();
|
||||
|
||||
expect(
|
||||
tester.allWidgets.map((Widget widget) => widget.runtimeType.toString()).toList(),
|
||||
equals(<String>['PlatformViewLink', 'Focus', '_FocusMarker', 'Semantics', 'PlatformViewSurface']),
|
||||
);
|
||||
|
||||
expect(createdPlatformViewId, currentViewId + 1);
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets(
|
||||
'PlatformViewLink calls create when needed for Android texture display modes',
|
||||
(WidgetTester tester) async {
|
||||
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
|
||||
late int createdPlatformViewId;
|
||||
|
||||
late PlatformViewCreatedCallback onPlatformViewCreatedCallBack;
|
||||
late PlatformViewController controller;
|
||||
|
||||
final PlatformViewLink platformViewLink = PlatformViewLink(
|
||||
viewType: 'webview',
|
||||
onCreatePlatformView: (PlatformViewCreationParams params) {
|
||||
onPlatformViewCreatedCallBack = params.onPlatformViewCreated;
|
||||
createdPlatformViewId = params.id;
|
||||
controller = FakeAndroidViewController(params.id, requiresSize: true);
|
||||
controller.create();
|
||||
// This test should be simulating one of the texture-based display
|
||||
// modes, where `create` is a no-op when not provided a size, and
|
||||
// creation is triggered via a later call to setSize, or to `create`
|
||||
// with a size.
|
||||
expect(controller.awaitingCreation, true);
|
||||
return controller;
|
||||
},
|
||||
surfaceFactory: (BuildContext context, PlatformViewController controller) {
|
||||
return PlatformViewSurface(
|
||||
gestureRecognizers: const <Factory<OneSequenceGestureRecognizer>>{},
|
||||
controller: controller,
|
||||
hitTestBehavior: PlatformViewHitTestBehavior.opaque,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
await tester.pumpWidget(platformViewLink);
|
||||
|
||||
expect(
|
||||
tester.allWidgets.map((Widget widget) => widget.runtimeType.toString()).toList(),
|
||||
equals(<String>['PlatformViewLink', '_PlatformViewPlaceHolder']),
|
||||
);
|
||||
|
||||
onPlatformViewCreatedCallBack(createdPlatformViewId);
|
||||
|
||||
await tester.pump();
|
||||
|
||||
expect(
|
||||
tester.allWidgets.map((Widget widget) => widget.runtimeType.toString()).toList(),
|
||||
equals(<String>['PlatformViewLink', 'Focus', '_FocusMarker', 'Semantics', 'PlatformViewSurface']),
|
||||
);
|
||||
|
||||
expect(createdPlatformViewId, currentViewId + 1);
|
||||
expect(controller.awaitingCreation, false);
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets(
|
||||
'PlatformViewLink does not double-call create for Android Hybrid Composition',
|
||||
(WidgetTester tester) async {
|
||||
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
|
||||
late int createdPlatformViewId;
|
||||
|
||||
late PlatformViewCreatedCallback onPlatformViewCreatedCallBack;
|
||||
late PlatformViewController controller;
|
||||
|
||||
final PlatformViewLink platformViewLink = PlatformViewLink(
|
||||
viewType: 'webview',
|
||||
onCreatePlatformView: (PlatformViewCreationParams params) {
|
||||
onPlatformViewCreatedCallBack = params.onPlatformViewCreated;
|
||||
createdPlatformViewId = params.id;
|
||||
controller = FakeAndroidViewController(params.id);
|
||||
controller.create();
|
||||
// This test should be simulating Hybrid Composition mode, where
|
||||
// `create` takes effect immidately.
|
||||
expect(controller.awaitingCreation, false);
|
||||
return controller;
|
||||
},
|
||||
surfaceFactory: (BuildContext context, PlatformViewController controller) {
|
||||
return PlatformViewSurface(
|
||||
|
||||
Reference in New Issue
Block a user