Commit d7ed623e authored by Adam Barth's avatar Adam Barth

Merge pull request #774 from abarth/add_listener

Add GlobalKey.registerAddListener
parents a61f0f50 16328cc8
...@@ -104,9 +104,9 @@ class Focus extends StatefulComponent { ...@@ -104,9 +104,9 @@ class Focus extends StatefulComponent {
} }
} }
void _widgetRemoved(GlobalKey key) { void _handleWidgetRemoved(GlobalKey key) {
assert(_focusedWidget == key); assert(_focusedWidget == key);
_currentlyRegisteredWidgetRemovalListenerKey = null; _updateWidgetRemovalListener(null);
setState(() { setState(() {
_focusedWidget = null; _focusedWidget = null;
}); });
...@@ -115,9 +115,9 @@ class Focus extends StatefulComponent { ...@@ -115,9 +115,9 @@ class Focus extends StatefulComponent {
void _updateWidgetRemovalListener(GlobalKey key) { void _updateWidgetRemovalListener(GlobalKey key) {
if (_currentlyRegisteredWidgetRemovalListenerKey != key) { if (_currentlyRegisteredWidgetRemovalListenerKey != key) {
if (_currentlyRegisteredWidgetRemovalListenerKey != null) if (_currentlyRegisteredWidgetRemovalListenerKey != null)
GlobalKey.unregisterRemovalListener(_currentlyRegisteredWidgetRemovalListenerKey, _widgetRemoved); GlobalKey.unregisterRemoveListener(_currentlyRegisteredWidgetRemovalListenerKey, _handleWidgetRemoved);
if (key != null) if (key != null)
GlobalKey.registerRemovalListener(key, _widgetRemoved); GlobalKey.registerRemoveListener(key, _handleWidgetRemoved);
_currentlyRegisteredWidgetRemovalListenerKey = key; _currentlyRegisteredWidgetRemovalListenerKey = key;
} }
} }
...@@ -151,9 +151,9 @@ class Focus extends StatefulComponent { ...@@ -151,9 +151,9 @@ class Focus extends StatefulComponent {
void _updateScopeRemovalListener(GlobalKey key) { void _updateScopeRemovalListener(GlobalKey key) {
if (_currentlyRegisteredScopeRemovalListenerKey != key) { if (_currentlyRegisteredScopeRemovalListenerKey != key) {
if (_currentlyRegisteredScopeRemovalListenerKey != null) if (_currentlyRegisteredScopeRemovalListenerKey != null)
GlobalKey.unregisterRemovalListener(_currentlyRegisteredScopeRemovalListenerKey, _scopeRemoved); GlobalKey.unregisterRemoveListener(_currentlyRegisteredScopeRemovalListenerKey, _scopeRemoved);
if (key != null) if (key != null)
GlobalKey.registerRemovalListener(key, _scopeRemoved); GlobalKey.registerRemoveListener(key, _scopeRemoved);
_currentlyRegisteredScopeRemovalListenerKey = key; _currentlyRegisteredScopeRemovalListenerKey = key;
} }
} }
......
...@@ -47,7 +47,8 @@ class ObjectKey extends Key { ...@@ -47,7 +47,8 @@ class ObjectKey extends Key {
int get hashCode => identityHashCode(value); int get hashCode => identityHashCode(value);
} }
typedef void GlobalKeyRemovalListener(GlobalKey key); typedef void GlobalKeySyncListener(GlobalKey key, Widget widget);
typedef void GlobalKeyRemoveListener(GlobalKey key);
abstract class GlobalKey extends Key { abstract class GlobalKey extends Key {
const GlobalKey.constructor() : super.constructor(); // so that subclasses can call us, since the Key() factory constructor shadows the implicit constructor const GlobalKey.constructor() : super.constructor(); // so that subclasses can call us, since the Key() factory constructor shadows the implicit constructor
...@@ -56,7 +57,9 @@ abstract class GlobalKey extends Key { ...@@ -56,7 +57,9 @@ abstract class GlobalKey extends Key {
static final Map<GlobalKey, Widget> _registry = new Map<GlobalKey, Widget>(); static final Map<GlobalKey, Widget> _registry = new Map<GlobalKey, Widget>();
static final Map<GlobalKey, int> _debugDuplicates = new Map<GlobalKey, int>(); static final Map<GlobalKey, int> _debugDuplicates = new Map<GlobalKey, int>();
static final Map<GlobalKey, Set<GlobalKeyRemovalListener>> _removalListeners = new Map<GlobalKey, Set<GlobalKeyRemovalListener>>(); static final Map<GlobalKey, Set<GlobalKeySyncListener>> _syncListeners = new Map<GlobalKey, Set<GlobalKeySyncListener>>();
static final Map<GlobalKey, Set<GlobalKeyRemoveListener>> _removeListeners = new Map<GlobalKey, Set<GlobalKeyRemoveListener>>();
static final Set<GlobalKey> _syncedKeys = new Set<GlobalKey>();
static final Set<GlobalKey> _removedKeys = new Set<GlobalKey>(); static final Set<GlobalKey> _removedKeys = new Set<GlobalKey>();
void _register(Widget widget) { void _register(Widget widget) {
...@@ -90,24 +93,47 @@ abstract class GlobalKey extends Key { ...@@ -90,24 +93,47 @@ abstract class GlobalKey extends Key {
} }
} }
void _didSync() {
_syncedKeys.add(this);
}
static bool _notifyingListeners = false; static bool _notifyingListeners = false;
static void registerRemovalListener(GlobalKey key, GlobalKeyRemovalListener listener) { static void registerSyncListener(GlobalKey key, GlobalKeySyncListener listener) {
assert(!_notifyingListeners); assert(!_notifyingListeners);
assert(key != null); assert(key != null);
if (!_removalListeners.containsKey(key)) Set<GlobalKeySyncListener> listeners =
_removalListeners[key] = new Set<GlobalKeyRemovalListener>(); _syncListeners.putIfAbsent(key, () => new Set<GlobalKeySyncListener>());
bool added = _removalListeners[key].add(listener); bool added = listeners.add(listener);
assert(added); assert(added);
} }
static void unregisterRemovalListener(GlobalKey key, GlobalKeyRemovalListener listener) { static void unregisterSyncListener(GlobalKey key, GlobalKeySyncListener listener) {
assert(!_notifyingListeners); assert(!_notifyingListeners);
assert(key != null); assert(key != null);
assert(_removalListeners.containsKey(key)); assert(_syncListeners.containsKey(key));
bool removed = _removalListeners[key].remove(listener); bool removed = _syncListeners[key].remove(listener);
if (_removalListeners[key].isEmpty) if (_syncListeners[key].isEmpty)
_removalListeners.remove(key); _syncListeners.remove(key);
assert(removed);
}
static void registerRemoveListener(GlobalKey key, GlobalKeyRemoveListener listener) {
assert(!_notifyingListeners);
assert(key != null);
Set<GlobalKeyRemoveListener> listeners =
_removeListeners.putIfAbsent(key, () => new Set<GlobalKeyRemoveListener>());
bool added = listeners.add(listener);
assert(added);
}
static void unregisterRemoveListener(GlobalKey key, GlobalKeyRemoveListener listener) {
assert(!_notifyingListeners);
assert(key != null);
assert(_removeListeners.containsKey(key));
bool removed = _removeListeners[key].remove(listener);
if (_removeListeners[key].isEmpty)
_removeListeners.remove(key);
assert(removed); assert(removed);
} }
...@@ -120,16 +146,34 @@ abstract class GlobalKey extends Key { ...@@ -120,16 +146,34 @@ abstract class GlobalKey extends Key {
assert(!_inBuildDirtyComponents); assert(!_inBuildDirtyComponents);
assert(!Widget._notifyingMountStatus); assert(!Widget._notifyingMountStatus);
assert(_debugDuplicates.isEmpty); assert(_debugDuplicates.isEmpty);
if (_syncedKeys.isEmpty && _removedKeys.isEmpty)
return;
_notifyingListeners = true; _notifyingListeners = true;
for (GlobalKey key in _removedKeys) { try {
if (!_registry.containsKey(key) && _removalListeners.containsKey(key)) { Map<GlobalKey, Set<GlobalKeyRemoveListener>> localRemoveListeners =
for (GlobalKeyRemovalListener listener in _removalListeners[key]) new Map<GlobalKey, Set<GlobalKeyRemoveListener>>.from(_removeListeners);
listener(key); Map<GlobalKey, Set<GlobalKeySyncListener>> localSyncListeners =
_removalListeners.remove(key); new Map<GlobalKey, Set<GlobalKeySyncListener>>.from(_syncListeners);
for (GlobalKey key in _syncedKeys) {
Widget widget = _registry[key];
if (widget != null && localSyncListeners.containsKey(key)) {
for (GlobalKeySyncListener listener in localSyncListeners[key])
listener(key, widget);
}
}
for (GlobalKey key in _removedKeys) {
if (!_registry.containsKey(key) && localRemoveListeners.containsKey(key)) {
for (GlobalKeyRemoveListener listener in localRemoveListeners[key])
listener(key);
}
} }
} finally {
_removedKeys.clear();
_syncedKeys.clear();
_notifyingListeners = false;
} }
_removedKeys.clear();
_notifyingListeners = false;
} }
} }
...@@ -260,7 +304,10 @@ abstract class Widget { ...@@ -260,7 +304,10 @@ abstract class Widget {
// Component.retainStatefulNodeIfPossible() calls syncConstructorArguments(). // Component.retainStatefulNodeIfPossible() calls syncConstructorArguments().
bool retainStatefulNodeIfPossible(Widget newNode) => false; bool retainStatefulNodeIfPossible(Widget newNode) => false;
void _sync(Widget old, dynamic slot); void _sync(Widget old, dynamic slot) {
if (key is GlobalKey)
(key as GlobalKey)._didSync(); // TODO(ianh): Remove the cast once the analyzer is cleverer.
}
void updateSlot(dynamic newSlot); void updateSlot(dynamic newSlot);
// 'slot' is the identifier that the ancestor RenderObjectWrapper uses to know // 'slot' is the identifier that the ancestor RenderObjectWrapper uses to know
// where to put this descendant. If you just defer to a child, then make sure // where to put this descendant. If you just defer to a child, then make sure
...@@ -433,6 +480,7 @@ abstract class TagNode extends Widget { ...@@ -433,6 +480,7 @@ abstract class TagNode extends Widget {
} else { } else {
_renderObject = null; _renderObject = null;
} }
super._sync(old, slot);
} }
void updateSlot(dynamic newSlot) { void updateSlot(dynamic newSlot) {
...@@ -669,6 +717,7 @@ abstract class Component extends Widget { ...@@ -669,6 +717,7 @@ abstract class Component extends Widget {
_renderObject = _child.renderObject; _renderObject = _child.renderObject;
assert(_renderObject == renderObject); // in case a subclass reintroduces it assert(_renderObject == renderObject); // in case a subclass reintroduces it
assert(renderObject != null); assert(renderObject != null);
super._sync(old, slot);
} }
void _buildIfDirty() { void _buildIfDirty() {
...@@ -911,6 +960,7 @@ abstract class RenderObjectWrapper extends Widget { ...@@ -911,6 +960,7 @@ abstract class RenderObjectWrapper extends Widget {
assert(mounted); assert(mounted);
_nodeMap[renderObject] = this; _nodeMap[renderObject] = this;
syncRenderObject(old); syncRenderObject(old);
super._sync(old, slot);
} }
void updateSlot(dynamic newSlot) { void updateSlot(dynamic newSlot) {
......
import 'package:sky/rendering.dart';
import 'package:sky/widgets.dart';
const Size _kTestViewSize = const Size(800.0, 600.0);
class TestRenderView extends RenderView {
TestRenderView({ RenderBox child }) : super(child: child) {
attach();
rootConstraints = new ViewConstraints(size: _kTestViewSize);
scheduleInitialLayout();
}
}
typedef Widget WidgetBuilder();
class TestApp extends App {
TestApp();
WidgetBuilder _builder;
void set builder (WidgetBuilder value) {
setState(() {
_builder = value;
});
}
Widget build() {
if (_builder != null)
return _builder();
return new Container();
}
}
class WidgetTester {
WidgetTester() {
_app = new TestApp();
_renderView = new TestRenderView();
runApp(_app, renderViewOverride: _renderView);
}
TestApp _app;
RenderView _renderView;
void pumpFrame(WidgetBuilder builder) {
_app.builder = builder;
Component.flushBuild();
RenderObject.flushLayout();
}
}
import 'package:sky/widgets.dart';
import 'package:test/test.dart';
import 'build_utils.dart';
void main() {
test('Global keys notify add and remove', () {
GlobalKey globalKey = new GlobalKey();
Container container;
bool syncListenerCalled = false;
bool removeListenerCalled = false;
void syncListener(GlobalKey key, Widget widget) {
syncListenerCalled = true;
expect(key, equals(globalKey));
expect(container, isNotNull);
expect(widget, equals(container));
}
void removeListener(GlobalKey key) {
removeListenerCalled = true;
expect(key, equals(globalKey));
}
WidgetTester tester = new WidgetTester();
GlobalKey.registerSyncListener(globalKey, syncListener);
GlobalKey.registerRemoveListener(globalKey, removeListener);
tester.pumpFrame(() {
container = new Container(key: globalKey);
return container;
});
expect(syncListenerCalled, isTrue);
expect(removeListenerCalled, isFalse);
syncListenerCalled = false;
removeListenerCalled = false;
tester.pumpFrame(() => new Container());
expect(syncListenerCalled, isFalse);
expect(removeListenerCalled, isTrue);
syncListenerCalled = false;
removeListenerCalled = false;
GlobalKey.unregisterSyncListener(globalKey, syncListener);
GlobalKey.unregisterRemoveListener(globalKey, removeListener);
tester.pumpFrame(() {
container = new Container(key: globalKey);
return container;
});
expect(syncListenerCalled, isFalse);
expect(removeListenerCalled, isFalse);
tester.pumpFrame(() => new Container());
expect(syncListenerCalled, isFalse);
expect(removeListenerCalled, isFalse);
});
test('Global key reparenting', () {
GlobalKey globalKey = new GlobalKey();
bool syncListenerCalled = false;
bool removeListenerCalled = false;
void syncListener(GlobalKey key, Widget widget) {
syncListenerCalled = true;
}
void removeListener(GlobalKey key) {
removeListenerCalled = true;
}
GlobalKey.registerSyncListener(globalKey, syncListener);
GlobalKey.registerRemoveListener(globalKey, removeListener);
WidgetTester tester = new WidgetTester();
tester.pumpFrame(() {
return new Container(
child: new Container(
key: globalKey
)
);
});
expect(syncListenerCalled, isTrue);
expect(removeListenerCalled, isFalse);
tester.pumpFrame(() {
return new Container(
key: globalKey,
child: new Container()
);
});
expect(syncListenerCalled, isTrue);
expect(removeListenerCalled, isFalse);
tester.pumpFrame(() {
return new Container(
child: new Container(
key: globalKey
)
);
});
expect(syncListenerCalled, isTrue);
expect(removeListenerCalled, isFalse);
GlobalKey.unregisterSyncListener(globalKey, syncListener);
GlobalKey.unregisterRemoveListener(globalKey, removeListener);
});
}
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