subject

Creator: coderz1093

Last updated:

0 purchases

TODO
Add to Cart

Description:

subject

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.


Click here to see how to setup the Code Generation.
Use dart run subject:build to generate the code.

Features #

Subject and Observer, as base implementation
Callback, Stream, Sink and Stateful mixins, to extend the Subject and Observer classes
Alternative implementations, such as Publisher and EventEmitter
Code generation using @subject and @observe, to automatically generate an observable interface for any class

Getting Started #
dart pub add subject
copied to clipboard
And import the package:
import 'package:subject/subject.dart';
copied to clipboard
Basic Observer Pattern #
The observer pattern is a software design pattern in which an object, called the subject, maintains a list of its dependents, called observers, and notifies them automatically of any state changes, usually by calling one of their methods.
You can create a subject and an observer, and attach the observer to the subject:
final subject = Subject<String>();

final observer = Observer<String>(
(subject, state) => print('Observer Callback: $state')
);

subject.attach(observer);
copied to clipboard
Now, you can notify the subject to update the state and notify the observers:
subject.notify('Hello World!');
copied to clipboard
Subjects and Observers #
There are many ways to implement the observer pattern, and this package provides several implementations, which can be mixed into the subject and observer classes.
Callback - Observer #
final subject = Subject<String>();
final observer = Observer<String>((subject, state) => print('Observer: $state'));

subject.attach(observer);
subject.notify('Hello World!');
copied to clipboard
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!');
copied to clipboard
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!');
copied to clipboard
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}');
copied to clipboard
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 attached to the subject as auto disposable 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 #

Cancelable - Allows the observer to be canceled, which will detach it from the subject
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

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.
Start the subject code generator by running the following command:

dart run subject:build - Build once
dart run subject:watch - Watch for changes and build

Or, using Flutter:

flutter pub run subject:build - Build once
flutter pub run subject:watch - Watch for changes and build

Or instead, manually:
dart pub add subject_gen -d
dart run build_runner build -d
copied to clipboard
These commands will add the subject_gen package as a development dependency and run the builder with the build -d command, or watch -d for continuous generation. You only need to add the package as a development dependency one time.
Annotation @subject #
By using the @subject annotation you can generate a subject class for the annotated class.
The generated class will be named ${className}Subject, and a mixin named Observable${className}.
@subject
class User {
final String name;

User(this.name);

void say(String message) => print('$name says "$message"');
}
copied to clipboard
The @subject annotation will create a UserSubject class that wraps all methods and properties in a notify call, making them observable.
You can use the @dontObserve annotation to exclude elements from the generated subject class.
Annotation @observe #
The @observe annotation is used to indicate which methods and properties should be observable in the generated subject class.
class User {
final String name;

User(this.name);

@observe
void say(String message) => print('$name says "$message"');
}
copied to clipboard
In the User class, only the say method is annotated with @observe, which means it will be the only observable element in the generated UserSubject class.
The other elements of the class will not be observable.
The @observe annotation overrides the @subject annotation, so if you use both, only the elements annotated with @observe will be observable.
Annotation @SubjectWith #
Is used to generate a subject class with customized properties, such as subject and mixin names.
@SubjectWith(name: 'User', observable: false)
class _User {
final String name;

_User(this.name);

void say(String message) => print('$name says "$message"');
}
copied to clipboard
Generated Code #
Applying the annotations will generate the following code:

Class ${className}Subject - Overrides for the target methods and properties to be observed and notify.
Mixin Observable${className} - Contains the .on() and .onBefore() methods, with the generated interface, to listen to the target methods and properties.

@subject
class User {
User(this.name);
}

void main() {
final user = UserSubject('John');
}
copied to clipboard
The mixin, Observable${className}, can be applied to the target class, to add the .on() and .onBefore() methods.
But this will require the target class to instantiate the subject class using a factory.
This is useful to avoid having to create a subject class for each instance of the target class.
Meaning that every instance of the target class will have the observable functionalities.
Should be used like:
@subject
class User<T, ...> with ObservableUser<T, ...> {
factory User(String name) => UserSubject._;
User._(this.name);
}

void main() {
final user = User('John');
}
copied to clipboard
If you apply the Observable mixin without using the Subject class, the target methods and properties will not notify.
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"'),
);
copied to clipboard
You can also listen to events using .subject.on(), .subject.onMethod() and .subject.onProperty() methods.
Examples #

Subject / Observer (View on GitHub)
import 'package:subject/observer.dart';

void main() {
final subject = Subject<String>();

final observer = Observer<String>((subject, state) => print('Observer 1: $state'));
final streamObserver = Observer.stream<String>()..listen((state) => print('Observer 2: $state'));

subject.attach(observer);
subject.attach(streamObserver);

subject.notify('Hello World!');
print('There are ${subject.observers.length} observers attached to the subject.');

print('Detaching observer...');
subject.detach(observer);

subject.notify('Hello World, again!');

/* [Output]
Observer 1: Hello World!
Observer 2: Hello World!

There are 2 observers attached to the subject.
Detaching observer...

Observer 2: Hello World, again!
*/
}
copied to clipboard


Code Generator (View on GitHub)
import 'package:subject/subject.dart';

part 'build.g.dart';

@subject
class User<T> {
final String name;
String? thought;

@dontObserve
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"'),
thought: (value, previous) => print('User thought "$value"'),
);

user.say('Hello World!');
user.thought = 'I am thinking...';

/* [Output]
John says "Hello World!"
User said "Hello World!"
User thought "I am thinking..."
*/
}
copied to clipboard


Extending (View on 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!"
*/
}
copied to clipboard


Sink / Stream (View on 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!"
*/
}
copied to clipboard


Stateful (View on GitHub)
import 'package:subject/observer.dart';

/* -= Stateful - Subject =- */

void statefulSubject() {
final subject = Subject.stateful<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 observer = Observer.stateful<String>();
subject.attach(observer);

subject.notify('Hello World!');
print('The state is "${ observer.state }"');

/* [Output]
The state is "Hello World!"
*/
}

void main() {
print('[Stateful Subject]');
statefulSubject();

print('');
print('[Stateful Observer]');
statefulObserver();
}
copied to clipboard


Publisher (View on 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!"
*/
}
copied to clipboard


EventEmitter (View on 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'));

events.emit('message', 'Hello World!');
events.emit('message', 42);

listener.cancel();
events.emit('message', 'Hello World, again!');

// [Output]
// String: Hello World!
// Integer: 42
}
copied to clipboard

Contributing #
Contributions are welcome! Please open an issue or pull request if you find a bug or have a feature request.

License

For personal and professional use. You cannot resell or redistribute these repositories in their original state.

Files In This Product:

Customer Reviews

There are no reviews.