Add a NotesFolderNotifier

This also implements the ChangeNotifier, but additional provides more
fine grained notifications. We neeed this in order to animate the list
of notes / folders when they change.
This commit is contained in:
Vishesh Handa
2020-02-01 13:14:24 +01:00
parent c91abc9010
commit 2711f1c4c9
2 changed files with 270 additions and 2 deletions

View File

@ -1,13 +1,13 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:path/path.dart' as p;
import 'package:path/path.dart';
import 'note.dart';
import 'note_fs_entity.dart';
import 'notes_folder_notifier.dart';
class NotesFolder with ChangeNotifier implements Comparable<NotesFolder> {
class NotesFolder with NotesFolderNotifier implements Comparable<NotesFolder> {
final NotesFolder parent;
String folderPath;

View File

@ -0,0 +1,268 @@
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<FolderNotificationCallback>();
var _folderRemovedListeners = ObserverList<FolderNotificationCallback>();
var _noteAddedListeners = ObserverList<NoteNotificationCallback>();
var _noteRemovedListeners = ObserverList<NoteNotificationCallback>();
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<FolderNotificationCallback> _listeners,
int index,
NotesFolder folder,
) {
if (_listeners == null) {
return;
}
final localListeners = List<FolderNotificationCallback>.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<ChangeNotifier>(
'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<NoteNotificationCallback> _listeners,
int index,
Note note,
) {
if (_listeners == null) {
return;
}
final localListeners = List<NoteNotificationCallback>.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<ChangeNotifier>(
'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<VoidCallback> _listeners = ObserverList<VoidCallback>();
bool _debugAssertNotDisposed() {
assert(() {
if (_listeners == null) {
throw FlutterError.fromParts(<DiagnosticsNode>[
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<VoidCallback> localListeners =
List<VoidCallback>.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<ChangeNotifier>(
'The $runtimeType sending notification was',
this,
style: DiagnosticsTreeStyle.errorProperty,
);
},
));
}
}
}
}
}