Merge pull request #2482 from HansMuller/dismiss_action
Support undo in the leave-behind demo
diff --git a/packages/flutter/lib/src/widgets/framework.dart b/packages/flutter/lib/src/widgets/framework.dart
index 35358be..53b7162 100644
--- a/packages/flutter/lib/src/widgets/framework.dart
+++ b/packages/flutter/lib/src/widgets/framework.dart
@@ -1605,8 +1605,8 @@
while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) {
Element oldChild = oldChildren[oldChildrenTop];
Widget newWidget = newWidgets[newChildrenTop];
- assert(oldChild._debugLifecycleState == _ElementLifecycle.active);
- if (!Widget.canUpdate(oldChild.widget, newWidget))
+ assert(oldChild == null || oldChild._debugLifecycleState == _ElementLifecycle.active);
+ if (oldChild == null || !Widget.canUpdate(oldChild.widget, newWidget))
break;
Element newChild = updateChild(oldChild, newWidget, previousChild);
assert(newChild._debugLifecycleState == _ElementLifecycle.active);
@@ -1620,8 +1620,8 @@
while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) {
Element oldChild = oldChildren[oldChildrenBottom];
Widget newWidget = newWidgets[newChildrenBottom];
- assert(oldChild._debugLifecycleState == _ElementLifecycle.active);
- if (!Widget.canUpdate(oldChild.widget, newWidget))
+ assert(oldChild == null || oldChild._debugLifecycleState == _ElementLifecycle.active);
+ if (oldChild == null || !Widget.canUpdate(oldChild.widget, newWidget))
break;
oldChildrenBottom -= 1;
newChildrenBottom -= 1;
@@ -1634,11 +1634,13 @@
oldKeyedChildren = new Map<Key, Element>();
while (oldChildrenTop <= oldChildrenBottom) {
Element oldChild = oldChildren[oldChildrenTop];
- assert(oldChild._debugLifecycleState == _ElementLifecycle.active);
- if (oldChild.widget.key != null)
- oldKeyedChildren[oldChild.widget.key] = oldChild;
- else
- _deactivateChild(oldChild);
+ assert(oldChild == null || oldChild._debugLifecycleState == _ElementLifecycle.active);
+ if (oldChild != null) {
+ if (oldChild.widget.key != null)
+ oldKeyedChildren[oldChild.widget.key] = oldChild;
+ else
+ _deactivateChild(oldChild);
+ }
oldChildrenTop += 1;
}
}
@@ -1682,6 +1684,7 @@
// Update the bottom of the list.
while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) {
Element oldChild = oldChildren[oldChildrenTop];
+ assert(oldChild != null);
assert(oldChild._debugLifecycleState == _ElementLifecycle.active);
Widget newWidget = newWidgets[newChildrenTop];
assert(Widget.canUpdate(oldChild.widget, newWidget));
@@ -1826,6 +1829,19 @@
}
List<Element> _children;
+ // We null out detached children lazily to avoid O(n^2) work walking _children
+ // repeatedly to remove children.
+ Set<Element> _detachedChildren;
+
+ void _replaceDetachedChildrenWithNull() {
+ if (_detachedChildren != null && _detachedChildren.isNotEmpty) {
+ for (int i = 0; i < _children.length; ++i) {
+ if (_detachedChildren.contains(_children[i]))
+ _children[i] = null;
+ }
+ _detachedChildren.clear();
+ }
+ }
void insertChildRenderObject(RenderObject child, Element slot) {
final ContainerRenderObjectMixin renderObject = this.renderObject;
@@ -1865,8 +1881,18 @@
}
void visitChildren(ElementVisitor visitor) {
- for (Element child in _children)
- visitor(child);
+ _replaceDetachedChildrenWithNull();
+ for (Element child in _children) {
+ if (child != null)
+ visitor(child);
+ }
+ }
+
+ bool detachChild(Element child) {
+ _detachedChildren ??= new Set<Element>();
+ _detachedChildren.add(child);
+ _deactivateChild(child);
+ return true;
}
void mount(Element parent, dynamic newSlot) {
@@ -1883,6 +1909,7 @@
void update(T newWidget) {
super.update(newWidget);
assert(widget == newWidget);
+ _replaceDetachedChildrenWithNull();
_children = updateChildren(_children, widget.children);
}
}
diff --git a/packages/flutter/test/widget/reparent_state_test.dart b/packages/flutter/test/widget/reparent_state_test.dart
index 024e34c..4577d9a 100644
--- a/packages/flutter/test/widget/reparent_state_test.dart
+++ b/packages/flutter/test/widget/reparent_state_test.dart
@@ -95,6 +95,68 @@
});
});
+ test('can reparent state with multichild widgets', () {
+ testWidgets((WidgetTester tester) {
+ GlobalKey left = new GlobalKey();
+ GlobalKey right = new GlobalKey();
+
+ StateMarker grandchild = new StateMarker();
+ tester.pumpWidget(
+ new Stack(
+ children: <Widget>[
+ new StateMarker(key: left),
+ new StateMarker(
+ key: right,
+ child: grandchild
+ )
+ ]
+ )
+ );
+
+ (left.currentState as StateMarkerState).marker = "left";
+ (right.currentState as StateMarkerState).marker = "right";
+
+ StateMarkerState grandchildState = tester.findStateByConfig(grandchild);
+ expect(grandchildState, isNotNull);
+ grandchildState.marker = "grandchild";
+
+ StateMarker newGrandchild = new StateMarker();
+ tester.pumpWidget(
+ new Stack(
+ children: <Widget>[
+ new StateMarker(
+ key: right,
+ child: newGrandchild
+ ),
+ new StateMarker(key: left)
+ ]
+ )
+ );
+
+ expect((left.currentState as StateMarkerState).marker, equals("left"));
+ expect((right.currentState as StateMarkerState).marker, equals("right"));
+
+ StateMarkerState newGrandchildState = tester.findStateByConfig(newGrandchild);
+ expect(newGrandchildState, isNotNull);
+ expect(newGrandchildState, equals(grandchildState));
+ expect(newGrandchildState.marker, equals("grandchild"));
+
+ tester.pumpWidget(
+ new Center(
+ child: new Container(
+ child: new StateMarker(
+ key: left,
+ child: new Container()
+ )
+ )
+ )
+ );
+
+ expect((left.currentState as StateMarkerState).marker, equals("left"));
+ expect(right.currentState, isNull);
+ });
+ });
+
test('can with scrollable list', () {
testWidgets((WidgetTester tester) {
GlobalKey key = new GlobalKey();
diff --git a/packages/flutter_tools/lib/src/service_protocol.dart b/packages/flutter_tools/lib/src/service_protocol.dart
new file mode 100644
index 0000000..ffbbdfa
--- /dev/null
+++ b/packages/flutter_tools/lib/src/service_protocol.dart
@@ -0,0 +1,52 @@
+// 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 'dart:async';
+
+import 'device.dart';
+
+/// Discover service protocol ports on devices.
+class ServiceProtocolDiscovery {
+ /// [logReader] A [DeviceLogReader] to look for Observatory messages in.
+ ServiceProtocolDiscovery(DeviceLogReader logReader)
+ : _logReader = logReader {
+ assert(_logReader != null);
+ if (!_logReader.isReading)
+ _logReader.start();
+
+ _logReader.lines.listen(_onLine);
+ }
+
+ final DeviceLogReader _logReader;
+ Completer _completer = new Completer();
+
+ /// The [Future] returned by this function will complete when the next
+ /// service protocol port is found.
+ Future<int> nextPort() {
+ return _completer.future;
+ }
+
+ void _onLine(String line) {
+ int portNumber = 0;
+ if (line.startsWith('Observatory listening on http://')) {
+ try {
+ RegExp portExp = new RegExp(r"\d+.\d+.\d+.\d+:(\d+)");
+ var port = portExp.firstMatch(line).group(1);
+ portNumber = int.parse(port);
+ } catch (_) {
+ // Ignore errors.
+ }
+ }
+ if (portNumber != 0) {
+ _located(portNumber);
+ }
+ }
+
+ void _located(int port) {
+ assert(_completer != null);
+ assert(!_completer.isCompleted);
+ _completer.complete(port);
+ _completer = new Completer();
+ }
+}
diff --git a/packages/flutter_tools/test/service_protocol_test.dart b/packages/flutter_tools/test/service_protocol_test.dart
new file mode 100644
index 0000000..7c62e57
--- /dev/null
+++ b/packages/flutter_tools/test/service_protocol_test.dart
@@ -0,0 +1,47 @@
+// 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 'dart:async';
+import 'package:test/test.dart';
+
+import 'package:flutter_tools/src/service_protocol.dart';
+
+import 'src/mocks.dart';
+
+main() => defineTests();
+
+defineTests() {
+ group('service_protocol', () {
+ test('Discovery Heartbeat', () async {
+ MockDeviceLogReader logReader = new MockDeviceLogReader();
+ ServiceProtocolDiscovery discoverer =
+ new ServiceProtocolDiscovery(logReader);
+ // Get next port future.
+ Future nextPort = discoverer.nextPort();
+ expect(nextPort, isNotNull);
+ // Inject some lines.
+ logReader.addLine('HELLO WORLD');
+ logReader.addLine(
+ 'Observatory listening on http://127.0.0.1:9999');
+ // Await the port.
+ expect(await nextPort, 9999);
+ // Get next port future.
+ nextPort = discoverer.nextPort();
+ logReader.addLine(
+ 'Observatory listening on http://127.0.0.1:3333');
+ expect(await nextPort, 3333);
+ // Get next port future.
+ nextPort = discoverer.nextPort();
+ // Inject some bad lines.
+ logReader.addLine('Observatory listening on http://127.0.0.1');
+ logReader.addLine('Observatory listening on http://127.0.0.1:');
+ logReader.addLine(
+ 'Observatory listening on http://127.0.0.1:apple');
+ int port = await nextPort.timeout(
+ const Duration(milliseconds: 100), onTimeout: () => 77);
+ // Expect the timeout port.
+ expect(port, 77);
+ });
+ });
+}
diff --git a/packages/flutter_tools/test/src/mocks.dart b/packages/flutter_tools/test/src/mocks.dart
index 38a4610..1d7b819 100644
--- a/packages/flutter_tools/test/src/mocks.dart
+++ b/packages/flutter_tools/test/src/mocks.dart
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+import 'dart:async';
import 'package:flutter_tools/src/android/android_device.dart';
import 'package:flutter_tools/src/application_package.dart';
import 'package:flutter_tools/src/build_configuration.dart';
@@ -51,6 +52,39 @@
iOSSimulator: new MockIOSSimulator());
}
+class MockDeviceLogReader extends DeviceLogReader {
+ String get name => 'MockLogReader';
+
+ final StreamController<String> _linesStreamController =
+ new StreamController<String>.broadcast();
+
+ final Completer _finishedCompleter = new Completer();
+
+ Stream<String> get lines => _linesStreamController.stream;
+
+ void addLine(String line) {
+ _linesStreamController.add(line);
+ }
+
+ bool _started = false;
+
+ Future start() {
+ assert(!_started);
+ _started = true;
+ return new Future.value(this);
+ }
+
+ bool get isReading => _started;
+
+ Future stop() {
+ assert(_started);
+ _started = false;
+ return new Future.value(this);
+ }
+
+ Future get finished => _finishedCompleter.future;
+}
+
void applyMocksToCommand(FlutterCommand command) {
command
..applicationPackages = new MockApplicationPackageStore()