subject 0.0.2
subject: ^0.0.2 copied to clipboard
Observer Pattern implementation for Dart, using callbacks, streams, etc... Subject code generator with annotations, to automatically generate an observable interface for any class.
Observer Pattern - Subject #
Observer Pattern implementation for Dart, using callbacks, streams and states. Subject code generator with annotations, to automatically generate an observable interface for any class.
@subject
class User {
final String name;
String? thought;
User(this.name);
void say(String message) => print('$name says "$message"');
}
void main() {
final user = UserSubject('John');
user.on(
say: (message) => print('User says "$message"')
);
user.say('Hello World!');
}
Features #
- Subject and Observer, for base implementation
- Callback, Stream and Stateful mixins, to extend the Subject and Observer classes
- Alternative implementations, such as Publisher and EventEmitter
@subject
and@observe
annotations, to generate an observable interface for any class
Getting Started #
dart pub add subject
And import the package:
import 'package:subject/subject.dart';
Usage #
Create a subject by initializing or extending Subject, you can set the type of the state that will be passed to the observers:
final subject = Subject<String>();
Create an observer and attach it to the subject.
An observer can be created using Observer
or Observer.stream
:
final observer = Observer<String>((subject, state) => print('Observer Callback: $state'));
final observerStream = Observer.stream<String>()..listen((state) => print('Observer Stream: $state'));
final observerCoupled = Observer.coupled<String>(
attached: (subject, observer) => print('Observer Attached'),
detached: (subject, observer) => print('Observer Detached'),
);
final observerStateful = Observer.stateful<String>();
subject.attach(observer);
subject.attach(observerStream);
Notify the subject to update the state and notify the observers:
subject.notify('Hello World!');
Subjects and Observers #
You have different bases to create a subject and observer, each with its features:
Callback - Observer
#
final subject = Subject<String>();
final observer = Observer<String>((subject, state) => print('Observer: $state'));
subject.attach(observer);
subject.notify('Hello World!');
Async - Subject.sink
/ Observer.stream
#
final subject = Subject.sink<String>(sync: true);
final observer = Observer.stream<String>(sync: true);
observer.listen((state) => print('Observer: $state'));
subject.attach(observer);
subject.add('Hello World!');
Coupled - Observer.coupled
#
final subject = Subject<String>();
final observer = Observer.coupled<String>(
attached: (subject, observer) => print('Observer Attached'),
detached: (subject, observer) => print('Observer Detached'),
);
subject.attach(observer);
subject.notify('Hello World!');
Stateful - Subject.stateful
/ Observer.stateful
#
final subject = Subject.stateful<String>(state: 'Initial State', notifyOnAttach: true);
final observer = Observer.stateful<String>();
subject.attach(observer);
subject.notify('Hello World!');
print('Subject State: ${subject.state}');
Mixins #
You can create your own subject and observer classes, by extending the base classes and mixing the desired features:
Subject #
- StreamableSubject - Transforms the subject into a stream, subscriptions are plugged into the subject as StreamObservers
- SinkableSubject - Transforms the subject into a sink, which notifies the subject when a value is added
- SubjectState - Allows the subject to have a persistent state
Observer #
- Callbackable - Allows the observer to be instantiated with a callback
- StreamableObserver - Transforms the observer into a stream, which can be listened to
- ObserverState - Allows the observer to have a persistent state
- Cancelable - Allows the observer to be canceled, which will detach it from the subject
Code Generation #
With the @subject
and @observe
annotations, you can generate an observable interface for any class automatically.
You can listen to methods calls, and changes in properties, using the .on()
and .onBefore()
methods.
Annotation @subject
#
By using the @subject
annotation, you can create a subject class that generates an observable interface for the annotated class.
The generated class will be named ${className}Subject
.
@subject
class User {
final String name;
String? thought;
User(this.name);
void say(String message) => print('$name says "$message"');
}
The @subject
annotation will create a UserSubject
class that wraps all the annotated methods and properties in a notify
call, making them observable.
Annotation @observe
#
The @observe
annotation is used to indicate which methods and properties should be wrapped when generating the observable interface.
You can use it to specify which elements should be observable and which should not.
class User {
final String name;
String? thought;
User(this.name);
@observe
void say(String message) => print('$name says "$message"');
}
In the User
class, only the say
method is annotated with @observe
, which means only it will be observable in the generated UserSubject
class.
The other elements of the class will not be included in the generated class.
The @observe
annotation overrides the @subject
annotation, so if you use both, only the elements annotated with @observe
will be observable.
Listening to events #
To listen to events, you can use the .on()
and .onBefore()
methods, which are included in the generated subject class.
The .on()
method contains all the generated methods and setters, making it easy to listen to events for the annotated class.
final user = UserSubject('John');
user.on(
say: (message) => print('User said "$message"'),
);
Example #
Subject / Observer (GitHub)
import 'package:subject/observer.dart';
void main() {
final subject = Subject<String>();
final observer = Observer<String>((subject, state) => print('Observer 1: $state'));
subject.attach(observer);
subject.attach(Observer((subject, state) => print('Observer 2: $state')));
subject.attach(Observer.stream()..listen((state) => print('Observer 3: $state')));
subject.notify('Hello World!');
print('There are ${subject.observers.length} observers attached to the subject.');
subject.detach(observer);
subject.notify('Hello World, again!');
/* [Output]
Observer 1: Hello World!
Observer 2: Hello World!
Observer 3: Hello World!
There are 3 observers attached to the subject.
Observer 2: Hello World, again!
Observer 3: Hello World, again!
*/
}
Code Generator (GitHub)
import 'package:subject/subject.dart';
part 'build.g.dart';
@subject
class User<T> {
final String name;
String? thought;
T value;
User(this.name, this.value);
// @observe
void say(String message) => print('$name says "$message"');
}
void main() {
final user = UserSubject('John', 4);
user.on(
say: (message) => print('User said "$message"'),
);
user.say('Hello world');
/* [Output]
John says "Hello world"
User said "Hello world"
*/
}
Extending (GitHub)
import 'package:subject/observer.dart';
class User extends Subject<String> {
final String name;
User(this.name);
void say(String message) {
print(message);
notify(message);
}
}
class UserObserver with Observer<String> {
@override
void update(Subject<String> subject, String message) {
if (subject is! User) return;
print('${ subject.name } says "$message"');
}
}
void main() {
final user = User('John');
user.attach(UserObserver());
user.say('Hello World!');
/* [Output]
Hello World!
John says "Hello World!"
*/
}
Sink / Stream (GitHub)
import 'package:subject/observer.dart';
void main() {
final subject = Subject.sink<String>();
final observer = Observer.stream<String>();
observer.listen((message) => print('Observer: "$message"'));
subject.attach(observer);
subject.add('Hello World!');
/* [Output]
Observer: "Hello World!"
*/
}
Stateful (GitHub)
import 'package:subject/observer.dart';
/* -= Stateful - Subject =- */
void statefulSubject() {
final subject = StatefulSubject<String>(notifyOnAttach: true);
subject.notify('Hello World!');
subject.attach(Observer((subject, state) => print('Observer: "$state"')));
print('The state is "${ subject.state }"');
/* [Output]
Observer: "Hello World!"
The state is "Hello World!"
*/
}
/* -= Stateful - Observer =- */
void statefulObserver() {
final subject = Subject<String>();
final stateful = StatefulObserver<String>();
subject.attach(stateful);
subject.notify('Hello World!');
print('The state is "${ stateful.state }"');
/* [Output]
The state is "Hello World!"
*/
}
void main() {
print('[Stateful Subject]');
statefulSubject();
print('');
print('[Stateful Observer]');
statefulObserver();
}
Publisher (GitHub)
import 'package:subject/publisher.dart';
void main() {
final publisher = Publisher<String>();
final subscriber = Subscriber<String>((subject, message) => print('Callback: "$message"'));
publisher.subscribe(subscriber);
publisher.subscribe(Subscriber<String>()..listen((message) => print('Stream: "$message"')));
publisher.publish('Hello World!');
print('There are ${ publisher.subscribers.length } subscribers attached to the publisher.');
subscriber.cancel();
publisher.publish('Hello World, again!');
/* [Output]
Callback: "Hello World!"
Stream: "Hello World!"
There are 2 subscribers attached to the publisher.
Stream: "Hello World, again!"
*/
}
EventEmitter (GitHub)
import 'package:subject/event_emitter.dart';
void main() {
final events = EventEmitter();
final listener = events.on('message', (String data) => print('String: $data'));
events.on('message', (int data) => print('Integer: $data'));
listener.listen((event) => print('Event: $event'));
events.emit('message', 'Hello World');
events.emit('message', 42);
// [Output]
// String: Hello World
// Integer: 42
}
Contributing #
Contributions are welcome! Please open an issue or pull request if you find a bug or have a feature request.