Skip to content

Commit

Permalink
Merge pull request #188 from Workiva/FEA-2771_action2
Browse files Browse the repository at this point in the history
FEA-2771 Add ActionV2 w/o null safety
  • Loading branch information
rmconsole5-wk authored Nov 3, 2023
2 parents b64f983 + 4c474cb commit 124b42f
Show file tree
Hide file tree
Showing 4 changed files with 266 additions and 63 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog


## 2.11.0
Create ActionV2 class with non-nullable payloads in preparation for null-safety.

## 2.10.15

- Dependency upgrades
Expand Down
42 changes: 28 additions & 14 deletions lib/src/action.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,22 @@ import 'package:w_common/disposable.dart';

import 'package:w_flux/src/constants.dart' show v3Deprecation;

/// Like [ActionV2], but payloads cannot be made non-nullable since the argument
/// to [call] is optional.
@Deprecated('Use ActionV2 instead, which supports non-nullable payloads.')
class Action<T> extends ActionV2<T> {
@override
String get disposableTypeName => 'Action';

@override
Future call([T payload]) => super.call(payload);
}

/// A command that can be dispatched and listened to.
///
/// An [Action] manages a collection of listeners and the manner of
/// An [ActionV2] manages a collection of listeners and the manner of
/// their invocation. It *does not* rely on [Stream] for managing listeners. By
/// managing its own listeners, an [Action] can track a [Future] that completes
/// managing its own listeners, an [ActionV2] can track a [Future] that completes
/// when all registered listeners have completed. This allows consumers to use
/// `await` to wait for an action to finish processing.
///
Expand All @@ -45,15 +56,15 @@ import 'package:w_flux/src/constants.dart' show v3Deprecation;
/// when a consumer needs to check state changes immediately after invoking an
/// action.
///
class Action<T> extends Object with Disposable implements Function {
class ActionV2<T> extends Object with Disposable implements Function {
@override
String get disposableTypeName => 'Action';
String get disposableTypeName => 'ActionV2';

List _listeners = [];
List<_ActionListener<T>> _listeners = [];

/// Dispatch this [Action] to all listeners. If a payload is supplied, it will
/// be passed to each listener's callback, otherwise null will be passed.
Future call([T payload]) {
/// Dispatch this [ActionV2] to all listeners. The payload will be passed to
/// each listener's callback.
Future call(T payload) {
// Invoke all listeners in a microtask to enable waiting on futures. The
// microtask queue is emptied before the event loop continues. This ensures
// synchronous listeners are invoked in the current tick of the event loop
Expand All @@ -65,23 +76,24 @@ class Action<T> extends Object with Disposable implements Function {
// a [Stream]-based action implementation. At smaller sample sizes this
// implementation slows down in comparison, yielding average times of 0.1 ms
// for stream-based actions vs. 0.14 ms for this action implementation.
Future callListenerInMicrotask(l) => Future.microtask(() => l(payload));
Future callListenerInMicrotask(_ActionListener<T> l) =>
Future.microtask(() => l(payload));
return Future.wait(_listeners.map(callListenerInMicrotask));
}

/// Cancel all subscriptions that exist on this [Action] as a result of
/// Cancel all subscriptions that exist on this [ActionV2] as a result of
/// [listen] being called. Useful when tearing down a flux cycle in some
/// module or unit test.
@Deprecated('Use (and await) dispose() instead. $v3Deprecation')
void clearListeners() {
_listeners.clear();
}

/// Supply a callback that will be called any time this [Action] is
/// Supply a callback that will be called any time this [ActionV2] is
/// dispatched. A payload of type [T] will be passed to the callback if
/// supplied at dispatch time, otherwise null will be passed. Returns an
/// [ActionSubscription] which provides means to cancel the subscription.
ActionSubscription listen(dynamic onData(T event)) {
ActionSubscription listen(dynamic Function(T event) onData) {
_listeners.add(onData);
return ActionSubscription(() => _listeners.remove(onData));
}
Expand All @@ -97,13 +109,15 @@ class Action<T> extends Object with Disposable implements Function {
}
}

/// A subscription used to cancel registered listeners to an [Action].
typedef _ActionListener<T> = dynamic Function(T event);

/// A subscription used to cancel registered listeners to an [ActionV2].
class ActionSubscription {
Function _onCancel;

ActionSubscription(this._onCancel);

/// Cancel this subscription to an [Action]
/// Cancel this subscription to an [ActionV2]
void cancel() {
if (_onCancel != null) {
_onCancel();
Expand Down
6 changes: 3 additions & 3 deletions lib/src/store.dart
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ typedef StoreHandler = Function(Store event);
///
/// General guidelines with respect to a `Store`'s data:
/// - A `Store`'s data should not be exposed for direct mutation.
/// - A `Store`'s data should be mutated internally in response to [Action]s.
/// - A `Store`'s data should be mutated internally in response to [ActionV2]s.
/// - A `Store` should expose relevant data ONLY via public getters.
///
/// To receive notifications of a `Store`'s data mutations, `Store`s can be
Expand Down Expand Up @@ -141,7 +141,7 @@ class Store extends Stream<Store> with Disposable {
/// Deprecated: 2.9.5
/// To be removed: 3.0.0
@deprecated
triggerOnAction(Action action, [void onAction(payload)]) {
triggerOnAction(ActionV2 action, [void onAction(payload)]) {
triggerOnActionV2(action, onAction);
}

Expand All @@ -153,7 +153,7 @@ class Store extends Stream<Store> with Disposable {
/// called until that future has resolved.
///
/// If the `Store` has been disposed, this method throws a [StateError].
void triggerOnActionV2<T>(Action<T> action,
void triggerOnActionV2<T>(ActionV2<T> action,
[FutureOr<dynamic> onAction(T payload)]) {
if (isOrWillBeDisposed) {
throw StateError('Store of type $runtimeType has been disposed');
Expand Down
Loading

0 comments on commit 124b42f

Please sign in to comment.