From c0a28afccc5c537d0eda1c65f2a71a17d49cf194 Mon Sep 17 00:00:00 2001 From: Kevin Moore Date: Fri, 6 Jun 2025 12:31:44 -0700 Subject: [PATCH] First prototype of pkg:listen --- pkgs/listen/.gitignore | 16 ++++ pkgs/listen/CHANGELOG.md | 3 + pkgs/listen/LICENSE | 25 ++++++ pkgs/listen/README.md | 1 + pkgs/listen/analysis_options.yaml | 17 ++++ pkgs/listen/lib/listen.dart | 114 ++++++++++++++++++++++++++ pkgs/listen/pubspec.yaml | 10 +++ pkgs/listen/test/listen_test.dart | 41 +++++++++ pkgs/listen/test/test_listenable.dart | 21 +++++ 9 files changed, 248 insertions(+) create mode 100644 pkgs/listen/.gitignore create mode 100644 pkgs/listen/CHANGELOG.md create mode 100644 pkgs/listen/LICENSE create mode 100644 pkgs/listen/README.md create mode 100644 pkgs/listen/analysis_options.yaml create mode 100644 pkgs/listen/lib/listen.dart create mode 100644 pkgs/listen/pubspec.yaml create mode 100644 pkgs/listen/test/listen_test.dart create mode 100644 pkgs/listen/test/test_listenable.dart diff --git a/pkgs/listen/.gitignore b/pkgs/listen/.gitignore new file mode 100644 index 00000000..813a31ec --- /dev/null +++ b/pkgs/listen/.gitignore @@ -0,0 +1,16 @@ +# Don’t commit the following directories created by pub. +.buildlog +.pub/ +.dart_tool/ +build/ +packages +.packages + +# Or the files created by dart2js. +*.dart.js +*.js_ +*.js.deps +*.js.map + +# Include when developing application packages. +pubspec.lock diff --git a/pkgs/listen/CHANGELOG.md b/pkgs/listen/CHANGELOG.md new file mode 100644 index 00000000..e7edfb1a --- /dev/null +++ b/pkgs/listen/CHANGELOG.md @@ -0,0 +1,3 @@ +## 1.0.0-beta.0 + +- First release. diff --git a/pkgs/listen/LICENSE b/pkgs/listen/LICENSE new file mode 100644 index 00000000..aea51a33 --- /dev/null +++ b/pkgs/listen/LICENSE @@ -0,0 +1,25 @@ +Copyright 2014 The Flutter Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/pkgs/listen/README.md b/pkgs/listen/README.md new file mode 100644 index 00000000..203e584b --- /dev/null +++ b/pkgs/listen/README.md @@ -0,0 +1 @@ +Very prototype package diff --git a/pkgs/listen/analysis_options.yaml b/pkgs/listen/analysis_options.yaml new file mode 100644 index 00000000..75fcb402 --- /dev/null +++ b/pkgs/listen/analysis_options.yaml @@ -0,0 +1,17 @@ +# https://dart.dev/guides/language/analysis-options + +include: package:dart_flutter_team_lints/analysis_options.yaml + +analyzer: + language: + strict-raw-types: true + +linter: + rules: + - avoid_unused_constructor_parameters + - cancel_subscriptions + - literal_only_boolean_expressions + - missing_whitespace_between_adjacent_strings + - no_adjacent_strings_in_list + - no_runtimeType_toString + - unnecessary_await_in_return diff --git a/pkgs/listen/lib/listen.dart b/pkgs/listen/lib/listen.dart new file mode 100644 index 00000000..a5199e6f --- /dev/null +++ b/pkgs/listen/lib/listen.dart @@ -0,0 +1,114 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/// An object that maintains a list of listeners. +/// +/// The listeners are typically used to notify clients that the object has been +/// updated. +/// +/// There are two variants of this interface: +/// +/// * [ValueListenable], an interface that augments the [Listenable] interface +/// with the concept of a _current value_. +/// +/// * [Animation], an interface that augments the [ValueListenable] interface +/// to add the concept of direction (forward or reverse). +/// +/// Many classes in the Flutter API use or implement these interfaces. The +/// following subclasses are especially relevant: +/// +/// * [ChangeNotifier], which can be subclassed or mixed in to create objects +/// that implement the [Listenable] interface. +/// +/// * [ValueNotifier], which implements the [ValueListenable] interface with +/// a mutable value that triggers the notifications when modified. +/// +/// The terms "notify clients", "send notifications", "trigger notifications", +/// and "fire notifications" are used interchangeably. +/// +/// See also: +/// +/// * [AnimatedBuilder], a widget that uses a builder callback to rebuild +/// whenever a given [Listenable] triggers its notifications. This widget is +/// commonly used with [Animation] subclasses, hence its name, but is by no +/// means limited to animations, as it can be used with any [Listenable]. It +/// is a subclass of [AnimatedWidget], which can be used to create widgets +/// that are driven from a [Listenable]. +/// * [ValueListenableBuilder], a widget that uses a builder callback to +/// rebuild whenever a [ValueListenable] object triggers its notifications, +/// providing the builder with the value of the object. +/// * [InheritedNotifier], an abstract superclass for widgets that use a +/// [Listenable]'s notifications to trigger rebuilds in descendant widgets +/// that declare a dependency on them, using the [InheritedWidget] mechanism. +/// * [Listenable.merge], which creates a [Listenable] that triggers +/// notifications whenever any of a list of other [Listenable]s trigger their +/// notifications. +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. + /// + /// Once the factory is called, items must not be added or removed from the + /// iterable. + /// Doing so will lead to memory leaks or exceptions. + /// + /// The iterable may contain nulls; they are ignored. + factory Listenable.merge(Iterable listenables) = + _MergingListenable; + + /// Register a closure to be called when the object notifies its listeners. + void addListener(void Function() listener); + + /// Remove a previously registered closure from the list of closures that the + /// object notifies. + void removeListener(void Function() listener); +} + +/// An interface for subclasses of [Listenable] that expose a [value]. +/// +/// This interface is implemented by [ValueNotifier] and [Animation], and +/// allows other APIs to accept either of those implementations interchangeably. +/// +/// See also: +/// +/// * [ValueListenableBuilder], a widget that uses a builder callback to +/// rebuild whenever a [ValueListenable] object triggers its notifications, +/// providing the builder with the value of the object. +abstract class ValueListenable extends Listenable { + /// Abstract const constructor. This constructor enables subclasses to provide + /// const constructors so that they can be used in const expressions. + const ValueListenable(); + + /// The current value of the object. When the value changes, the callbacks + /// registered with [addListener] will be invoked. + T get value; +} + +class _MergingListenable extends Listenable { + _MergingListenable(this._children); + + final Iterable _children; + + @override + void addListener(void Function() listener) { + for (final child in _children) { + child?.addListener(listener); + } + } + + @override + void removeListener(void Function() listener) { + for (final child in _children) { + child?.removeListener(listener); + } + } + + @override + String toString() { + return 'Listenable.merge([${_children.join(", ")}])'; + } +} diff --git a/pkgs/listen/pubspec.yaml b/pkgs/listen/pubspec.yaml new file mode 100644 index 00000000..a081f731 --- /dev/null +++ b/pkgs/listen/pubspec.yaml @@ -0,0 +1,10 @@ +name: listen +version: 1.0.0-beta.0 +description: Includes core Flutter types Listenable and ValueListenable + +environment: + sdk: ^3.7.0 + +dev_dependencies: + dart_flutter_team_lints: ^3.5.0 + test: any diff --git a/pkgs/listen/test/listen_test.dart b/pkgs/listen/test/listen_test.dart new file mode 100644 index 00000000..11904aef --- /dev/null +++ b/pkgs/listen/test/listen_test.dart @@ -0,0 +1,41 @@ +// Copyright 2014 The Flutter 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:listen/listen.dart'; +import 'package:test/test.dart'; + +import 'test_listenable.dart'; + +void main() { + test('Listenable.merge', () { + final listenableA = TestListenable(); + final listenableB = TestListenable(); + + final merged = Listenable.merge([listenableA, listenableB]); + + var callCount = 0; + + void testListener() { + callCount++; + } + + merged.addListener(testListener); + + expect(callCount, 0); + + listenableA.notify(); + expect(callCount, 1); + + listenableB.notify(); + expect(callCount, 2); + + merged.removeListener(testListener); + + listenableA.notify(); + expect(callCount, 2); + + listenableB.notify(); + expect(callCount, 2); + }); +} diff --git a/pkgs/listen/test/test_listenable.dart b/pkgs/listen/test/test_listenable.dart new file mode 100644 index 00000000..57788348 --- /dev/null +++ b/pkgs/listen/test/test_listenable.dart @@ -0,0 +1,21 @@ +import 'package:listen/listen.dart'; + +class TestListenable extends Listenable { + final _listeners = {}; + + void notify() { + for (final listener in _listeners) { + listener(); + } + } + + @override + void addListener(void Function() listener) { + _listeners.add(listener); + } + + @override + void removeListener(void Function() listener) { + _listeners.remove(listener); + } +}