fix: detach shrink-wrapped editor listeners on dispose
This commit is contained in:
parent
e95e16a2ce
commit
829d4edf2e
2 changed files with 82 additions and 17 deletions
|
|
@ -24,22 +24,13 @@ class EditorScrollController {
|
||||||
// if shrinkWrap is true, we will render the document with Column layout.
|
// if shrinkWrap is true, we will render the document with Column layout.
|
||||||
// otherwise, we will render the document with ScrollablePositionedList.
|
// otherwise, we will render the document with ScrollablePositionedList.
|
||||||
if (shrinkWrap) {
|
if (shrinkWrap) {
|
||||||
void updateVisibleRange() {
|
_updateVisibleRange();
|
||||||
visibleRangeNotifier.value = (
|
editorState.document.root.addListener(_updateVisibleRange);
|
||||||
0,
|
|
||||||
editorState.document.root.children.length - 1,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
updateVisibleRange();
|
|
||||||
editorState.document.root.addListener(updateVisibleRange);
|
|
||||||
|
|
||||||
shouldDisposeScrollController = scrollController == null;
|
shouldDisposeScrollController = scrollController == null;
|
||||||
this.scrollController = scrollController ?? ScrollController();
|
this.scrollController = scrollController ?? ScrollController();
|
||||||
// listen to the scroll offset
|
// listen to the scroll offset
|
||||||
this.scrollController.addListener(
|
this.scrollController.addListener(_updateScrollOffset);
|
||||||
() => offsetNotifier.value = this.scrollController.offset,
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
// listen to the scroll offset
|
// listen to the scroll offset
|
||||||
_scrollOffsetSubscription = _scrollOffsetListener.changes.listen((value) {
|
_scrollOffsetSubscription = _scrollOffsetListener.changes.listen((value) {
|
||||||
|
|
@ -141,13 +132,23 @@ class EditorScrollController {
|
||||||
|
|
||||||
late final StreamSubscription<double> _scrollOffsetSubscription;
|
late final StreamSubscription<double> _scrollOffsetSubscription;
|
||||||
|
|
||||||
|
void _updateVisibleRange() {
|
||||||
|
visibleRangeNotifier.value = (
|
||||||
|
0,
|
||||||
|
editorState.document.root.children.length - 1,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _updateScrollOffset() {
|
||||||
|
offsetNotifier.value = scrollController.offset;
|
||||||
|
}
|
||||||
|
|
||||||
// dispose the subscription
|
// dispose the subscription
|
||||||
void dispose() {
|
void dispose() {
|
||||||
if (shouldDisposeScrollController) {
|
if (shrinkWrap) {
|
||||||
scrollController.dispose();
|
editorState.document.root.removeListener(_updateVisibleRange);
|
||||||
}
|
scrollController.removeListener(_updateScrollOffset);
|
||||||
|
} else {
|
||||||
if (!shrinkWrap) {
|
|
||||||
_scrollOffsetSubscription.cancel();
|
_scrollOffsetSubscription.cancel();
|
||||||
_itemPositionsListener.itemPositions.removeListener(_listenItemPositions);
|
_itemPositionsListener.itemPositions.removeListener(_listenItemPositions);
|
||||||
(_itemPositionsListener as ItemPositionsNotifier?)
|
(_itemPositionsListener as ItemPositionsNotifier?)
|
||||||
|
|
@ -155,6 +156,10 @@ class EditorScrollController {
|
||||||
.dispose();
|
.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (shouldDisposeScrollController) {
|
||||||
|
scrollController.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
offsetNotifier.dispose();
|
offsetNotifier.dispose();
|
||||||
visibleRangeNotifier.dispose();
|
visibleRangeNotifier.dispose();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
testWidgets(
|
||||||
|
'disposing a shrink-wrapped controller detaches document listeners',
|
||||||
|
(tester) async {
|
||||||
|
final editorState = EditorState.blank(withInitialText: false);
|
||||||
|
addTearDown(editorState.dispose);
|
||||||
|
final scrollController = EditorScrollController(
|
||||||
|
editorState: editorState,
|
||||||
|
shrinkWrap: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
scrollController.dispose();
|
||||||
|
editorState.document.insert([0], [paragraphNode()]);
|
||||||
|
|
||||||
|
expect(tester.takeException(), isNull);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
testWidgets(
|
||||||
|
'disposing a shrink-wrapped controller detaches external scroll listeners',
|
||||||
|
(tester) async {
|
||||||
|
final editorState = EditorState.blank(withInitialText: false);
|
||||||
|
final externalScrollController = _TestScrollController();
|
||||||
|
addTearDown(editorState.dispose);
|
||||||
|
addTearDown(externalScrollController.dispose);
|
||||||
|
final scrollController = EditorScrollController(
|
||||||
|
editorState: editorState,
|
||||||
|
shrinkWrap: true,
|
||||||
|
scrollController: externalScrollController,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(externalScrollController.listenerCount, 1);
|
||||||
|
scrollController.dispose();
|
||||||
|
|
||||||
|
expect(externalScrollController.listenerCount, 0);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class _TestScrollController extends ScrollController {
|
||||||
|
final Set<VoidCallback> _listeners = {};
|
||||||
|
|
||||||
|
int get listenerCount => _listeners.length;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void addListener(VoidCallback listener) {
|
||||||
|
_listeners.add(listener);
|
||||||
|
super.addListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void removeListener(VoidCallback listener) {
|
||||||
|
_listeners.remove(listener);
|
||||||
|
super.removeListener(listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue