From 384c64b1cc143479c487c67cf1cb90a9e69ee9cd Mon Sep 17 00:00:00 2001
From: Vishesh Handa <me@vhanda.in>
Date: Tue, 25 May 2021 12:57:06 +0200
Subject: [PATCH] null safety++

---
 lib/editors/disposable_change_notifier.dart | 123 ++++++++++++--------
 lib/widgets/folder_tree_view.dart           |  24 ++--
 lib/widgets/note_editor_selector.dart       |   4 +-
 3 files changed, 88 insertions(+), 63 deletions(-)

diff --git a/lib/editors/disposable_change_notifier.dart b/lib/editors/disposable_change_notifier.dart
index ba8e8f5f..7e1f922e 100644
--- a/lib/editors/disposable_change_notifier.dart
+++ b/lib/editors/disposable_change_notifier.dart
@@ -1,19 +1,27 @@
-// @dart=2.9
+import 'dart:collection';
 
 import 'package:flutter/foundation.dart';
 import 'package:flutter/material.dart';
 
 /// A duplicate of the ChangeNotifier class which has the dispose method
-/// renamed to disposeListeners. This is done so that it can be used
+/// renamed to disposeListenables. This is done so that it can be used
 /// as a mixin with a State class which also has a dispose method
+
+class _ListenerEntry extends LinkedListEntry<_ListenerEntry> {
+  _ListenerEntry(this.listener);
+  final VoidCallback listener;
+}
+
 class DisposableChangeNotifier implements Listenable {
-  ObserverList<VoidCallback> _listeners = ObserverList<VoidCallback>();
+  LinkedList<_ListenerEntry>? _listeners = LinkedList<_ListenerEntry>();
 
   bool _debugAssertNotDisposed() {
     assert(() {
       if (_listeners == null) {
-        throw FlutterError('A $runtimeType was used after being disposed.\n'
-            'Once you have called dispose() on a $runtimeType, it can no longer be used.');
+        throw FlutterError(
+          'A $runtimeType was used after being disposed.\n'
+          'Once you have called dispose() on a $runtimeType, it can no longer be used.',
+        );
       }
       return true;
     }());
@@ -38,16 +46,39 @@ class DisposableChangeNotifier implements Listenable {
   @protected
   bool get hasListeners {
     assert(_debugAssertNotDisposed());
-    return _listeners.isNotEmpty;
+    return _listeners!.isNotEmpty;
   }
 
   /// Register a closure to be called when the object changes.
   ///
+  /// If the given closure is already registered, an additional instance is
+  /// added, and must be removed the same number of times it is added before it
+  /// will stop being called.
+  ///
   /// This method must not be called after [dispose] has been called.
+  ///
+  /// {@template flutter.foundation.ChangeNotifier.addListener}
+  /// If a listener is added twice, and is removed once during an iteration
+  /// (e.g. 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, therefore it will conservatively still
+  /// call 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.
+  /// {@endtemplate}
+  ///
+  /// See also:
+  ///
+  ///  * [removeListener], which removes a previously registered closure from
+  ///    the list of closures that are notified when the object changes.
   @override
   void addListener(VoidCallback listener) {
     assert(_debugAssertNotDisposed());
-    _listeners.add(listener);
+    _listeners!.add(_ListenerEntry(listener));
   }
 
   /// Remove a previously registered closure from the list of closures that are
@@ -57,22 +88,21 @@ class DisposableChangeNotifier implements Listenable {
   ///
   /// 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.
+  /// {@macro flutter.foundation.ChangeNotifier.addListener}
   ///
-  /// 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.
+  /// See also:
+  ///
+  ///  * [addListener], which registers a closure to be called when the object
+  ///    changes.
   @override
   void removeListener(VoidCallback listener) {
     assert(_debugAssertNotDisposed());
-    _listeners.remove(listener);
+    for (final _ListenerEntry entry in _listeners!) {
+      if (entry.listener == listener) {
+        entry.unlink();
+        return;
+      }
+    }
   }
 
   /// Discards any resources used by the object. After this is called, the
@@ -90,46 +120,45 @@ class DisposableChangeNotifier implements Listenable {
   /// 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.
+  /// object may have changed. 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.
+  /// Surprising behavior can result when reentrantly removing a listener (e.g.
   /// in response to a notification) that has been registered multiple times.
   /// See the discussion at [removeListener].
   @protected
   @visibleForTesting
   void notifyListeners() {
     assert(_debugAssertNotDisposed());
-    if (_listeners != null) {
-      final List<VoidCallback> localListeners =
-          List<VoidCallback>.from(_listeners);
-      for (final 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<DisposableChangeNotifier>(
-                'The $runtimeType sending notification was',
-                this,
-                style: DiagnosticsTreeStyle.errorProperty,
-              );
-            },
-          ));
-        }
+    if (_listeners!.isEmpty) return;
+
+    final List<_ListenerEntry> localListeners =
+        List<_ListenerEntry>.from(_listeners!);
+
+    for (final _ListenerEntry entry in localListeners) {
+      try {
+        if (entry.list != null) entry.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<DisposableChangeNotifier>(
+              'The $runtimeType sending notification was',
+              this,
+              style: DiagnosticsTreeStyle.errorProperty,
+            );
+          },
+        ));
       }
     }
   }
diff --git a/lib/widgets/folder_tree_view.dart b/lib/widgets/folder_tree_view.dart
index a8f6997e..b3c29326 100644
--- a/lib/widgets/folder_tree_view.dart
+++ b/lib/widgets/folder_tree_view.dart
@@ -1,5 +1,3 @@
-// @dart=2.9
-
 import 'package:flutter/material.dart';
 
 import 'package:gitjournal/core/notes_folder_fs.dart';
@@ -14,9 +12,9 @@ class FolderTreeView extends StatefulWidget {
   final FolderSelectedCallback onFolderEntered;
 
   FolderTreeView({
-    Key key,
-    @required this.rootFolder,
-    @required this.onFolderEntered,
+    Key? key,
+    required this.rootFolder,
+    required this.onFolderEntered,
     this.onFolderSelected = _doNothing,
     this.onFolderUnselected = _doNothing,
   }) : super(key: key);
@@ -29,7 +27,7 @@ class FolderTreeView extends StatefulWidget {
 
 class FolderTreeViewState extends State<FolderTreeView> {
   bool inSelectionMode = false;
-  NotesFolderFS selectedFolder;
+  NotesFolderFS? selectedFolder;
 
   @override
   Widget build(BuildContext context) {
@@ -73,13 +71,13 @@ class FolderTile extends StatefulWidget {
   final NotesFolderFS folder;
   final FolderSelectedCallback onTap;
   final FolderSelectedCallback onLongPress;
-  final NotesFolderFS selectedFolder;
+  final NotesFolderFS? selectedFolder;
 
   FolderTile({
-    @required this.folder,
-    @required this.onTap,
-    @required this.onLongPress,
-    @required this.selectedFolder,
+    required this.folder,
+    required this.onTap,
+    required this.onLongPress,
+    required this.selectedFolder,
   });
 
   @override
@@ -127,7 +125,7 @@ class FolderTileState extends State<FolderTile> {
 
     var publicName = folder.publicName;
     if (folder.parent != null) {
-      publicName = publicName.substring(folder.parent.pathSpec().length);
+      publicName = publicName.substring(folder.parent!.pathSpec().length);
       if (publicName.startsWith('/')) {
         publicName = publicName.substring(1);
       }
@@ -167,7 +165,7 @@ class FolderTileState extends State<FolderTile> {
     var children = <FolderTile>[];
     widget.folder.subFolders.forEach((folder) {
       children.add(FolderTile(
-        folder: folder,
+        folder: folder as NotesFolderFS,
         onTap: widget.onTap,
         onLongPress: widget.onLongPress,
         selectedFolder: widget.selectedFolder,
diff --git a/lib/widgets/note_editor_selector.dart b/lib/widgets/note_editor_selector.dart
index 330fbfa1..63350365 100644
--- a/lib/widgets/note_editor_selector.dart
+++ b/lib/widgets/note_editor_selector.dart
@@ -1,5 +1,3 @@
-// @dart=2.9
-
 /*
 Copyright 2020-2021 Vishesh Handa <me@vhanda.in>
 
@@ -85,7 +83,7 @@ class NoteEditorSelector extends StatelessWidget {
     var selected = et == currentEditor;
     var theme = Theme.of(context);
     var listTileTheme = ListTileTheme.of(context);
-    var textStyle = theme.textTheme.bodyText2.copyWith(
+    var textStyle = theme.textTheme.bodyText2!.copyWith(
       color: selected ? theme.primaryColor : listTileTheme.textColor,
     );