event 1.1.3

  • Readme
  • Changelog
  • Example
  • Installing
  • 78

Event #

Pub Package

Supports the creation of lightweight custom Dart Events, that allow interested subscribers to be notified that something has happened. Provides a notification mechanism across independent packages/layers/modules.

This package is inspired by the C# language's implementation of Events and Delegates.

See also #

EventSubscriber - A Flutter Widget that can subscribe to an [Event] with optional arguments. Updates (rebuilds) when notified.

Background #

As developers, we understand that dividing independent functionality into separate modules (packages) is something to which we should aspire. It can be ideal to model our problem domain independent of user interface, other systems, and technical plumbing. Equally, independent pieces of infrastructure benefit from being in separate modules (packages). Doing so has the same attraction as the decomposition of functionality into separate subroutines, albeit at a larger scale. Let's divide a large problem into smaller pieces, that can be reasoned about and worked on independently, and then re-combined to represent a solution to the problem.

To make something independent, it should should know nothing of the things that might depend on it.

An elevator example

Consider for example, that an independent model of the operation of a single elevator needs know nothing of the user interfaces (UI) or system interfaces (SI) dependent on it. There might be a diagnostics UI written in Flutter, a console (CUI) interface, as well as as a module that only exposes a programmatic interface (API) that external programs might consume to control the elevator.

There might be other 'consumers' of the domain model, but the model need not care as it knows nothing of them.

Or does it need to?

How do these 'consumers' know when something has happened in the model? In the case of the elevator, the model might be connected to a real physical elevator through a manufacturer supplied control library, which in turn talks to the elevator's programmable logic controller (PLC).

How can the physical elevator report that something happened through the control library to the model, and in turn to each of the three (or more) consumers? The model knows nothing of its consumers. Likewise, an independent manufacturers control library knows nothing of your elevator domain model.

How can something that is independent and in a separate module (package), notify a consumer it doesn't know, that something has happened?

The solution

The answer provided in this package, is to model an Event that can be published by an independent module (package), and subscribed to by a consumer elsewhere. An Event represents that something has happened. It can be created and broadcast (triggered) without the publisher having any connection to those that might be consuming it.

In the case of the elevator, the manufacturer's control library can indicate that something in the real elevator happened (via the PLC) by publishing an Event. The domain model can subscribe to those Events where applicable, and cause some change in the model if required - perhaps updating the floor the current elevator is on.

Likewise, the domain model can publish Events which the three consumers of the model can choose to subscribe to.

Note that the three consumers of the model, as well as the model in relation to the elevator control library, remain independent.

Dependencies #

None. This Dart package has no non-development dependencies on other packages.

Implementation Notes #

An Event is lightweight. It maintains a list of subscribers, but that list is only instantiated the first time it is subscribed to. Broadcasting an Event does nothing if there are no subscribers. With no overhead, or impact on performance, feel free to declare and publish large numbers of Events.

var changeEvent = Event();
changeEvent.broadcast();

// onChange is lightweight
// broadcast incurs no cost here as no subscribers

An Event can include a custom 'argument' [EventArgs], which supports the subscriber being supplied with some data related to the Event.

// A custom 'argument' class
class ChangeArgs extends EventArgs {
  int value;
  ChangeArgs(this.value);
}

var changeEvent = Event<ChangeArgs>();
changeEvent.broadcast(ChangeArgs(61));

// ChangeArgs, and hence its value 61, is passed to all subscribers

// TODO: show example of "Named Event Pattern".

Examples #

Two examples are shown below. The first shows an Event, without an argument provided to handlers (subscribers). The second, shows an Event which does provide a custom argument to the handlers.

Example 1: A simple Event with no argument

import 'package:event/event.dart';

void main() {
  /// An incrementing counter.
  var c = Counter();

  // Subscribe to the custom event
  c.valueChangedEvent + (args) => print('boom');

  c.increment();
  c.reset();

  // outputs...
  // boom
  // boom
}

//-----------------

/// Represents a number counter that can be incremented.
/// Notifies [Event] handlers (subscribers) when incremented.
class Counter {
  /// The current [Counter] value.
  int value = 0;

  /// A custom [Event]
  final valueChangedEvent = Event();

  /// Increment the [Counter] [value] by 1.
  void increment() {
    value++;
    // Broadcast the change
    valueChangedEvent.broadcast();
  }

  /// Reset the [Counter] [value] to 0.
  void reset() {
    value = 0;
    // Broadcast the change
    valueChangedEvent.broadcast();
  }
}

Example 2: An Event with a custom event argument [EventArgs]

import 'package:event/event.dart';

void main() {
   /// An incrementing counter.
  var c = Counter();

  // Subscribe to the custom event
  c.valueChangedEvent + (args) => print('value changed to ${args.changedValue}');

  c.increment();
  c.reset();

  // outputs...
  // 1
  // 0
}

//-----------------

/// Represents a number counter that can be incremented.
/// Notifies [Event] handlers (subscribers) when incremented.
/// The notification includes the changed [value]
/// See [ValueEventArgs].
class Counter {
  /// The current [Counter] value.
  int value = 0;

  /// A custom [Event] with argument [ValueEventArgs]
  /// See [ValueEventArgs] class below.
  final valueChangedEvent = Event<ValueEventArgs>();

  /// Increment the [Counter] [value] by 1.
  void increment() {
    value++;
    // Broadcast the change, supplying the value
    valueChangedEvent.broadcast(ValueEventArgs(value));
  }

  /// Reset the [Counter] [value] to 0.
  void reset() {
    value = 0;
    // Broadcast the change, supplying the value
    valueChangedEvent.broadcast(ValueEventArgs(value));
  }
}

//-----------------

/// Represents the arguments provided to handlers
/// when an [Event] occurs.
class ValueEventArgs extends EventArgs {
  int changedValue;
  ValueEventArgs(this.changedValue);
}

Features and bugs #

Please file feature requests and bugs at the issue tracker.

Changelog - Event #

Version 1.1.3 (2020-02-14) #

  • BasicEventArgs renamed to StdEventArgs for clarity.

Version 1.1.2 (2020-02-09) #

  • EventArgs changes
    • Added BasicEventArgs as a standard EventArgs derived type. It includes a whenOccurred field that contains the date and time the Event was broadcast, as well as an optional description field.
    • Renamed EventArgs1 and EventArgs2 to GenericEventArgs1 and GenericEventArgs2 respectively, to better indicate their purpose. Both are now derived from BasicEventArgs, meaning that they have whenOccurred and (optional) description fields.
    • Improvements to documentation
  • Minor improvements to Event documentation.

Version 1.1.1 (2020-02-03) #

  • Add new method unsubscribeAll, to unsubscribe all subscribers (handlers).

Version 1.1.0 (2020-01-29) #

Breaking Change

The function signature for an Event has been simplified. The sender argument has been removed, leaving only the need to provide an optional argument derived from type EventArgs.

The sender argument was previously intended to be used to provide the source of the Event, or the object in which the Event was declared, to a subscriber. This can be equally well done within an EventArg passed as an argument to a subscriber.

// Before
// subscribe to onValueChanged Event
myCounter.onValueChanged + (sender, args) => print('before');

// Now
// subscribe to onValueChanged Event
myCounter.onValueChanged + (args) => print('after');

Other Breaking Changes

  • Renamed addHandler and removeHandler methods to subscribe and unsubscribe respectively.
  • Renamed raise methods to broadcast.
  • Method broadcastWithSubject removed to reflect to removal of sender described above.
  • The count method has been renamed to `subscriberCount'

Other

  • Two general purpose EventArg derived classes (EventArgs1 and EventArgs2) have been included, which offers a quick alternative to producing your own custom EventArgs class.

EventArgs1 supports one generic value, while EventArgs2 supports two. Example:-

// EventArgs1 (one value)
var e = Event<EventArgs1<String>>();
e.subscribe((args) => print(args.value));
e.broadcast(EventArgs1('hello'));
// prints hello

// EventArgs2 (two values)
var e = Event<EventArgs2<String, int>>();
e.subscribe((args) => print('${args.value1} - ${args.value2}'));
e.broadcast(EventArgs2('boom', 37));
// prints boom - 37

Version 1.0.3 (2020-01-22) #

  • Added image of elevator example to README.

Version 1.0.2 (2020-01-22) #

  • Updated reference to Flutter EventSubscriber in the README.
  • Minor documentation improvements

Version 1.0.1 (2020-01-22) #

  • Documentation improvements and corrections

Version 1.0.0 (2020-01-22) #

  • Initial release

example/event_example.dart

import 'package:event/event.dart';

/*
Summary

1. An Event 'valueChangedEvent' is declared in the Counter class
2. It provides a custom argument to subscribers of the event
      as specified in the ValueEventArgs class at the
       bottom of this page.
   Note that providing an argument to an Event is optional.
3. A subscriber to the Event is added in the main() method.
4. The Event (with custom argument) is broadcast (notified) to
      subscribers in the Counter's increment and reset methods.
*/

void main() {
  var c = Counter();

  // Subscribe to the custom Event.
  c.valueChangedEvent.subscribe((args) => print('value changed to ${args.changedValue}'));

  // The '+' operator is a shortcut for the subscribe method.
  // It is directly equivalent to ...
  // c.onValueChanged + (args) => print('value changed to ${args.changedValue}');

  // Increment the Counter. Subscribers are notified.
  c.increment();

  // Reset the Counter to 0. Subscribers are notified.
  c.reset();
}

//-----------------

/// Represents an example number counter that can be incremented.
///
/// Notifies [Event] handlers (subscribers) when incremented.
/// The notification includes some custom arguments - in this case
/// the changed [value] (see [ValueEventArgs] below).
class Counter {
  /// The current [Counter] value.
  int value = 0;

  /// A custom [Event] of type [ValueEventArgs]
  final valueChangedEvent = Event<ValueEventArgs>();

  /// Increment the [Counter] [value] by 1.
  void increment() {
    value++;
    // notify subscribers of the change in value
    valueChangedEvent.broadcast(ValueEventArgs(value));
  }

  /// Reset the [Counter] [value] to 0.
  void reset() {
    value = 0;
    // notify subscribers of the change in value
    valueChangedEvent.broadcast(ValueEventArgs(value));
  }
}

//-----------------

/// Represents some custom arguments provided to subscribers
/// when an [Event] occurs.
class ValueEventArgs extends EventArgs {
  int changedValue;

  ValueEventArgs(this.changedValue);
}

Use this package as a library

1. Depend on it

Add this to your package's pubspec.yaml file:


dependencies:
  event: ^1.1.3

2. Install it

You can install packages from the command line:

with pub:


$ pub get

with Flutter:


$ flutter pub get

Alternatively, your editor might support pub get or flutter pub get. Check the docs for your editor to learn more.

3. Import it

Now in your Dart code, you can use:


import 'package:event/event.dart';
  
Popularity:
Describes how popular the package is relative to other packages. [more]
56
Health:
Code health derived from static analysis. [more]
100
Maintenance:
Reflects how tidy and up-to-date the package is. [more]
100
Overall:
Weighted score of the above. [more]
78
Learn more about scoring.

We analyzed this package on Feb 26, 2020, and provided a score, details, and suggestions below. Analysis was completed with status completed using:

  • Dart: 2.7.1
  • pana: 0.13.5

Health suggestions

Format lib/src/eventargs.dart.

Run dartfmt to format lib/src/eventargs.dart.

Dependencies

Package Constraint Resolved Available
Direct dependencies
Dart SDK >=2.7.0 <3.0.0
Dev dependencies
pedantic ^1.8.0
test ^1.6.0