From 6eb002a1679dfbf0bbc7bb9ebb68d743504565f0 Mon Sep 17 00:00:00 2001 From: Michael Goderbauer Date: Thu, 22 Dec 2022 14:05:25 -0800 Subject: [PATCH] Reland "Remove single-view assumption from widgets library (#117480)" (#117549) * Revert "Revert "Remove single-view assumption from widgets library (#117480)" (#117545)" This reverts commit b8d5d9c46ca084a707caa1e994d896cb977662d2. * check for mounted --- .../lib/src/widgets/editable_text.dart | 13 +++++++---- .../lib/src/widgets/scroll_physics.dart | 3 ++- .../lib/src/widgets/semantics_debugger.dart | 5 +++-- .../lib/src/widgets/widget_inspector.dart | 5 ++++- .../test/widgets/editable_text_test.dart | 22 +++++++++++++++++++ 5 files changed, 40 insertions(+), 8 deletions(-) diff --git a/packages/flutter/lib/src/widgets/editable_text.dart b/packages/flutter/lib/src/widgets/editable_text.dart index b7c3f27784..448b9ec5bf 100644 --- a/packages/flutter/lib/src/widgets/editable_text.dart +++ b/packages/flutter/lib/src/widgets/editable_text.dart @@ -43,6 +43,7 @@ import 'text_editing_intents.dart'; import 'text_selection.dart'; import 'text_selection_toolbar_anchors.dart'; import 'ticker_provider.dart'; +import 'view.dart'; import 'widget_span.dart'; export 'package:flutter/services.dart' show SelectionChangedCause, SmartDashesType, SmartQuotesType, TextEditingValue, TextInputType, TextSelection; @@ -3303,17 +3304,21 @@ class EditableTextState extends State with AutomaticKeepAliveClien @override void didChangeMetrics() { - if (_lastBottomViewInset != WidgetsBinding.instance.window.viewInsets.bottom) { + if (!mounted) { + return; + } + final ui.FlutterView view = View.of(context); + if (_lastBottomViewInset != view.viewInsets.bottom) { SchedulerBinding.instance.addPostFrameCallback((Duration _) { _selectionOverlay?.updateForScroll(); }); - if (_lastBottomViewInset < WidgetsBinding.instance.window.viewInsets.bottom) { + if (_lastBottomViewInset < view.viewInsets.bottom) { // Because the metrics change signal from engine will come here every frame // (on both iOS and Android). So we don't need to show caret with animation. _scheduleShowCaretOnScreen(withAnimation: false); } } - _lastBottomViewInset = WidgetsBinding.instance.window.viewInsets.bottom; + _lastBottomViewInset = view.viewInsets.bottom; } Future _performSpellCheck(final String text) async { @@ -3516,7 +3521,7 @@ class EditableTextState extends State with AutomaticKeepAliveClien if (_hasFocus) { // Listen for changing viewInsets, which indicates keyboard showing up. WidgetsBinding.instance.addObserver(this); - _lastBottomViewInset = WidgetsBinding.instance.window.viewInsets.bottom; + _lastBottomViewInset = View.of(context).viewInsets.bottom; if (!widget.readOnly) { _scheduleShowCaretOnScreen(withAnimation: true); } diff --git a/packages/flutter/lib/src/widgets/scroll_physics.dart b/packages/flutter/lib/src/widgets/scroll_physics.dart index 3c1d259f2b..bca75a0228 100644 --- a/packages/flutter/lib/src/widgets/scroll_physics.dart +++ b/packages/flutter/lib/src/widgets/scroll_physics.dart @@ -14,6 +14,7 @@ import 'framework.dart'; import 'overscroll_indicator.dart'; import 'scroll_metrics.dart'; import 'scroll_simulation.dart'; +import 'view.dart'; export 'package:flutter/physics.dart' show ScrollSpringSimulation, Simulation, Tolerance; @@ -252,7 +253,7 @@ class ScrollPhysics { assert(metrics != null); assert(context != null); if (parent == null) { - final double maxPhysicalPixels = WidgetsBinding.instance.window.physicalSize.longestSide; + final double maxPhysicalPixels = View.of(context).physicalSize.longestSide; return velocity.abs() > maxPhysicalPixels; } return parent!.recommendDeferredLoading(velocity, metrics, context); diff --git a/packages/flutter/lib/src/widgets/semantics_debugger.dart b/packages/flutter/lib/src/widgets/semantics_debugger.dart index 1fe00962c3..6656979c71 100644 --- a/packages/flutter/lib/src/widgets/semantics_debugger.dart +++ b/packages/flutter/lib/src/widgets/semantics_debugger.dart @@ -12,6 +12,7 @@ import 'basic.dart'; import 'binding.dart'; import 'framework.dart'; import 'gesture_detector.dart'; +import 'view.dart'; /// A widget that visualizes the semantics for the child. /// @@ -96,7 +97,7 @@ class _SemanticsDebuggerState extends State with WidgetsBindi Offset? _lastPointerDownLocation; void _handlePointerDown(PointerDownEvent event) { setState(() { - _lastPointerDownLocation = event.position * WidgetsBinding.instance.window.devicePixelRatio; + _lastPointerDownLocation = event.position * View.of(context).devicePixelRatio; }); // TODO(ianh): Use a gesture recognizer so that we can reset the // _lastPointerDownLocation when none of the other gesture recognizers win. @@ -159,7 +160,7 @@ class _SemanticsDebuggerState extends State with WidgetsBindi _pipelineOwner, _client.generation, _lastPointerDownLocation, // in physical pixels - WidgetsBinding.instance.window.devicePixelRatio, + View.of(context).devicePixelRatio, widget.labelStyle, ), child: GestureDetector( diff --git a/packages/flutter/lib/src/widgets/widget_inspector.dart b/packages/flutter/lib/src/widgets/widget_inspector.dart index 130bc3eb79..6135736216 100644 --- a/packages/flutter/lib/src/widgets/widget_inspector.dart +++ b/packages/flutter/lib/src/widgets/widget_inspector.dart @@ -10,6 +10,7 @@ import 'dart:math' as math; import 'dart:ui' as ui show ClipOp, + FlutterView, Image, ImageByteFormat, Paragraph, @@ -30,6 +31,7 @@ import 'debug.dart'; import 'framework.dart'; import 'gesture_detector.dart'; import 'service_extensions.dart'; +import 'view.dart'; /// Signature for the builder callback used by /// [WidgetInspector.selectButtonBuilder]. @@ -2726,7 +2728,8 @@ class _WidgetInspectorState extends State // on the edge of the display. If the pointer is being dragged off the edge // of the display we do not want to select anything. A user can still select // a widget that is only at the exact screen margin by tapping. - final Rect bounds = (Offset.zero & (WidgetsBinding.instance.window.physicalSize / WidgetsBinding.instance.window.devicePixelRatio)).deflate(_kOffScreenMargin); + final ui.FlutterView view = View.of(context); + final Rect bounds = (Offset.zero & (view.physicalSize / view.devicePixelRatio)).deflate(_kOffScreenMargin); if (!bounds.contains(_lastPointerLocation!)) { setState(() { selection.clear(); diff --git a/packages/flutter/test/widgets/editable_text_test.dart b/packages/flutter/test/widgets/editable_text_test.dart index 4cc4d13ff9..fda5c401e6 100644 --- a/packages/flutter/test/widgets/editable_text_test.dart +++ b/packages/flutter/test/widgets/editable_text_test.dart @@ -14398,6 +14398,28 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async expect(tester.takeException(), null); }); + + testWidgets('does not crash when didChangeMetrics is called after unmounting', (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: EditableText( + controller: controller, + focusNode: FocusNode(), + style: Typography.material2018().black.titleMedium!, + cursorColor: Colors.blue, + backgroundCursorColor: Colors.grey, + ), + ), + ); + + final EditableTextState state = tester.state(find.byType(EditableText)); + + // Disposes the EditableText. + await tester.pumpWidget(const Placeholder()); + + // Shouldn't crash. + state.didChangeMetrics(); + }); } class UnsettableController extends TextEditingController {