import 'package:flutter/material.dart'; import 'package:flutter/foundation.dart'; import 'note.dart'; import 'notes_folder.dart'; typedef FolderNotificationCallback = void Function( int index, NotesFolder folder); typedef NoteNotificationCallback = void Function(int index, Note note); class NotesFolderNotifier implements ChangeNotifier { var _folderAddedListeners = ObserverList(); var _folderRemovedListeners = ObserverList(); var _noteAddedListeners = ObserverList(); var _noteRemovedListeners = ObserverList(); void addFolderRemovedListener(FolderNotificationCallback listener) { _folderRemovedListeners.add(listener); } void removeFolderRemovedListener(FolderNotificationCallback listener) { _folderRemovedListeners.remove(listener); } void addFolderAddedListener(FolderNotificationCallback listener) { _folderAddedListeners.add(listener); } void removeFolderAddedListener(FolderNotificationCallback listener) { _folderAddedListeners.remove(listener); } void addNoteAddedListener(NoteNotificationCallback listener) { _noteAddedListeners.add(listener); } void removeNoteAddedListener(NoteNotificationCallback listener) { _noteAddedListeners.remove(listener); } void addNoteRemovedListener(NoteNotificationCallback listener) { _noteRemovedListeners.add(listener); } void removeNoteRemovedListener(NoteNotificationCallback listener) { _noteRemovedListeners.remove(listener); } @mustCallSuper @override void dispose() { _folderAddedListeners = null; _folderRemovedListeners = null; _noteAddedListeners = null; _noteRemovedListeners = null; assert(_debugAssertNotDisposed()); _listeners = null; } void _notifyFolderCallback( ObserverList _listeners, int index, NotesFolder folder, ) { if (_listeners == null) { return; } final localListeners = List.from(_listeners); for (var listener in localListeners) { try { if (_listeners.contains(listener)) { listener(index, folder); } } catch (exception, stack) { FlutterError.reportError(FlutterErrorDetails( exception: exception, stack: stack, library: 'GitJournal', context: ErrorDescription( 'while dispatching notifications for $runtimeType'), informationCollector: () sync* { yield DiagnosticsProperty( 'The $runtimeType sending notification was', this, style: DiagnosticsTreeStyle.errorProperty, ); }, )); } } notifyListeners(); } void notifyFolderAdded(int index, NotesFolder folder) { _notifyFolderCallback(_folderAddedListeners, index, folder); } void notifyFolderRemoved(int index, NotesFolder folder) { _notifyFolderCallback(_folderRemovedListeners, index, folder); } void _notifyNoteCallback( ObserverList _listeners, int index, Note note, ) { if (_listeners == null) { return; } final localListeners = List.from(_listeners); for (var listener in localListeners) { try { if (_listeners.contains(listener)) { listener(index, note); } } catch (exception, stack) { FlutterError.reportError(FlutterErrorDetails( exception: exception, stack: stack, library: 'GitJournal', context: ErrorDescription( 'while dispatching notifications for $runtimeType'), informationCollector: () sync* { yield DiagnosticsProperty( 'The $runtimeType sending notification was', this, style: DiagnosticsTreeStyle.errorProperty, ); }, )); } } notifyListeners(); } void notifyNoteAdded(int index, Note note) { _notifyNoteCallback(_noteAddedListeners, index, note); } void notifyNoteRemoved(int index, Note note) { _notifyNoteCallback(_noteRemovedListeners, index, note); } // // ChangeNotifier implementation - How to not duplicate this? // ObserverList _listeners = ObserverList(); bool _debugAssertNotDisposed() { assert(() { if (_listeners == null) { throw FlutterError.fromParts([ ErrorSummary('A $runtimeType was used after being disposed.'), ErrorDescription( 'Once you have called dispose() on a $runtimeType, it can no longer be used.') ]); } return true; }()); return true; } /// Whether any listeners are currently registered. /// /// Clients should not depend on this value for their behavior, because having /// one listener's logic change when another listener happens to start or stop /// listening will lead to extremely hard-to-track bugs. Subclasses might use /// this information to determine whether to do any work when there are no /// listeners, however; for example, resuming a [Stream] when a listener is /// added and pausing it when a listener is removed. /// /// Typically this is used by overriding [addListener], checking if /// [hasListeners] is false before calling `super.addListener()`, and if so, /// starting whatever work is needed to determine when to call /// [notifyListeners]; and similarly, by overriding [removeListener], checking /// if [hasListeners] is false after calling `super.removeListener()`, and if /// so, stopping that same work. @protected @override bool get hasListeners { assert(_debugAssertNotDisposed()); return _listeners.isNotEmpty; } /// Register a closure to be called when the object changes. /// /// This method must not be called after [dispose] has been called. @override void addListener(VoidCallback listener) { assert(_debugAssertNotDisposed()); _listeners.add(listener); } /// Remove a previously registered closure from the list of closures that are /// notified when the object changes. /// /// If the given listener is not registered, the call is ignored. /// /// This method must not be called after [dispose] has been called. /// /// If a listener had been added twice, and is removed once during an /// iteration (i.e. in response to a notification), it will still be called /// again. If, on the other hand, it is removed as many times as it was /// registered, then it will no longer be called. This odd behavior is the /// result of the [ChangeNotifier] not being able to determine which listener /// is being removed, since they are identical, and therefore conservatively /// still calling all the listeners when it knows that any are still /// registered. /// /// This surprising behavior can be unexpectedly observed when registering a /// listener on two separate objects which are both forwarding all /// registrations to a common upstream object. @override void removeListener(VoidCallback listener) { assert(_debugAssertNotDisposed()); _listeners.remove(listener); } /// Call all the registered listeners. /// /// Call this method whenever the object changes, to notify any clients the /// object may have. Listeners that are added during this iteration will not /// be visited. Listeners that are removed during this iteration will not be /// visited after they are removed. /// /// Exceptions thrown by listeners will be caught and reported using /// [FlutterError.reportError]. /// /// This method must not be called after [dispose] has been called. /// /// Surprising behavior can result when reentrantly removing a listener (i.e. /// in response to a notification) that has been registered multiple times. /// See the discussion at [removeListener]. @protected @visibleForTesting @override void notifyListeners() { assert(_debugAssertNotDisposed()); if (_listeners != null) { final List localListeners = List.from(_listeners); for (VoidCallback listener in localListeners) { try { if (_listeners.contains(listener)) { listener(); } } catch (exception, stack) { FlutterError.reportError(FlutterErrorDetails( exception: exception, stack: stack, library: 'foundation library', context: ErrorDescription( 'while dispatching notifications for $runtimeType'), informationCollector: () sync* { yield DiagnosticsProperty( 'The $runtimeType sending notification was', this, style: DiagnosticsTreeStyle.errorProperty, ); }, )); } } } } }