Remove "disqualified" concept Allows a non-initialised stateful component to be used as a source of settings more than once. Instead of asserting that it was only being used once, we assert that once you are stateful you don't get used as a bag of settings, which is the real thing we were trying to avoid. A lot of code ends up using StatefulComponents as a source multiple times in a row, and before this would fail. Patch by Ian Hickson
diff --git a/sky/packages/sky/lib/src/widgets/framework.dart b/sky/packages/sky/lib/src/widgets/framework.dart index 7b887a2..09d5dd2 100644 --- a/sky/packages/sky/lib/src/widgets/framework.dart +++ b/sky/packages/sky/lib/src/widgets/framework.dart
@@ -879,25 +879,13 @@ StatefulComponent({ Key key }) : super(key: key); - bool _disqualifiedFromEverAppearingAgain = false; bool _isStateInitialized = false; - void didMount() { - assert(!_disqualifiedFromEverAppearingAgain); - super.didMount(); - } - - void _buildIfDirty() { - assert(!_disqualifiedFromEverAppearingAgain); - super._buildIfDirty(); - } - bool retainStatefulNodeIfPossible(StatefulComponent newNode) { - assert(!_disqualifiedFromEverAppearingAgain); assert(newNode != null); + assert(!newNode._isStateInitialized); assert(_canSync(this, newNode)); assert(_child != null); - newNode._disqualifiedFromEverAppearingAgain = true; newNode._child = _child; _child = null; @@ -910,14 +898,17 @@ // when _sync is called, our 'old' is actually the new instance that // we are to copy state from. void _sync(Widget old, dynamic slot) { - assert(!_disqualifiedFromEverAppearingAgain); - // TODO(ianh): _sync should only be called once when old == null - if (old == null && !_isStateInitialized) { - initState(); - _isStateInitialized = true; + if (old == null) { + if (!_isStateInitialized) { + initState(); + _isStateInitialized = true; + } } - if (old != null) + if (old != null) { + assert(_isStateInitialized); + assert(!old._isStateInitialized); syncConstructorArguments(old); + } super._sync(old, slot); } @@ -930,25 +921,21 @@ // method to update `this` to account for the new values the parent // passed to `source`. Make sure to call super.syncConstructorArguments(source) // unless you are extending StatefulComponent directly. + // A given source can be used multiple times as a source. + // The source must not be mutated. void syncConstructorArguments(Component source); - Widget syncChild(Widget node, Widget oldNode, dynamic slot) { - assert(!_disqualifiedFromEverAppearingAgain); - return super.syncChild(node, oldNode, slot); - } - // Calls function fn immediately and then schedules another build // for this Component. void setState(void fn()) { - assert(!_disqualifiedFromEverAppearingAgain); fn(); _scheduleBuild(); } String toStringName() { - if (_disqualifiedFromEverAppearingAgain) - return '[[DISQUALIFIED]] ${super.toStringName()}'; - return super.toStringName(); + if (_isStateInitialized) + return 'Stateful ${super.toStringName()}'; + return 'Stateless ${super.toStringName()}'; } }
diff --git a/sky/unit/test/widget/stateful_components_test.dart b/sky/unit/test/widget/stateful_components_test.dart new file mode 100644 index 0000000..782a296 --- /dev/null +++ b/sky/unit/test/widget/stateful_components_test.dart
@@ -0,0 +1,66 @@ +import 'package:sky/animation.dart'; +import 'package:sky/widgets.dart'; +import 'package:test/test.dart'; + +import 'widget_tester.dart'; + +class InnerComponent extends StatefulComponent { + InnerComponent(); + + bool _didInitState = false; + + void initState() { + _didInitState = true; + } + + void syncConstructorArguments(InnerComponent source) { + } + + Widget build() { + return new Container(); + } +} + +class OutterContainer extends StatefulComponent { + OutterContainer({ this.child }); + + InnerComponent child; + + void syncConstructorArguments(OutterContainer source) { + child = source.child; + } + + Widget build() { + return child; + } +} + +void main() { + test('resync stateful widget', () { + + WidgetTester tester = new WidgetTester(); + + InnerComponent inner; + OutterContainer outter; + + tester.pumpFrame(() { + return new OutterContainer(child: new InnerComponent()); + }); + + tester.pumpFrame(() { + inner = new InnerComponent(); + outter = new OutterContainer(child: inner); + return outter; + }); + + expect(inner._didInitState, isFalse); + expect(inner.parent, isNull); + + outter.setState(() {}); + scheduler.beginFrame(0.0); + + expect(inner._didInitState, isFalse); + expect(inner.parent, isNull); + + }); +}