diff --git a/packages/flutter/lib/src/widgets/framework.dart b/packages/flutter/lib/src/widgets/framework.dart index e632431bfea..925e7facd7f 100644 --- a/packages/flutter/lib/src/widgets/framework.dart +++ b/packages/flutter/lib/src/widgets/framework.dart @@ -52,6 +52,12 @@ abstract class LocalKey extends Key { /// /// A [ValueKey] is equal to another [ValueKey] if, and only if, their /// values are [operator==]. +/// +/// This class can be subclassed to create value keys that will not be equal to +/// other value keys that happen to use the same value. If the subclass is +/// private, this results in a value key type that cannot collide with keys from +/// other sources, which could be useful, for example, if the keys are being +/// used as fallbacks in the same scope as keys supplied from another widget. class ValueKey extends LocalKey { /// Creates a key that delgates its [operator==] to the given value. const ValueKey(this.value); @@ -61,19 +67,28 @@ class ValueKey extends LocalKey { @override bool operator ==(dynamic other) { - if (other is! ValueKey) + if (other.runtimeType != runtimeType) return false; final ValueKey typedOther = other; return value == typedOther.value; } @override - int get hashCode => value.hashCode; + int get hashCode => hashValues(runtimeType, value); @override - String toString() => '[\'$value\']'; + String toString() { + final String valueString = T == String ? '<\'$value\'>' : '<$value>'; + // The crazy on the next line is a workaround for + // https://github.com/dart-lang/sdk/issues/28548 + if (runtimeType == new _TypeLiteral>().type) + return '[$valueString]'; + return '[$T $valueString]'; + } } +class _TypeLiteral { Type get type => T; } + /// A key that is only equal to itself. class UniqueKey extends LocalKey { /// Creates a key that is equal only to itself. @@ -96,17 +111,21 @@ class ObjectKey extends LocalKey { @override bool operator ==(dynamic other) { - if (other is! ObjectKey) + if (other.runtimeType != runtimeType) return false; final ObjectKey typedOther = other; return identical(value, typedOther.value); } @override - int get hashCode => identityHashCode(value); + int get hashCode => hashValues(runtimeType, identityHashCode(value)); @override - String toString() => '[${value.runtimeType}(${value.hashCode})]'; + String toString() { + if (runtimeType == ObjectKey) + return '[${value.runtimeType}@${value.hashCode}]'; + return '[$runtimeType ${value.runtimeType}@${value.hashCode}]'; + } } /// Signature for a callback when a global key is removed from the tree. @@ -286,13 +305,19 @@ class LabeledGlobalKey> extends GlobalKey { final String _debugLabel; @override - String toString() => '[GlobalKey ${_debugLabel != null ? _debugLabel : hashCode}]'; + String toString() { + if (this.runtimeType == LabeledGlobalKey) + return '[GlobalKey ${_debugLabel ?? hashCode}]'; + return '[$runtimeType ${_debugLabel ?? hashCode}]'; + } } /// A global key that takes its identity from the object used as its value. /// /// Used to tie the identity of a widget to the identity of an object used to /// generate that widget. +/// +/// Any [GlobalObjectKey] created for the same value will match. @optionalTypeArgs class GlobalObjectKey> extends GlobalKey { /// Creates a global key that uses [identical] on [value] for its [operator==]. @@ -303,7 +328,7 @@ class GlobalObjectKey> extends GlobalKey { @override bool operator ==(dynamic other) { - if (other is! GlobalObjectKey) + if (other.runtimeType != runtimeType) return false; final GlobalObjectKey typedOther = other; return identical(value, typedOther.value); @@ -313,7 +338,7 @@ class GlobalObjectKey> extends GlobalKey { int get hashCode => identityHashCode(value); @override - String toString() => '[$runtimeType ${value.runtimeType}(${value.hashCode})]'; + String toString() => '[$runtimeType ${value.runtimeType}@${value.hashCode}]'; } /// This class is a work-around for the "is" operator not accepting a variable value as its right operand diff --git a/packages/flutter/test/widgets/key_test.dart b/packages/flutter/test/widgets/key_test.dart new file mode 100644 index 00000000000..538277a6c2c --- /dev/null +++ b/packages/flutter/test/widgets/key_test.dart @@ -0,0 +1,57 @@ +// 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'; +import 'package:flutter/widgets.dart'; + +class TestValueKey extends ValueKey { + const TestValueKey(T value) : super(value); +} + +class NotEquals { + const NotEquals(); + @override + bool operator ==(dynamic other) => false; + @override + int get hashCode => 0; +} + +void main() { + testWidgets('Keys', (WidgetTester tester) async { + expect(new ValueKey(3) == new ValueKey(3), isTrue); + expect(new ValueKey(3) == new ValueKey(3), isFalse); + expect(new ValueKey(3) == new ValueKey(2), isFalse); + expect(const ValueKey(double.NAN) == const ValueKey(double.NAN), isFalse); + + expect(new Key('') == new ValueKey(''), isTrue); + expect(new ValueKey('') == new ValueKey(''), isTrue); + expect(new TestValueKey('') == new ValueKey(''), isFalse); + expect(new TestValueKey('') == new TestValueKey(''), isTrue); + + expect(new ValueKey('') == new ValueKey(''), isFalse); + expect(new TestValueKey('') == new TestValueKey(''), isFalse); + + expect(new UniqueKey() == new UniqueKey(), isFalse); + LocalKey k = new UniqueKey(); + expect(new UniqueKey() == new UniqueKey(), isFalse); + expect(k == k, isTrue); + + expect(new ValueKey(k) == new ValueKey(k), isTrue); + expect(new ValueKey(k) == new ValueKey(k), isFalse); + expect(new ObjectKey(k) == new ObjectKey(k), isTrue); + + expect(new ValueKey(const NotEquals()) == new ValueKey(const NotEquals()), isFalse); + expect(new ObjectKey(const NotEquals()) == new ObjectKey(const NotEquals()), isTrue); + + expect(new ObjectKey(const Object()) == new ObjectKey(const Object()), isTrue); + expect(new ObjectKey(new Object()) == new ObjectKey(new Object()), isFalse); + + expect(new ValueKey(true), hasOneLineDescription); + expect(new UniqueKey(), hasOneLineDescription); + expect(new ObjectKey(true), hasOneLineDescription); + expect(new GlobalKey(), hasOneLineDescription); + expect(new GlobalKey(debugLabel: 'hello'), hasOneLineDescription); + expect(new GlobalObjectKey(true), hasOneLineDescription); + }); +}