| part of '../observable_collections.dart'; |
| |
| Atom _observableSetAtom<T>(ReactiveContext context, String? name) => |
| Atom(name: name ?? context.nameFor('ObservableSet<$T>'), context: context); |
| |
| /// ObservableSet provides a reactive set that notifies changes when a member is added or removed. |
| /// |
| /// ```dart |
| /// final set = ObservableSet.of([1, 2, 3]); |
| /// |
| /// const disposer = autorun((_){ |
| /// print(set); |
| /// }); |
| /// |
| /// set.add(4); // prints {1, 2, 3, 4} |
| /// |
| /// ``` |
| class ObservableSet<T> |
| with |
| // ignore:prefer_mixin |
| SetMixin<T> |
| implements |
| Listenable<SetChange<T>> { |
| ObservableSet({ReactiveContext? context, String? name}) |
| : this._(context ?? mainContext, HashSet(), name); |
| |
| ObservableSet.of(Iterable<T> other, {ReactiveContext? context, String? name}) |
| : this._(context ?? mainContext, HashSet.of(other), name); |
| |
| ObservableSet.linkedHashSetFrom(Iterable<T> other, |
| {bool Function(T, T)? equals, |
| int Function(T)? hashCode, |
| // ignore:avoid_annotating_with_dynamic |
| bool Function(dynamic)? isValidKey, |
| ReactiveContext? context, |
| String? name}) |
| : this._( |
| context ?? mainContext, |
| // ignore: prefer_collection_literals |
| LinkedHashSet( |
| equals: equals, hashCode: hashCode, isValidKey: isValidKey) |
| ..addAll(other), |
| name); |
| |
| ObservableSet.splayTreeSetFrom(Iterable<T> other, |
| {int Function(T, T)? compare, |
| // ignore:avoid_annotating_with_dynamic |
| bool Function(dynamic)? isValidKey, |
| ReactiveContext? context, |
| String? name}) |
| : this._(context ?? mainContext, |
| SplayTreeSet.of(other, compare, isValidKey), name); |
| |
| ObservableSet._wrap(this._context, this._atom, this._set); |
| |
| ObservableSet._(this._context, Set<T> wrapped, String? name) |
| : _atom = _observableSetAtom(_context, name), |
| _set = wrapped; |
| |
| final ReactiveContext _context; |
| final Atom _atom; |
| final Set<T> _set; |
| |
| String get name => _atom.name; |
| |
| Listeners<SetChange<T>>? _listenersField; |
| |
| Listeners<SetChange<T>> get _listeners => |
| _listenersField ??= Listeners(_context); |
| |
| bool get _hasListeners => |
| _listenersField != null && _listenersField!.hasHandlers; |
| |
| @override |
| bool add(T value) { |
| var result = false; |
| |
| _context.conditionallyRunInAction(() { |
| result = _set.add(value); |
| |
| if (result && _hasListeners) { |
| _reportAdd(value); |
| } |
| |
| if (result) { |
| _atom.reportChanged(); |
| } |
| }, _atom); |
| |
| return result; |
| } |
| |
| @override |
| bool contains(Object? element) { |
| _context.enforceReadPolicy(_atom); |
| |
| _atom.reportObserved(); |
| return _set.contains(element); |
| } |
| |
| @override |
| Iterator<T> get iterator => ObservableIterator(_atom, _set.iterator); |
| |
| @override |
| int get length { |
| _context.enforceReadPolicy(_atom); |
| |
| _atom.reportObserved(); |
| return _set.length; |
| } |
| |
| @override |
| T? lookup(Object? element) { |
| _context.enforceReadPolicy(_atom); |
| |
| _atom.reportObserved(); |
| return _set.lookup(element); |
| } |
| |
| @override |
| bool remove(Object? value) { |
| var removed = false; |
| |
| _context.conditionallyRunInAction(() { |
| removed = _set.remove(value); |
| |
| if (removed && _hasListeners) { |
| _reportRemove(value as T?); |
| } |
| |
| if (removed) { |
| _atom.reportChanged(); |
| } |
| }, _atom); |
| |
| return removed; |
| } |
| |
| @override |
| void clear() { |
| _context.conditionallyRunInAction(() { |
| if (_hasListeners) { |
| final items = _set.toList(growable: false); |
| _set.clear(); |
| items.forEach(_reportRemove); |
| } else { |
| _set.clear(); |
| } |
| _atom.reportChanged(); |
| }, _atom); |
| } |
| |
| @override |
| Set<R> cast<R>() => ObservableSet<R>._wrap(_context, _atom, _set.cast<R>()); |
| |
| @override |
| Set<T> toSet() { |
| _context.enforceReadPolicy(_atom); |
| |
| _atom.reportObserved(); |
| return Set.from(_set); |
| } |
| |
| /// Attaches a listener to changes happening in the [ObservableSet]. You have |
| /// the option to be notified immediately ([fireImmediately]) or wait for until the first change. |
| @override |
| Dispose observe(SetChangeListener<T> listener, |
| {bool fireImmediately = false}) { |
| final dispose = _listeners.add(listener); |
| if (fireImmediately == true) { |
| _set.forEach(_reportAdd); |
| } |
| return dispose; |
| } |
| |
| void _reportAdd(T value) { |
| _listeners.notifyListeners(SetChange( |
| object: this, |
| type: OperationType.add, |
| value: value, |
| )); |
| } |
| |
| void _reportRemove(T? value) { |
| _listeners.notifyListeners(SetChange( |
| object: this, |
| type: OperationType.remove, |
| value: value, |
| )); |
| } |
| } |
| |
| /// A convenience method used during unit testing. It creates an [ObservableSet] with a custom instance |
| /// of an [Atom] |
| @visibleForTesting |
| ObservableSet<T> wrapInObservableSet<T>(Atom atom, Set<T> set) => |
| ObservableSet._wrap(mainContext, atom, set); |
| |
| /// An internal iterator used to ensure that every read is tracked as part of the |
| /// MobX reactivity system. |
| /// |
| /// It does this be keeping an instance of an [Atom] and calling the [Atom.reportObserved] |
| /// method for every read. |
| class ObservableIterator<T> implements Iterator<T> { |
| ObservableIterator(this._atom, this._iterator); |
| |
| final Iterator<T> _iterator; |
| final Atom _atom; |
| |
| @override |
| T get current { |
| _atom.context.enforceReadPolicy(_atom); |
| |
| _atom.reportObserved(); |
| return _iterator.current; |
| } |
| |
| @override |
| bool moveNext() { |
| _atom.context.enforceReadPolicy(_atom); |
| |
| _atom.reportObserved(); |
| return _iterator.moveNext(); |
| } |
| } |
| |
| typedef SetChangeListener<T> = void Function(SetChange<T>); |
| |
| /// Capture the change related information for an [ ObservableSet]. This is used |
| /// as the notification instance. |
| class SetChange<T> { |
| SetChange({ |
| required this.object, |
| required this.type, |
| required this.value, |
| }); |
| |
| final ObservableSet<T> object; |
| final OperationType type; |
| final T? value; |
| } |