From 78c74116a90f2ca52386f14ddf183f815437482b Mon Sep 17 00:00:00 2001 From: Ian Hickson Date: Mon, 26 Sep 2016 10:27:53 -0700 Subject: [PATCH] LayoutBuilder + GlobalKey + setState assert (#6068) This silences an assertion that fired when reparenting a widget with a global key inside a LayoutBuilder callback when that callback also happened to call setState (directly or indirectly) on that widget. Normally such setStates are considered ok since we know we haven't cleaned that subtree yet, but we were not correctly handling the case where the list needed resorting in that situation. --- .../flutter/lib/src/widgets/framework.dart | 3 + .../layout_builder_and_global_keys_test.dart | 59 +++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 packages/flutter/test/widget/layout_builder_and_global_keys_test.dart diff --git a/packages/flutter/lib/src/widgets/framework.dart b/packages/flutter/lib/src/widgets/framework.dart index 5f32907e3f5..e85bc1309ba 100644 --- a/packages/flutter/lib/src/widgets/framework.dart +++ b/packages/flutter/lib/src/widgets/framework.dart @@ -1495,6 +1495,8 @@ class BuildOwner { /// widget tree. void markNeedsToResortDirtyElements() { assert(() { + if (debugPrintScheduleBuildForStacks) + debugPrintStack(label: 'markNeedsToResortDirtyElements() called; _dirtyElementsNeedsResorting was $_dirtyElementsNeedsResorting (now true); dirty list is: $_dirtyElements'); if (_dirtyElementsNeedsResorting == null) { throw new FlutterError( 'markNeedsToResortDirtyElements() called inappropriately.\n' @@ -1633,6 +1635,7 @@ class BuildOwner { _debugCurrentBuildTarget = context; return true; }); + _dirtyElementsNeedsResorting = false; try { callback(); } finally { diff --git a/packages/flutter/test/widget/layout_builder_and_global_keys_test.dart b/packages/flutter/test/widget/layout_builder_and_global_keys_test.dart new file mode 100644 index 00000000000..5dc5dad15bb --- /dev/null +++ b/packages/flutter/test/widget/layout_builder_and_global_keys_test.dart @@ -0,0 +1,59 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter_test/flutter_test.dart' hide TypeMatcher; +import 'package:flutter/widgets.dart'; + +class Wrapper extends StatelessWidget { + Wrapper({ + Key key, + this.child, + }) : super(key: key); + + final Widget child; + + @override + Widget build(BuildContext context) => child; +} + +class StatefulWrapper extends StatefulWidget { + StatefulWrapper({ + Key key, + this.child, + }) : super(key: key); + + final Widget child; + + @override + StatefulWrapperState createState() => new StatefulWrapperState(); +} + +class StatefulWrapperState extends State { + + void trigger() { + setState(() { /* for test purposes */ }); + } + + @override + Widget build(BuildContext context) => config.child; +} + +void main() { + testWidgets('Moving global key inside a LayoutBuilder', (WidgetTester tester) async { + GlobalKey key = new GlobalKey(); + await tester.pumpWidget( + new LayoutBuilder(builder: (BuildContext context, BoxConstraints constraints) { + return new Wrapper( + child: new StatefulWrapper(key: key, child: new Container(height: 100.0)), + ); + }), + ); + await tester.pumpWidget( + new LayoutBuilder(builder: (BuildContext context, BoxConstraints constraints) { + key.currentState.trigger(); + return new StatefulWrapper(key: key, child: new Container(height: 100.0)); + }), + ); + }); +}