diff --git a/packages/flutter/lib/src/rendering/object.dart b/packages/flutter/lib/src/rendering/object.dart index 9c0df3882c..140ee48a6e 100644 --- a/packages/flutter/lib/src/rendering/object.dart +++ b/packages/flutter/lib/src/rendering/object.dart @@ -2958,7 +2958,7 @@ abstract class RenderObject with DiagnosticableTreeMixin implements HitTestTarge if (!isRepaintBoundary && _wasRepaintBoundary) { _needsPaint = false; _needsCompositedLayerUpdate = false; - owner?._nodesNeedingPaint.remove(this); + owner?._nodesNeedingPaint.removeWhere((RenderObject t) => identical(t, this)); _needsCompositingBitsUpdate = false; markNeedsPaint(); } else if (oldNeedsCompositing != _needsCompositing) { diff --git a/packages/flutter/test/rendering/object_test.dart b/packages/flutter/test/rendering/object_test.dart index ae9b38c31d..f38c0f0055 100644 --- a/packages/flutter/test/rendering/object_test.dart +++ b/packages/flutter/test/rendering/object_test.dart @@ -378,6 +378,25 @@ void main() { expect(calledBack, true); }); + test('Change isRepaintBoundary after both markNeedsCompositedLayerUpdate and markNeedsPaint', () { + List? caughtErrors; + TestRenderingFlutterBinding.instance.onErrors = () { + caughtErrors = TestRenderingFlutterBinding.instance.takeAllFlutterErrorDetails().toList(); + }; + final TestRenderObject object = TestRenderObject(allowPaintBounds: true); + object.isRepaintBoundary = true; + object.attach(TestRenderingFlutterBinding.instance.pipelineOwner); + object.layout(const BoxConstraints.tightForFinite()); + PaintingContext.repaintCompositedChild(object, debugAlsoPaintedParent: true); + + object.markNeedsCompositedLayerUpdate(); + object.markNeedsPaint(); + object.isRepaintBoundary = false; + object.markNeedsCompositingBitsUpdate(); + TestRenderingFlutterBinding.instance.pumpCompleteFrame(); + expect(caughtErrors, isNull); + }); + test('ContainerParentDataMixin asserts parentData type', () { final TestRenderObject renderObject = TestRenderObjectWithoutSetupParentData(); final TestRenderObject child = TestRenderObject(); diff --git a/packages/flutter/test/widgets/fade_transition_test.dart b/packages/flutter/test/widgets/fade_transition_test.dart index b606c95137..4f8136cf6f 100644 --- a/packages/flutter/test/widgets/fade_transition_test.dart +++ b/packages/flutter/test/widgets/fade_transition_test.dart @@ -10,7 +10,7 @@ void main() { testWidgets('FadeTransition', (WidgetTester tester) async { final DebugPrintCallback oldPrint = debugPrint; final List log = []; - debugPrint = (String? message, { int? wrapWidth }) { + debugPrint = (String? message, {int? wrapWidth}) { log.add(message!); }; debugPrintBuildScope = true; @@ -33,4 +33,24 @@ void main() { debugPrint = oldPrint; debugPrintBuildScope = false; }); + + // Regression test for https://github.com/flutter/flutter/issues/157312 + testWidgets('No exception when calling markNeedsPaint during opacity changes', (WidgetTester tester) async { + final GlobalKey key = GlobalKey(); + final AnimationController controller = AnimationController( + vsync: const TestVSync(), + value: 1, + duration: const Duration(seconds: 2), + ); + addTearDown(controller.dispose); + await tester.pumpWidget(FadeTransition( + opacity: controller, + child: Placeholder(key: key), + )); + controller.value = 0.5; + key.currentContext?.findRenderObject()?.markNeedsPaint(); + controller.value = 0; + await tester.pump(); + expect(tester.takeException(), isNull); + }); }