null safety++

This commit is contained in:
Vishesh Handa
2021-05-25 12:57:06 +02:00
parent a8de212430
commit 384c64b1cc
3 changed files with 88 additions and 63 deletions

View File

@ -1,19 +1,27 @@
// @dart=2.9 import 'dart:collection';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
/// A duplicate of the ChangeNotifier class which has the dispose method /// 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 /// 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 { class DisposableChangeNotifier implements Listenable {
ObserverList<VoidCallback> _listeners = ObserverList<VoidCallback>(); LinkedList<_ListenerEntry>? _listeners = LinkedList<_ListenerEntry>();
bool _debugAssertNotDisposed() { bool _debugAssertNotDisposed() {
assert(() { assert(() {
if (_listeners == null) { if (_listeners == null) {
throw FlutterError('A $runtimeType was used after being disposed.\n' throw FlutterError(
'Once you have called dispose() on a $runtimeType, it can no longer be used.'); 'A $runtimeType was used after being disposed.\n'
'Once you have called dispose() on a $runtimeType, it can no longer be used.',
);
} }
return true; return true;
}()); }());
@ -38,16 +46,39 @@ class DisposableChangeNotifier implements Listenable {
@protected @protected
bool get hasListeners { bool get hasListeners {
assert(_debugAssertNotDisposed()); assert(_debugAssertNotDisposed());
return _listeners.isNotEmpty; return _listeners!.isNotEmpty;
} }
/// Register a closure to be called when the object changes. /// 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. /// 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 @override
void addListener(VoidCallback listener) { void addListener(VoidCallback listener) {
assert(_debugAssertNotDisposed()); assert(_debugAssertNotDisposed());
_listeners.add(listener); _listeners!.add(_ListenerEntry(listener));
} }
/// Remove a previously registered closure from the list of closures that are /// 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. /// This method must not be called after [dispose] has been called.
/// ///
/// If a listener had been added twice, and is removed once during an /// {@macro flutter.foundation.ChangeNotifier.addListener}
/// 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 /// See also:
/// listener on two separate objects which are both forwarding all ///
/// registrations to a common upstream object. /// * [addListener], which registers a closure to be called when the object
/// changes.
@override @override
void removeListener(VoidCallback listener) { void removeListener(VoidCallback listener) {
assert(_debugAssertNotDisposed()); 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 /// Discards any resources used by the object. After this is called, the
@ -90,30 +120,30 @@ class DisposableChangeNotifier implements Listenable {
/// Call all the registered listeners. /// Call all the registered listeners.
/// ///
/// Call this method whenever the object changes, to notify any clients the /// Call this method whenever the object changes, to notify any clients the
/// object may have. Listeners that are added during this iteration will not /// object may have changed. Listeners that are added during this iteration
/// be visited. Listeners that are removed during this iteration will not be /// will not be visited. Listeners that are removed during this iteration will
/// visited after they are removed. /// not be visited after they are removed.
/// ///
/// Exceptions thrown by listeners will be caught and reported using /// Exceptions thrown by listeners will be caught and reported using
/// [FlutterError.reportError]. /// [FlutterError.reportError].
/// ///
/// This method must not be called after [dispose] has been called. /// 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. /// in response to a notification) that has been registered multiple times.
/// See the discussion at [removeListener]. /// See the discussion at [removeListener].
@protected @protected
@visibleForTesting @visibleForTesting
void notifyListeners() { void notifyListeners() {
assert(_debugAssertNotDisposed()); assert(_debugAssertNotDisposed());
if (_listeners != null) { if (_listeners!.isEmpty) return;
final List<VoidCallback> localListeners =
List<VoidCallback>.from(_listeners); final List<_ListenerEntry> localListeners =
for (final VoidCallback listener in localListeners) { List<_ListenerEntry>.from(_listeners!);
for (final _ListenerEntry entry in localListeners) {
try { try {
if (_listeners.contains(listener)) { if (entry.list != null) entry.listener();
listener();
}
} catch (exception, stack) { } catch (exception, stack) {
FlutterError.reportError(FlutterErrorDetails( FlutterError.reportError(FlutterErrorDetails(
exception: exception, exception: exception,
@ -133,4 +163,3 @@ class DisposableChangeNotifier implements Listenable {
} }
} }
} }
}

View File

@ -1,5 +1,3 @@
// @dart=2.9
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:gitjournal/core/notes_folder_fs.dart'; import 'package:gitjournal/core/notes_folder_fs.dart';
@ -14,9 +12,9 @@ class FolderTreeView extends StatefulWidget {
final FolderSelectedCallback onFolderEntered; final FolderSelectedCallback onFolderEntered;
FolderTreeView({ FolderTreeView({
Key key, Key? key,
@required this.rootFolder, required this.rootFolder,
@required this.onFolderEntered, required this.onFolderEntered,
this.onFolderSelected = _doNothing, this.onFolderSelected = _doNothing,
this.onFolderUnselected = _doNothing, this.onFolderUnselected = _doNothing,
}) : super(key: key); }) : super(key: key);
@ -29,7 +27,7 @@ class FolderTreeView extends StatefulWidget {
class FolderTreeViewState extends State<FolderTreeView> { class FolderTreeViewState extends State<FolderTreeView> {
bool inSelectionMode = false; bool inSelectionMode = false;
NotesFolderFS selectedFolder; NotesFolderFS? selectedFolder;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -73,13 +71,13 @@ class FolderTile extends StatefulWidget {
final NotesFolderFS folder; final NotesFolderFS folder;
final FolderSelectedCallback onTap; final FolderSelectedCallback onTap;
final FolderSelectedCallback onLongPress; final FolderSelectedCallback onLongPress;
final NotesFolderFS selectedFolder; final NotesFolderFS? selectedFolder;
FolderTile({ FolderTile({
@required this.folder, required this.folder,
@required this.onTap, required this.onTap,
@required this.onLongPress, required this.onLongPress,
@required this.selectedFolder, required this.selectedFolder,
}); });
@override @override
@ -127,7 +125,7 @@ class FolderTileState extends State<FolderTile> {
var publicName = folder.publicName; var publicName = folder.publicName;
if (folder.parent != null) { if (folder.parent != null) {
publicName = publicName.substring(folder.parent.pathSpec().length); publicName = publicName.substring(folder.parent!.pathSpec().length);
if (publicName.startsWith('/')) { if (publicName.startsWith('/')) {
publicName = publicName.substring(1); publicName = publicName.substring(1);
} }
@ -167,7 +165,7 @@ class FolderTileState extends State<FolderTile> {
var children = <FolderTile>[]; var children = <FolderTile>[];
widget.folder.subFolders.forEach((folder) { widget.folder.subFolders.forEach((folder) {
children.add(FolderTile( children.add(FolderTile(
folder: folder, folder: folder as NotesFolderFS,
onTap: widget.onTap, onTap: widget.onTap,
onLongPress: widget.onLongPress, onLongPress: widget.onLongPress,
selectedFolder: widget.selectedFolder, selectedFolder: widget.selectedFolder,

View File

@ -1,5 +1,3 @@
// @dart=2.9
/* /*
Copyright 2020-2021 Vishesh Handa <me@vhanda.in> Copyright 2020-2021 Vishesh Handa <me@vhanda.in>
@ -85,7 +83,7 @@ class NoteEditorSelector extends StatelessWidget {
var selected = et == currentEditor; var selected = et == currentEditor;
var theme = Theme.of(context); var theme = Theme.of(context);
var listTileTheme = ListTileTheme.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, color: selected ? theme.primaryColor : listTileTheme.textColor,
); );