blob: 7d1ec6f4c4f3dd5af22d1f590a3239c7755f3790 [file] [log] [blame]
// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
library observable.src.observable;
import 'dart:async';
import 'dart:collection' show UnmodifiableListView;
import 'package:meta/meta.dart';
import 'change_record.dart' show ChangeRecord;
import 'property_change_record.dart' show PropertyChangeRecord;
/// Represents an object with observable properties. This is used by data in
/// model-view architectures to notify interested parties of [changes] to the
/// object's properties (fields or getter/setter pairs).
///
/// The interface does not require any specific technique to implement
/// observability. You can implement it in the following ways:
///
/// - Deriving from this class via a mixin or base class. When a field,
/// property, or indexable item is changed, the derived class should call
/// [notifyPropertyChange]. See that method for an example.
/// - Implementing this interface and providing your own implementation.
abstract class Observable {
StreamController<List<ChangeRecord>> _changes;
List<ChangeRecord> _records;
/// The stream of property change records to this object, delivered
/// asynchronously.
///
/// [deliverChanges] can be called to force synchronous delivery.
Stream<List<ChangeRecord>> get changes {
if (_changes == null) {
_changes = new StreamController.broadcast(
sync: true, onListen: observed, onCancel: unobserved);
}
return _changes.stream;
}
/// Derived classes may override this method to be called when the [changes]
/// are first observed.
// TODO(tvolkert): @mustCallSuper (github.com/dart-lang/sdk/issues/27275)
@protected
void observed() {}
/// Derived classes may override this method to be called when the [changes]
/// are no longer being observed.
// TODO(tvolkert): @mustCallSuper (github.com/dart-lang/sdk/issues/27275)
@protected
void unobserved() {
// Free some memory
_changes = null;
}
/// True if this object has any observers.
bool get hasObservers => _changes != null && _changes.hasListener;
/// Synchronously deliver pending [changes].
///
/// Returns `true` if any records were delivered, otherwise `false`.
/// Pending records will be cleared regardless, to keep newly added
/// observers from being notified of changes that occurred before
/// they started observing.
bool deliverChanges() {
List<ChangeRecord> records = _records;
_records = null;
if (hasObservers && records != null) {
_changes.add(new UnmodifiableListView<ChangeRecord>(records));
return true;
}
return false;
}
/// Notify that the [field] name of this object has been changed.
///
/// The [oldValue] and [newValue] are also recorded. If the two values are
/// equal, no change will be recorded.
///
/// For convenience this returns [newValue].
/*=T*/ notifyPropertyChange/*<T>*/(
Symbol field, /*=T*/ oldValue, /*=T*/ newValue) {
if (hasObservers && oldValue != newValue) {
notifyChange(new PropertyChangeRecord(this, field, oldValue, newValue));
}
return newValue;
}
/// Notify observers of a change.
///
/// This will automatically schedule [deliverChanges].
///
/// For most objects [Observable.notifyPropertyChange] is more convenient, but
/// collections sometimes deliver other types of changes such as a
/// [MapChangeRecord].
void notifyChange(ChangeRecord record) {
if (!hasObservers) return;
if (_records == null) {
_records = [];
scheduleMicrotask(deliverChanges);
}
_records.add(record);
}
}