Skip to content

Adding package:listen #898

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions pkgs/listen/.gitignore
Original file line number Diff line number Diff line change
@@ -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
3 changes: 3 additions & 0 deletions pkgs/listen/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## 1.0.0-beta.0

- First release.
25 changes: 25 additions & 0 deletions pkgs/listen/LICENSE
Original file line number Diff line number Diff line change
@@ -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.
1 change: 1 addition & 0 deletions pkgs/listen/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Very prototype package
17 changes: 17 additions & 0 deletions pkgs/listen/analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -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
114 changes: 114 additions & 0 deletions pkgs/listen/lib/listen.dart
Original file line number Diff line number Diff line change
@@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is that class in scope? Same for ValueListenableBuilder.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope! This is a draft PR. These will need to be cleaned up!

/// 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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not abstract. Just "Const constructor".

/// 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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Return" -> "Creates"

/// 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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(That's a bad API. If the class plans to iterate the argument more than once, it should copy it, or request a List. Allowing an Iterable means you can get a lazily computed collection, and if you iterate it more than once, that's going to be even more expensive than just copying to a list once and for all.)

///
/// The iterable may contain nulls; they are ignored.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really not great API to allowing null if you don't use it. (A left-over from NNBD conversion?)

factory Listenable.merge(Iterable<Listenable?> listenables) =
_MergingListenable;

/// Register a closure to be called when the object notifies its listeners.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Registers", and "Removes" below. Check using third person present tense everywhere.

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<T>] and [Animation<T>], 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<T> 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<Listenable?> _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(", ")}])';
}
}
10 changes: 10 additions & 0 deletions pkgs/listen/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -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
41 changes: 41 additions & 0 deletions pkgs/listen/test/listen_test.dart
Original file line number Diff line number Diff line change
@@ -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);
});
}
21 changes: 21 additions & 0 deletions pkgs/listen/test/test_listenable.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import 'package:listen/listen.dart';

class TestListenable extends Listenable {
final _listeners = <void Function()>{};

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);
}
}
Loading