blob: a093d01efb7f1645ff3290c258adb9837ee434ef [file] [log] [blame]
import 'dart:async';
import 'package:meta/meta.dart';
import 'internal.dart';
import 'observable.dart';
import 'records.dart';
/// Supplies [changes] and various hooks to implement [Observable].
///
/// May use [notifyChange] to queue a change record; they are asynchronously
/// delivered at the end of the VM turn.
///
/// [ChangeNotifier] may be extended, mixed in, or used as a delegate.
class ChangeNotifier<C extends ChangeRecord> implements Observable<C> {
StreamController<List<C>> _changes;
bool _scheduled = false;
List<C> _queue;
/// Emits a list of changes when the state of the object changes.
///
/// Changes should produced in order, if significant.
@override
Stream<List<C>> get changes {
return (_changes ??= StreamController<List<C>>.broadcast(
sync: true,
onListen: observed,
onCancel: unobserved,
))
.stream;
}
/// May override to be notified when [changes] is first observed.
@override
@protected
@mustCallSuper
void observed() {}
/// May override to be notified when [changes] is no longer observed.
@override
@protected
@mustCallSuper
void unobserved() {
_changes = _queue = null;
}
/// If [hasObservers], synchronously emits [changes] that have been queued.
///
/// Returns `true` if changes were emitted.
@override
@mustCallSuper
bool deliverChanges() {
if (_scheduled && hasObservers) {
final changes = _queue == null
? ChangeRecords<C>.any()
: ChangeRecords.wrap(freezeInDevMode(_queue));
_queue = null;
_scheduled = false;
_changes.add(changes);
return true;
}
return false;
}
/// Whether [changes] has at least one active listener.
///
/// May be used to optimize whether to produce change records.
@override
bool get hasObservers => _changes?.hasListener == true;
/// Schedules [change] to be delivered.
///
/// If [change] is omitted then [ChangeRecord.ANY] will be sent.
///
/// If there are no listeners to [changes], this method does nothing.
@override
void notifyChange([C change]) {
if (!hasObservers) {
return;
}
if (change != null) {
(_queue ??= <C>[]).add(change);
}
if (!_scheduled) {
scheduleMicrotask(deliverChanges);
_scheduled = true;
}
}
@Deprecated('Exists to make migrations off Observable easier')
@override
@protected
T notifyPropertyChange<T>(
Symbol field,
T oldValue,
T newValue,
) {
throw UnsupportedError('Not supported by ChangeNotifier');
}
}
/// Supplies property `changes` and various hooks to implement [Observable].
///
/// May use `notifyChange` or `notifyPropertyChange` to queue a property change
/// record; they are asynchronously delivered at the end of the VM turn.
///
/// [PropertyChangeNotifier] may be extended or used as a delegate. To use as
/// a mixin, instead use with [PropertyChangeMixin]:
/// with ChangeNotifier<PropertyChangeRecord>, PropertyChangeMixin
class PropertyChangeNotifier extends ChangeNotifier {
@override
T notifyPropertyChange<T>(
Symbol field,
T oldValue,
T newValue,
) {
if (hasObservers && oldValue != newValue) {
notifyChange(
PropertyChangeRecord<T>(
this,
field,
oldValue,
newValue,
),
);
}
return newValue;
}
}