Commit 977a25f2 authored by Ian Hickson's avatar Ian Hickson Committed by GitHub

Listenable.merge (#7256)

Sometimes you have several listenables, but you want to hand them to an
API (e.g. CustomPainter) that only expects one.
parent 971ca4b8
...@@ -14,7 +14,6 @@ export 'src/foundation/basic_types.dart'; ...@@ -14,7 +14,6 @@ export 'src/foundation/basic_types.dart';
export 'src/foundation/binding.dart'; export 'src/foundation/binding.dart';
export 'src/foundation/change_notifier.dart'; export 'src/foundation/change_notifier.dart';
export 'src/foundation/licenses.dart'; export 'src/foundation/licenses.dart';
export 'src/foundation/listenable.dart';
export 'src/foundation/platform.dart'; export 'src/foundation/platform.dart';
export 'src/foundation/print.dart'; export 'src/foundation/print.dart';
export 'src/foundation/synchronous_future.dart'; export 'src/foundation/synchronous_future.dart';
......
...@@ -6,7 +6,27 @@ import 'package:meta/meta.dart'; ...@@ -6,7 +6,27 @@ import 'package:meta/meta.dart';
import 'assertions.dart'; import 'assertions.dart';
import 'basic_types.dart'; import 'basic_types.dart';
import 'listenable.dart';
/// An object that maintains a list of listeners.
abstract class Listenable {
/// Abstract const constructor. This constructor enables subclasses to provide
/// const constructors so that they can be used in const expressions.
const Listenable();
/// Return a [Listenable] that triggers when any of the given [Listenable]s
/// themselves trigger.
///
/// The list must not be changed after this method has been called. Doing so
/// will lead to memory leaks or exceptions.
factory Listenable.merge(List<Listenable> listenables) = _MergingListenable;
/// Register a closure to be called when the object notifies its listeners.
void addListener(VoidCallback listener);
/// Remove a previously registered closure from the list of closures that the
/// object notifies.
void removeListener(VoidCallback listener);
}
/// A class that can be extended or mixed in that provides a change notification /// A class that can be extended or mixed in that provides a change notification
/// API using [VoidCallback] for notifications. /// API using [VoidCallback] for notifications.
...@@ -69,3 +89,19 @@ class ChangeNotifier extends Listenable { ...@@ -69,3 +89,19 @@ class ChangeNotifier extends Listenable {
} }
} }
} }
class _MergingListenable extends ChangeNotifier {
_MergingListenable(this._children) {
for (Listenable child in _children)
child.addListener(notifyListeners);
}
final List<Listenable> _children;
@override
void dispose() {
for (Listenable child in _children)
child.removeListener(notifyListeners);
super.dispose();
}
}
// 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 'basic_types.dart';
/// An object that maintains a list of listeners.
abstract class Listenable {
/// Abstract const constructor. This constructor enables subclasses to provide
/// const constructors so that they can be used in const expressions.
const Listenable();
/// Register a closure to be called when the object notifies its listeners.
void addListener(VoidCallback listener);
/// Remove a previously registered closure from the list of closures that the
/// object notifies.
void removeListener(VoidCallback listener);
}
...@@ -24,52 +24,52 @@ void main() { ...@@ -24,52 +24,52 @@ void main() {
test.addListener(listener); test.addListener(listener);
test.addListener(listener); test.addListener(listener);
test.notify(); test.notify();
expect(log, equals(<String>['listener', 'listener'])); expect(log, <String>['listener', 'listener']);
log.clear(); log.clear();
test.removeListener(listener); test.removeListener(listener);
test.notify(); test.notify();
expect(log, equals(<String>['listener'])); expect(log, <String>['listener']);
log.clear(); log.clear();
test.removeListener(listener); test.removeListener(listener);
test.notify(); test.notify();
expect(log, equals(<String>[])); expect(log, <String>[]);
log.clear(); log.clear();
test.removeListener(listener); test.removeListener(listener);
test.notify(); test.notify();
expect(log, equals(<String>[])); expect(log, <String>[]);
log.clear(); log.clear();
test.addListener(listener); test.addListener(listener);
test.notify(); test.notify();
expect(log, equals(<String>['listener'])); expect(log, <String>['listener']);
log.clear(); log.clear();
test.addListener(listener1); test.addListener(listener1);
test.notify(); test.notify();
expect(log, equals(<String>['listener', 'listener1'])); expect(log, <String>['listener', 'listener1']);
log.clear(); log.clear();
test.addListener(listener2); test.addListener(listener2);
test.notify(); test.notify();
expect(log, equals(<String>['listener', 'listener1', 'listener2'])); expect(log, <String>['listener', 'listener1', 'listener2']);
log.clear(); log.clear();
test.removeListener(listener1); test.removeListener(listener1);
test.notify(); test.notify();
expect(log, equals(<String>['listener', 'listener2'])); expect(log, <String>['listener', 'listener2']);
log.clear(); log.clear();
test.addListener(listener1); test.addListener(listener1);
test.notify(); test.notify();
expect(log, equals(<String>['listener', 'listener2', 'listener1'])); expect(log, <String>['listener', 'listener2', 'listener1']);
log.clear(); log.clear();
test.addListener(badListener); test.addListener(badListener);
test.notify(); test.notify();
expect(log, equals(<String>['listener', 'listener2', 'listener1', 'badListener'])); expect(log, <String>['listener', 'listener2', 'listener1', 'badListener']);
expect(tester.takeException(), isNullThrownError); expect(tester.takeException(), isNullThrownError);
log.clear(); log.clear();
...@@ -79,7 +79,7 @@ void main() { ...@@ -79,7 +79,7 @@ void main() {
test.removeListener(listener2); test.removeListener(listener2);
test.addListener(listener2); test.addListener(listener2);
test.notify(); test.notify();
expect(log, equals(<String>['badListener', 'listener1', 'listener2'])); expect(log, <String>['badListener', 'listener1', 'listener2']);
expect(tester.takeException(), isNullThrownError); expect(tester.takeException(), isNullThrownError);
log.clear(); log.clear();
}); });
...@@ -102,15 +102,39 @@ void main() { ...@@ -102,15 +102,39 @@ void main() {
test.addListener(listener2); test.addListener(listener2);
test.addListener(listener3); test.addListener(listener3);
test.notify(); test.notify();
expect(log, equals(<String>['listener1', 'listener2'])); expect(log, <String>['listener1', 'listener2']);
log.clear(); log.clear();
test.notify(); test.notify();
expect(log, equals(<String>['listener2', 'listener4'])); expect(log, <String>['listener2', 'listener4']);
log.clear(); log.clear();
test.notify(); test.notify();
expect(log, equals(<String>['listener2', 'listener4', 'listener4'])); expect(log, <String>['listener2', 'listener4', 'listener4']);
log.clear();
});
testWidgets('Merging change notifiers', (WidgetTester tester) async {
final TestNotifier source1 = new TestNotifier();
final TestNotifier source2 = new TestNotifier();
final TestNotifier source3 = new TestNotifier();
final List<String> log = <String>[];
final Listenable merged = new Listenable.merge(<Listenable>[source1, source2]);
final VoidCallback listener = () { log.add('listener'); };
merged.addListener(listener);
source1.notify();
source2.notify();
source3.notify();
expect(log, <String>['listener', 'listener']);
log.clear();
merged.removeListener(listener);
source1.notify();
source2.notify();
source3.notify();
expect(log, isEmpty);
log.clear(); log.clear();
}); });
} }
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