Commit 547c324d authored by Hixie's avatar Hixie

fn3 review

- adds dartdocs
- replaces config setter with didUpdateConfig() so that you can't replace
the config outside of the system
- renames didUnmount() with destroy().
- rename Component to StatelessComponent, ComponentConfiguration to
StatefulComponent
- move debug dump to end of file
- renamed _holder to _element
parent d27f3d52
...@@ -5,93 +5,149 @@ ...@@ -5,93 +5,149 @@
import 'package:sky/animation.dart'; import 'package:sky/animation.dart';
import 'package:sky/rendering.dart'; import 'package:sky/rendering.dart';
abstract class Key { abstract class Key { }
}
/// A Widget object describes the configuration for an [Element].
/// Widget subclasses should be immutable with const constructors.
/// Widgets form a tree that is then inflated into an Element tree.
abstract class Widget { abstract class Widget {
Widget(this.key); const Widget({ this.key });
final Key key; final Key key;
/// Inflates this configuration to a concrete instance.
Element createElement(); Element createElement();
} }
typedef Widget WidgetBuilder(); /// RenderObjectWidgets provide the configuration for [RenderObjectElement]s,
/// which wrap [RenderObject]s, which provide the actual rendering of the
/// application.
abstract class RenderObjectWidget extends Widget { abstract class RenderObjectWidget extends Widget {
RenderObjectWidget({ Key key }) : super(key); const RenderObjectWidget({ Key key }) : super(key: key);
Element createElement() => new RenderObjectElement(this); /// RenderObjectWidgets always inflate to a RenderObjectElement subclass.
RenderObjectElement createElement();
/// Constructs an instance of the RenderObject class that this
/// RenderObjectWidget represents, using the configuration described by this
/// RenderObjectWidget.
RenderObject createRenderObject(); RenderObject createRenderObject();
/// Copies the configuration described by this RenderObjectWidget to the given
/// RenderObject, which must be of the same type as returned by this class'
/// createRenderObject().
void updateRenderObject(RenderObject renderObject); void updateRenderObject(RenderObject renderObject);
} }
/// A superclass for RenderObjectWidgets that configure RenderObject subclasses
/// that have no children.
abstract class LeafRenderObjectWidget extends RenderObjectWidget {
const LeafRenderObjectWidget({ Key key }) : super(key: key);
RenderObjectElement createElement() => new LeafRenderObjectElement(this);
}
/// A superclass for RenderObjectWidgets that configure RenderObject subclasses
/// that have a single child slot. (This superclass only provides the storage
/// for that child, it doesn't actually provide the updating logic.)
abstract class OneChildRenderObjectWidget extends RenderObjectWidget { abstract class OneChildRenderObjectWidget extends RenderObjectWidget {
OneChildRenderObjectWidget({ Key key, Widget this.child }) : super(key: key); const OneChildRenderObjectWidget({ Key key, Widget this.child }) : super(key: key);
final Widget child; final Widget child;
Element createElement() => new OneChildRenderObjectElement(this); RenderObjectElement createElement() => new OneChildRenderObjectElement(this);
} }
/// A superclass for RenderObjectWidgets that configure RenderObject subclasses
/// that have a single list of children. (This superclass only provides the
/// storage for that child list, it doesn't actually provide the updating
/// logic.)
abstract class MultiChildRenderObjectWidget extends RenderObjectWidget { abstract class MultiChildRenderObjectWidget extends RenderObjectWidget {
MultiChildRenderObjectWidget({ Key key, List<Widget> this.children }) const MultiChildRenderObjectWidget({ Key key, List<Widget> this.children })
: super(key: key); : super(key: key);
final List<Widget> children; final List<Widget> children;
Element createElement() => new MultiChildRenderObjectElement(this); RenderObjectElement createElement() => new MultiChildRenderObjectElement(this);
} }
abstract class Component extends Widget { /// StatelessComponents describe a way to compose other Widgets to form reusable
Component({ Key key }) : super(key); /// parts, which doesn't depend on anything other than the configuration
Element createElement() => new ComponentElement(this); /// information in the object itself. (For compositions that can change
/// dynamically, e.g. due to having an internal clock-driven state, or depending
/// on some system state, use [StatefulComponent].)
abstract class StatelessComponent extends Widget {
const StatelessComponent({ Key key }) : super(key: key);
/// StatelessComponents always use StatelessComponentElements to represent
/// themselves in the Element tree.
StatelessComponentElement createElement() => new StatelessComponentElement(this);
/// Returns another Widget out of which this StatelessComponent is built.
/// Typically that Widget will have been configured with further children,
/// such that really this function returns a tree of configuration.
Widget build(); Widget build();
} }
abstract class ComponentState<T extends ComponentConfiguration> { /// StatefulComponents provide the configuration for
ComponentStateElement _holder; /// [StatefulComponentElement]s, which wrap [ComponentState]s, which hold
/// mutable state and can dynamically and spontaneously ask to be rebuilt.
abstract class StatefulComponent extends Widget {
const StatefulComponent({ Key key }) : super(key: key);
/// StatefulComponents always use StatefulComponentElements to represent
/// themselves in the Element tree.
StatefulComponentElement createElement() => new StatefulComponentElement(this);
/// Returns an instance of the state to which this StatefulComponent is
/// related, using this object as the configuration. Subclasses should
/// override this to return a new instance of the ComponentState class
/// associated with this StatefulComponent class, like this:
///
/// MyComponentState createState() => new MyComponentState(this);
ComponentState createState();
}
/// The logic and internal state for a StatefulComponent.
abstract class ComponentState<T extends StatefulComponent> {
ComponentState(this._config);
StatefulComponentElement _element;
/// Whenever you need to change internal state for a ComponentState object,
/// make the change in a function that you pass to setState(), as in:
///
/// setState(() { myState = newValue });
///
/// If you just change the state directly without calling setState(), then
/// the component will not be scheduled for rebuilding, meaning that its
/// rendering will not be updated.
void setState(void fn()) { void setState(void fn()) {
fn(); fn();
_holder.scheduleBuild(); _element.scheduleBuild();
} }
/// The current configuration (an instance of the corresponding
/// StatefulComponent class).
T get config => _config; T get config => _config;
T _config; T _config;
/// Override this setter to update additional state when the config changes. /// Called whenever the configuration changes. Override this method to update
void set config(T config) { /// additional state when the config field's value is changed.
_config = config; void didUpdateConfig(T oldConfig) { }
}
/// Called when this object is removed from the tree /// Called when this object is removed from the tree. Override this to clean
void didUnmount() { } /// up any resources allocated by this object.
void dispose() { }
/// Returns another Widget out of which this StatefulComponent is built.
/// Typically that Widget will have been configured with further children,
/// such that really this function returns a tree of configuration.
Widget build(); Widget build();
} }
abstract class ComponentConfiguration extends Widget {
ComponentConfiguration({ Key key }) : super(key);
ComponentStateElement createElement() => new ComponentStateElement(this);
ComponentState createState();
}
bool _canUpdate(Widget oldWidget, Widget newWidget) { bool _canUpdate(Widget oldWidget, Widget newWidget) {
return oldWidget.runtimeType == newWidget.runtimeType return oldWidget.runtimeType == newWidget.runtimeType &&
&& oldWidget.key == newWidget.key; oldWidget.key == newWidget.key;
}
void _debugReportException(String context, dynamic exception, StackTrace stack) {
print('------------------------------------------------------------------------');
'Exception caught while $context'.split('\n').forEach(print);
print('$exception');
print('Stack trace:');
'$stack'.split('\n').forEach(print);
print('------------------------------------------------------------------------');
} }
enum _ElementLifecycle { enum _ElementLifecycle {
...@@ -108,15 +164,23 @@ abstract class Element<T extends Widget> { ...@@ -108,15 +164,23 @@ abstract class Element<T extends Widget> {
} }
Element _parent; Element _parent;
/// information set by parent to define where this child fits in its parent's
/// child list
dynamic _slot; dynamic _slot;
/// an integer that is guaranteed to be greater than the parent's, if any
int _depth; int _depth;
/// the configuration for this element
T _widget; T _widget;
_ElementLifecycle _lifecycleState = _ElementLifecycle.initial; _ElementLifecycle _lifecycleState = _ElementLifecycle.initial;
/// Calls the argument for each child. Must be overridden by subclasses that support having children.
void visitChildren(ElementVisitor visitor) { } void visitChildren(ElementVisitor visitor) { }
/// Calls the argument for each descendant, depth-first pre-order.
void visitDescendants(ElementVisitor visitor) { void visitDescendants(ElementVisitor visitor) {
void walk(Element element) { void walk(Element element) {
visitor(element); visitor(element);
...@@ -146,13 +210,13 @@ abstract class Element<T extends Widget> { ...@@ -146,13 +210,13 @@ abstract class Element<T extends Widget> {
_slot = slot; _slot = slot;
} }
void update(T updated) { void update(T newWidget) {
assert(updated != null); assert(newWidget != null);
assert(_lifecycleState == _ElementLifecycle.mounted); assert(_lifecycleState == _ElementLifecycle.mounted);
assert(_widget != null); assert(_widget != null);
assert(_depth != null); assert(_depth != null);
assert(_canUpdate(_widget, updated)); assert(_canUpdate(_widget, newWidget));
_widget = updated; _widget = newWidget;
} }
void unmount() { void unmount() {
...@@ -272,6 +336,8 @@ class _BuildScheduler { ...@@ -272,6 +336,8 @@ class _BuildScheduler {
final _BuildScheduler _buildScheduler = new _BuildScheduler(); final _BuildScheduler _buildScheduler = new _BuildScheduler();
typedef Widget WidgetBuilder();
abstract class BuildableElement<T extends Widget> extends Element<T> { abstract class BuildableElement<T extends Widget> extends Element<T> {
BuildableElement(T widget) : super(widget); BuildableElement(T widget) : super(widget);
...@@ -317,40 +383,42 @@ abstract class BuildableElement<T extends Widget> extends Element<T> { ...@@ -317,40 +383,42 @@ abstract class BuildableElement<T extends Widget> extends Element<T> {
} }
} }
class ComponentElement extends BuildableElement<Component> { class StatelessComponentElement extends BuildableElement<StatelessComponent> {
ComponentElement(Component component) : super(component) { StatelessComponentElement(StatelessComponent component) : super(component) {
_builder = component.build; _builder = component.build;
} }
void update(Component updated) { void update(StatelessComponent newWidget) {
super.update(updated); super.update(newWidget);
assert(_widget == updated); assert(_widget == newWidget);
_builder = _widget.build; _builder = _widget.build;
_rebuild(); _rebuild();
} }
} }
class ComponentStateElement extends BuildableElement<ComponentConfiguration> { class StatefulComponentElement extends BuildableElement<StatefulComponent> {
ComponentStateElement(ComponentConfiguration configuration) StatefulComponentElement(StatefulComponent configuration)
: _state = configuration.createState(), super(configuration) { : _state = configuration.createState(), super(configuration) {
assert(_state._config == configuration);
_state._element = this;
_builder = _state.build; _builder = _state.build;
_state._holder = this;
_state.config = configuration;
} }
ComponentState get state => _state; ComponentState get state => _state;
ComponentState _state; ComponentState _state;
void update(ComponentConfiguration updated) { void update(StatefulComponent newWidget) {
super.update(updated); super.update(newWidget);
assert(_widget == updated); assert(_widget == newWidget);
_state.config = _widget; StatefulComponent oldConfig = _state._config;
_state._config = _widget;
_state.didUpdateConfig(oldConfig);
_rebuild(); _rebuild();
} }
void unmount() { void unmount() {
super.unmount(); super.unmount();
_state.didUnmount(); _state.dispose();
_state = null; _state = null;
} }
} }
...@@ -441,3 +509,26 @@ class OneChildRenderObjectElement<T extends OneChildRenderObjectWidget> extends ...@@ -441,3 +509,26 @@ class OneChildRenderObjectElement<T extends OneChildRenderObjectWidget> extends
class MultiChildRenderObjectElement<T extends MultiChildRenderObjectWidget> extends RenderObjectElement<T> { class MultiChildRenderObjectElement<T extends MultiChildRenderObjectWidget> extends RenderObjectElement<T> {
MultiChildRenderObjectElement(T widget) : super(widget); MultiChildRenderObjectElement(T widget) : super(widget);
} }
typedef void WidgetsExceptionHandler(String context, dynamic exception, StackTrace stack);
/// This callback is invoked whenever an exception is caught by the widget
/// system. The 'context' argument is a description of what was happening when
/// the exception occurred, and may include additional details such as
/// descriptions of the objects involved. The 'exception' argument contains the
/// object that was thrown, and the 'stack' argument contains the stack trace.
/// The callback is invoked after the information is printed to the console, and
/// could be used to print additional information, such as from
/// [debugDumpApp()].
WidgetsExceptionHandler debugWidgetsExceptionHandler;
void _debugReportException(String context, dynamic exception, StackTrace stack) {
print('------------------------------------------------------------------------');
'Exception caught while $context'.split('\n').forEach(print);
print('$exception');
print('Stack trace:');
'$stack'.split('\n').forEach(print);
if (debugWidgetsExceptionHandler != null)
debugWidgetsExceptionHandler(context, exception, stack);
print('------------------------------------------------------------------------');
}
...@@ -4,7 +4,7 @@ import 'package:test/test.dart'; ...@@ -4,7 +4,7 @@ import 'package:test/test.dart';
import 'widget_tester.dart'; import 'widget_tester.dart';
class TestComponentConfig extends ComponentConfiguration { class TestComponentConfig extends StatefulComponent {
TestComponentConfig({ this.left, this.right }); TestComponentConfig({ this.left, this.right });
final Widget left; final Widget left;
...@@ -13,9 +13,7 @@ class TestComponentConfig extends ComponentConfiguration { ...@@ -13,9 +13,7 @@ class TestComponentConfig extends ComponentConfiguration {
TestComponentState createState() => new TestComponentState(); TestComponentState createState() => new TestComponentState();
} }
class TestComponentState extends ComponentState { class TestComponentState extends ComponentState<TestComponentConfig> {
TestComponentConfig get config => super.config;
bool _showLeft = true; bool _showLeft = true;
void flip() { void flip() {
...@@ -32,7 +30,7 @@ class TestComponentState extends ComponentState { ...@@ -32,7 +30,7 @@ class TestComponentState extends ComponentState {
final BoxDecoration kBoxDecorationA = new BoxDecoration(); final BoxDecoration kBoxDecorationA = new BoxDecoration();
final BoxDecoration kBoxDecorationB = new BoxDecoration(); final BoxDecoration kBoxDecorationB = new BoxDecoration();
class TestBuildCounter extends Component { class TestBuildCounter extends StatelessComponent {
static int buildCount = 0; static int buildCount = 0;
Widget build() { Widget build() {
...@@ -42,8 +40,8 @@ class TestBuildCounter extends Component { ...@@ -42,8 +40,8 @@ class TestBuildCounter extends Component {
} }
void flipStatefulComponent(WidgetTester tester) { void flipStatefulComponent(WidgetTester tester) {
ComponentStateElement stateElement = StatefulComponentElement stateElement =
tester.findElement((element) => element is ComponentStateElement); tester.findElement((element) => element is StatefulComponentElement);
(stateElement.state as TestComponentState).flip(); (stateElement.state as TestComponentState).flip();
} }
......
import 'package:sky/src/fn3/framework.dart'; import 'package:sky/src/fn3/framework.dart';
class TestComponent extends Component { class TestComponent extends StatelessComponent {
TestComponent({ this.child }); TestComponent({ this.child });
final Widget child; final Widget child;
Widget build() => child; Widget build() => child;
...@@ -9,7 +9,7 @@ class TestComponent extends Component { ...@@ -9,7 +9,7 @@ class TestComponent extends Component {
final Object _rootSlot = new Object(); final Object _rootSlot = new Object();
class WidgetTester { class WidgetTester {
ComponentElement _rootElement; StatelessComponentElement _rootElement;
void walkElements(ElementVisitor visitor) { void walkElements(ElementVisitor visitor) {
void walk(Element element) { void walk(Element element) {
...@@ -36,7 +36,7 @@ class WidgetTester { ...@@ -36,7 +36,7 @@ class WidgetTester {
void pumpFrame(Widget widget) { void pumpFrame(Widget widget) {
if (_rootElement == null) { if (_rootElement == null) {
_rootElement = new ComponentElement(new TestComponent(child: widget)); _rootElement = new StatelessComponentElement(new TestComponent(child: widget));
_rootElement.mount(_rootSlot); _rootElement.mount(_rootSlot);
} else { } else {
_rootElement.update(new TestComponent(child: widget)); _rootElement.update(new TestComponent(child: widget));
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment