memory leak tracking & fixes

This commit is contained in:
Thibault Deckers 2023-10-21 00:31:06 +03:00
parent c5fde95c73
commit 506190f882
34 changed files with 146 additions and 38 deletions

View file

@ -123,7 +123,6 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
_unregisterWidget(widget); _unregisterWidget(widget);
_queryBarFocusNode.dispose(); _queryBarFocusNode.dispose();
_queryFocusRequestNotifier.removeListener(_onQueryFocusRequest); _queryFocusRequestNotifier.removeListener(_onQueryFocusRequest);
_isSelectingNotifier.removeListener(_onActivityChanged);
_isSelectingNotifier.dispose(); _isSelectingNotifier.dispose();
_browseToSelectAnimation.dispose(); _browseToSelectAnimation.dispose();
_subscriptions _subscriptions

View file

@ -302,7 +302,7 @@ class _CollectionSectionedContentState extends State<_CollectionSectionedContent
@override @override
void dispose() { void dispose() {
_appBarHeightNotifier.removeListener(_onAppBarHeightChanged); _appBarHeightNotifier.dispose();
super.dispose(); super.dispose();
} }

View file

@ -29,7 +29,7 @@ class FavouriteToggler extends StatefulWidget {
} }
class _FavouriteTogglerState extends State<FavouriteToggler> { class _FavouriteTogglerState extends State<FavouriteToggler> {
final ValueNotifier<bool> isFavouriteNotifier = ValueNotifier(false); final ValueNotifier<bool> _isFavouriteNotifier = ValueNotifier(false);
Set<AvesEntry> get entries => widget.entries; Set<AvesEntry> get entries => widget.entries;
@ -53,14 +53,14 @@ class _FavouriteTogglerState extends State<FavouriteToggler> {
@override @override
void dispose() { void dispose() {
favourites.removeListener(_onChanged); favourites.removeListener(_onChanged);
isFavouriteNotifier.dispose(); _isFavouriteNotifier.dispose();
super.dispose(); super.dispose();
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ValueListenableBuilder<bool>( return ValueListenableBuilder<bool>(
valueListenable: isFavouriteNotifier, valueListenable: _isFavouriteNotifier,
builder: (context, isFavourite, child) { builder: (context, isFavourite, child) {
if (widget.isMenuItem) { if (widget.isMenuItem) {
return isFavourite return isFavourite
@ -88,7 +88,7 @@ class _FavouriteTogglerState extends State<FavouriteToggler> {
favouriteSweeperIcon, favouriteSweeperIcon,
color: context.select<AvesColorsData, Color>((v) => v.favourite), color: context.select<AvesColorsData, Color>((v) => v.favourite),
), ),
toggledNotifier: isFavouriteNotifier, toggledNotifier: _isFavouriteNotifier,
), ),
], ],
); );
@ -97,7 +97,7 @@ class _FavouriteTogglerState extends State<FavouriteToggler> {
} }
void _onChanged() { void _onChanged() {
isFavouriteNotifier.value = entries.isNotEmpty && entries.every((entry) => entry.isFavourite); _isFavouriteNotifier.value = entries.isNotEmpty && entries.every((entry) => entry.isFavourite);
} }
} }
@ -116,7 +116,7 @@ class FavouriteTogglerCaption extends StatefulWidget {
} }
class _FavouriteTogglerCaptionState extends State<FavouriteTogglerCaption> { class _FavouriteTogglerCaptionState extends State<FavouriteTogglerCaption> {
final ValueNotifier<bool> isFavouriteNotifier = ValueNotifier(false); final ValueNotifier<bool> _isFavouriteNotifier = ValueNotifier(false);
Set<AvesEntry> get entries => widget.entries; Set<AvesEntry> get entries => widget.entries;
@ -136,14 +136,14 @@ class _FavouriteTogglerCaptionState extends State<FavouriteTogglerCaption> {
@override @override
void dispose() { void dispose() {
favourites.removeListener(_onChanged); favourites.removeListener(_onChanged);
isFavouriteNotifier.dispose(); _isFavouriteNotifier.dispose();
super.dispose(); super.dispose();
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ValueListenableBuilder<bool>( return ValueListenableBuilder<bool>(
valueListenable: isFavouriteNotifier, valueListenable: _isFavouriteNotifier,
builder: (context, isFavourite, child) { builder: (context, isFavourite, child) {
return CaptionedButtonText( return CaptionedButtonText(
text: isFavourite ? context.l10n.entryActionRemoveFavourite : context.l10n.entryActionAddFavourite, text: isFavourite ? context.l10n.entryActionRemoveFavourite : context.l10n.entryActionAddFavourite,
@ -154,6 +154,6 @@ class _FavouriteTogglerCaptionState extends State<FavouriteTogglerCaption> {
} }
void _onChanged() { void _onChanged() {
isFavouriteNotifier.value = entries.isNotEmpty && entries.every((entry) => entry.isFavourite); _isFavouriteNotifier.value = entries.isNotEmpty && entries.every((entry) => entry.isFavourite);
} }
} }

View file

@ -391,12 +391,15 @@ mixin EntryStorageMixin on FeedbackMixin, PermissionAwareMixin, SizeAwareMixin {
return dateMillis == null || dateMillis == 0; return dateMillis == null || dateMillis == 0;
}).toSet(); }).toSet();
if (undatedItems.isNotEmpty) { if (undatedItems.isNotEmpty) {
if (!await showSkippableConfirmationDialog( final confirmationDialogDelegate = MoveUndatedConfirmationDialogDelegate();
final confirmed = await showSkippableConfirmationDialog(
context: context, context: context,
type: ConfirmationDialog.moveUndatedItems, type: ConfirmationDialog.moveUndatedItems,
delegate: MoveUndatedConfirmationDialogDelegate(), delegate: confirmationDialogDelegate,
confirmationButtonLabel: context.l10n.continueButtonLabel, confirmationButtonLabel: context.l10n.continueButtonLabel,
)) return false; );
confirmationDialogDelegate.dispose();
if (!confirmed) return false;
if (settings.setMetadataDateBeforeFileOp) { if (settings.setMetadataDateBeforeFileOp) {
final entriesToDate = undatedItems.where((entry) => entry.canEditDate).toSet(); final entriesToDate = undatedItems.where((entry) => entry.canEditDate).toSet();
@ -461,6 +464,10 @@ class MoveUndatedConfirmationDialogDelegate extends ConfirmationDialogDelegate {
_setMetadataDate.value = settings.setMetadataDateBeforeFileOp; _setMetadataDate.value = settings.setMetadataDateBeforeFileOp;
} }
void dispose() {
_setMetadataDate.dispose();
}
@override @override
List<Widget> build(BuildContext context) => [ List<Widget> build(BuildContext context) => [
Padding( Padding(

View file

@ -190,6 +190,7 @@ class _AvesFloatingBarState extends State<AvesFloatingBar> with RouteAware {
@override @override
void dispose() { void dispose() {
AvesApp.pageRouteObserver.unsubscribe(this); AvesApp.pageRouteObserver.unsubscribe(this);
_isBlurAllowedNotifier.dispose();
super.dispose(); super.dispose();
} }

View file

@ -53,6 +53,12 @@ class _ThumbnailHighlightOverlayState extends State<ThumbnailHighlightOverlay> {
static const startAngle = pi * -3 / 4; static const startAngle = pi * -3 / 4;
@override
void dispose() {
_highlightedNotifier.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final highlightInfo = context.watch<HighlightInfo>(); final highlightInfo = context.watch<HighlightInfo>();

View file

@ -36,7 +36,7 @@ class ThumbnailScroller extends StatefulWidget {
} }
class _ThumbnailScrollerState extends State<ThumbnailScroller> { class _ThumbnailScrollerState extends State<ThumbnailScroller> {
final _cancellableNotifier = ValueNotifier(true); final ValueNotifier<bool> _cancellableNotifier = ValueNotifier(true);
late ScrollController _scrollController; late ScrollController _scrollController;
bool _isAnimating = false, _isScrolling = false; bool _isAnimating = false, _isScrolling = false;
@ -71,6 +71,7 @@ class _ThumbnailScrollerState extends State<ThumbnailScroller> {
@override @override
void dispose() { void dispose() {
_unregisterWidget(widget); _unregisterWidget(widget);
_cancellableNotifier.dispose();
super.dispose(); super.dispose();
} }

View file

@ -50,6 +50,7 @@ class _AddShortcutDialogState extends State<AddShortcutDialog> {
@override @override
void dispose() { void dispose() {
_nameController.dispose(); _nameController.dispose();
_isValidNotifier.dispose();
super.dispose(); super.dispose();
} }

View file

@ -119,7 +119,13 @@ class _SkippableConfirmationDialog extends StatefulWidget {
} }
class _SkippableConfirmationDialogState extends State<_SkippableConfirmationDialog> { class _SkippableConfirmationDialogState extends State<_SkippableConfirmationDialog> {
final ValueNotifier<bool> _skip = ValueNotifier(false); final ValueNotifier<bool> _skipNotifier = ValueNotifier(false);
@override
void dispose() {
_skipNotifier.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -127,10 +133,10 @@ class _SkippableConfirmationDialogState extends State<_SkippableConfirmationDial
scrollableContent: [ scrollableContent: [
...widget.delegate.build(context), ...widget.delegate.build(context),
ValueListenableBuilder<bool>( ValueListenableBuilder<bool>(
valueListenable: _skip, valueListenable: _skipNotifier,
builder: (context, flag, child) => SwitchListTile( builder: (context, flag, child) => SwitchListTile(
value: flag, value: flag,
onChanged: (v) => _skip.value = v, onChanged: (v) => _skipNotifier.value = v,
title: Text(context.l10n.doNotAskAgain), title: Text(context.l10n.doNotAskAgain),
), ),
), ),
@ -139,7 +145,7 @@ class _SkippableConfirmationDialogState extends State<_SkippableConfirmationDial
const CancelButton(), const CancelButton(),
TextButton( TextButton(
onPressed: () { onPressed: () {
if (_skip.value) { if (_skipNotifier.value) {
_skipConfirmation(widget.type); _skipConfirmation(widget.type);
} }
Navigator.maybeOf(context)?.pop(true); Navigator.maybeOf(context)?.pop(true);

View file

@ -88,6 +88,8 @@ class _ConvertEntryDialogState extends State<ConvertEntryDialog> {
void dispose() { void dispose() {
_widthController.dispose(); _widthController.dispose();
_heightController.dispose(); _heightController.dispose();
_isValidNotifier.dispose();
_mimeTypeNotifier.dispose();
super.dispose(); super.dispose();
} }

View file

@ -28,6 +28,13 @@ class _DurationDialogState extends State<DurationDialog> {
_seconds = ValueNotifier(seconds % secondsInMinute); _seconds = ValueNotifier(seconds % secondsInMinute);
} }
@override
void dispose() {
_minutes.dispose();
_seconds.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MediaQueryDataProvider( return MediaQueryDataProvider(

View file

@ -60,6 +60,9 @@ class _EditEntryDateDialogState extends State<EditEntryDateDialog> {
@override @override
void dispose() { void dispose() {
_isValidNotifier.dispose(); _isValidNotifier.dispose();
_shiftHour.dispose();
_shiftMinute.dispose();
_shiftSign.dispose();
super.dispose(); super.dispose();
} }

View file

@ -82,6 +82,7 @@ class _EditEntryLocationDialogState extends State<EditEntryLocationDialog> {
void dispose() { void dispose() {
_latitudeController.dispose(); _latitudeController.dispose();
_longitudeController.dispose(); _longitudeController.dispose();
_isValidNotifier.dispose();
super.dispose(); super.dispose();
} }

View file

@ -45,6 +45,12 @@ class _RemoveEntryMetadataDialogState extends State<RemoveEntryMetadataDialog> {
_validate(); _validate();
} }
@override
void dispose() {
_isValidNotifier.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final l10n = context.l10n; final l10n = context.l10n;

View file

@ -37,6 +37,7 @@ class _RenameEntryDialogState extends State<RenameEntryDialog> {
@override @override
void dispose() { void dispose() {
_nameController.dispose(); _nameController.dispose();
_isValidNotifier.dispose();
super.dispose(); super.dispose();
} }

View file

@ -89,6 +89,12 @@ class _AlbumPickPageState extends State<_AlbumPickPage> {
ChipSetAction.createVault, ChipSetAction.createVault,
]; ];
@override
void dispose() {
_appBarHeightNotifier.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ListenableProvider<ValueNotifier<AppMode>>.value( return ListenableProvider<ValueNotifier<AppMode>>.value(

View file

@ -96,8 +96,10 @@ class _ContentState extends State<_Content> with SingleTickerProviderStateMixin
_subscriptions _subscriptions
..forEach((sub) => sub.cancel()) ..forEach((sub) => sub.cancel())
..clear(); ..clear();
_dotLocationNotifier.removeListener(_updateLocationInfo);
_mapController.dispose(); _mapController.dispose();
_isPageAnimatingNotifier.dispose();
_dotLocationNotifier.dispose();
_infoLocationNotifier.dispose();
super.dispose(); super.dispose();
} }

View file

@ -73,6 +73,12 @@ class _TileViewDialogState<S, G, L> extends State<TileViewDialog<S, G, L>> with
_columnMax = columnRange.$2; _columnMax = columnRange.$2;
} }
@override
void dispose() {
_columnCountNotifier.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final l10n = context.l10n; final l10n = context.l10n;

View file

@ -111,7 +111,6 @@ class _FilterGridAppBarState<T extends CollectionFilter, CSAD extends ChipSetAct
void dispose() { void dispose() {
_queryBarFocusNode.dispose(); _queryBarFocusNode.dispose();
_queryFocusRequestNotifier.removeListener(_onQueryFocusRequest); _queryFocusRequestNotifier.removeListener(_onQueryFocusRequest);
_isSelectingNotifier.removeListener(_onActivityChanged);
_isSelectingNotifier.dispose(); _isSelectingNotifier.dispose();
_browseToSelectAnimation.dispose(); _browseToSelectAnimation.dispose();
_subscriptions _subscriptions

View file

@ -99,6 +99,12 @@ class FilterNavigationPage<T extends CollectionFilter, CSAD extends ChipSetActio
class _FilterNavigationPageState<T extends CollectionFilter, CSAD extends ChipSetActionDelegate<T>> extends State<FilterNavigationPage<T, CSAD>> { class _FilterNavigationPageState<T extends CollectionFilter, CSAD extends ChipSetActionDelegate<T>> extends State<FilterNavigationPage<T, CSAD>> {
final ValueNotifier<double> _appBarHeightNotifier = ValueNotifier(0); final ValueNotifier<double> _appBarHeightNotifier = ValueNotifier(0);
@override
void dispose() {
_appBarHeightNotifier.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SelectionProvider<FilterGridItem<T>>( return SelectionProvider<FilterGridItem<T>>(

View file

@ -27,6 +27,12 @@ class _ChipHighlightOverlayState extends State<ChipHighlightOverlay> {
CollectionFilter get filter => widget.filter; CollectionFilter get filter => widget.filter;
@override
void dispose() {
_highlightedNotifier.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final highlightInfo = context.watch<HighlightInfo>(); final highlightInfo = context.watch<HighlightInfo>();

View file

@ -159,12 +159,18 @@ class _ContentState extends State<_Content> with SingleTickerProviderStateMixin
_subscriptions _subscriptions
..forEach((sub) => sub.cancel()) ..forEach((sub) => sub.cancel())
..clear(); ..clear();
_dotEntryNotifier.value?.metadataChangeNotifier.removeListener(_onMarkerEntryMetadataChanged);
_overlayAnimationController.dispose();
_overlayVisible.removeListener(_onOverlayVisibleChanged);
_mapController.dispose(); _mapController.dispose();
_isPageAnimatingNotifier.dispose();
_selectedIndexNotifier.dispose(); _selectedIndexNotifier.dispose();
regionCollection?.dispose(); _regionCollectionNotifier.value?.dispose();
_regionCollectionNotifier.dispose();
_dotLocationNotifier.dispose();
_dotEntryNotifier.value?.metadataChangeNotifier.removeListener(_onMarkerEntryMetadataChanged);
_dotEntryNotifier.dispose();
_overlayOpacityNotifier.dispose();
_overlayVisible.dispose();
_overlayAnimationController.dispose();
// provided collection should be a new instance specifically created // provided collection should be a new instance specifically created
// for the `MapPage` widget, so it can be safely disposed here // for the `MapPage` widget, so it can be safely disposed here
widget.collection.dispose(); widget.collection.dispose();

View file

@ -104,7 +104,13 @@ class _QuickActionEditorBodyState<T extends Object> extends State<QuickActionEdi
@override @override
void dispose() { void dispose() {
_draggedQuickAction.dispose();
_draggedAvailableAction.dispose();
_quickActionHighlight.dispose();
_availableActionHighlight.dispose();
_quickActionsChangeNotifier.dispose(); _quickActionsChangeNotifier.dispose();
_availableActionPageController.dispose();
_stopLeavingTimer(); _stopLeavingTimer();
super.dispose(); super.dispose();
} }

View file

@ -108,6 +108,12 @@ class _StatsPageState extends State<StatsPage> with FeedbackMixin, VaultAwareMix
}); });
} }
@override
void dispose() {
_isPageAnimatingNotifier.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final useTvLayout = settings.useTvLayout; final useTvLayout = settings.useTvLayout;

View file

@ -65,7 +65,7 @@ class ViewerController {
if (kFlutterMemoryAllocationsEnabled) { if (kFlutterMemoryAllocationsEnabled) {
MemoryAllocations.instance.dispatchObjectDisposed(object: this); MemoryAllocations.instance.dispatchObjectDisposed(object: this);
} }
_autopilotNotifier.removeListener(_onAutopilotChanged); _autopilotNotifier.dispose();
_clearAutopilotAnimations(); _clearAutopilotAnimations();
_stopPlayTimer(); _stopPlayTimer();
_streamController.close(); _streamController.close();

View file

@ -61,6 +61,12 @@ class _XmpCardState extends State<XmpCard> {
} }
} }
@override
void dispose() {
_indexNotifier.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final _isIndexed = isIndexed; final _isIndexed = isIndexed;

View file

@ -50,7 +50,8 @@ class _PanoramaPageState extends State<PanoramaPage> {
@override @override
void dispose() { void dispose() {
_overlayVisible.removeListener(_onOverlayVisibleChanged); _overlayVisible.dispose();
_sensorControl.dispose();
super.dispose(); super.dispose();
} }

View file

@ -88,6 +88,7 @@ class _RasterImageViewState extends State<RasterImageView> {
@override @override
void dispose() { void dispose() {
_fullImageLoaded.dispose();
_unregisterFullImage(); _unregisterFullImage();
super.dispose(); super.dispose();
} }

View file

@ -78,6 +78,7 @@ class _VectorImageViewState extends State<VectorImageView> {
@override @override
void dispose() { void dispose() {
_fullImageLoaded.dispose();
_unregisterFullImage(); _unregisterFullImage();
super.dispose(); super.dispose();
} }

View file

@ -119,10 +119,10 @@ class _EntryEditorState extends State<EntryEditor> with EntryViewControllerMixin
@override @override
void dispose() { void dispose() {
cleanEntryControllers(entry); cleanEntryControllers(entry);
_viewerController.dispose(); _overlayVisible.dispose();
_videoActionDelegate.dispose();
_overlayAnimationController.dispose(); _overlayAnimationController.dispose();
_overlayVisible.removeListener(_onOverlayVisibleChanged); _videoActionDelegate.dispose();
_viewerController.dispose();
super.dispose(); super.dispose();
} }

View file

@ -9,16 +9,17 @@ abstract class AvesVideoControllerFactory {
void init(); void init();
AvesVideoController buildController( AvesVideoController buildController(
AvesEntryBase entry, { AvesEntryBase entry, {
required PlaybackStateHandler playbackStateHandler, required PlaybackStateHandler playbackStateHandler,
required VideoSettings settings, required VideoSettings settings,
}); });
} }
abstract class AvesVideoController { abstract class AvesVideoController {
final AvesEntryBase _entry; final AvesEntryBase _entry;
final PlaybackStateHandler playbackStateHandler; final PlaybackStateHandler playbackStateHandler;
final VideoSettings settings; final VideoSettings settings;
bool _disposed = false;
AvesEntryBase get entry => _entry; AvesEntryBase get entry => _entry;
@ -41,6 +42,8 @@ abstract class AvesVideoController {
@mustCallSuper @mustCallSuper
Future<void> dispose() async { Future<void> dispose() async {
assert(!_disposed);
_disposed = true;
if (kFlutterMemoryAllocationsEnabled) { if (kFlutterMemoryAllocationsEnabled) {
MemoryAllocations.instance.dispatchObjectDisposed(object: this); MemoryAllocations.instance.dispatchObjectDisposed(object: this);
} }

View file

@ -80,12 +80,19 @@ class IjkVideoController extends AvesVideoController {
@override @override
Future<void> dispose() async { Future<void> dispose() async {
await super.dispose(); await super.dispose();
_initialPlayTimer?.cancel(); _initialPlayTimer?.cancel();
_stopListening(); _stopListening();
await _valueStreamController.close(); await _valueStreamController.close();
await _timedTextStreamController.close(); await _timedTextStreamController.close();
await _instance.release(); await _instance.release();
_completedNotifier.dispose(); _completedNotifier.dispose();
canCaptureFrameNotifier.dispose();
canMuteNotifier.dispose();
canSetSpeedNotifier.dispose();
canSelectStreamNotifier.dispose();
sarNotifier.dispose();
} }
void _startListening() { void _startListening() {

View file

@ -12,7 +12,7 @@ import 'package:media_kit_video/media_kit_video.dart';
class MpvVideoController extends AvesVideoController { class MpvVideoController extends AvesVideoController {
late Player _instance; late Player _instance;
late VideoStatus _status; late VideoStatus _status;
bool _disposed = false, _firstFrameRendered = false; bool _firstFrameRendered = false;
final ValueNotifier<VideoController?> _controllerNotifier = ValueNotifier(null); final ValueNotifier<VideoController?> _controllerNotifier = ValueNotifier(null);
final List<StreamSubscription> _subscriptions = []; final List<StreamSubscription> _subscriptions = [];
final StreamController<VideoStatus> _statusStreamController = StreamController.broadcast(); final StreamController<VideoStatus> _statusStreamController = StreamController.broadcast();
@ -63,16 +63,21 @@ class MpvVideoController extends AvesVideoController {
@override @override
Future<void> dispose() async { Future<void> dispose() async {
assert(!_disposed);
_disposed = true;
await super.dispose(); await super.dispose();
_stopListening(); _stopListening();
_stopStreamFetchTimer(); _stopStreamFetchTimer();
await _statusStreamController.close(); await _statusStreamController.close();
await _timedTextStreamController.close(); await _timedTextStreamController.close();
await _instance.dispose(); await _instance.dispose();
_controllerNotifier.dispose(); _controllerNotifier.dispose();
_completedNotifier.dispose(); _completedNotifier.dispose();
canCaptureFrameNotifier.dispose();
canMuteNotifier.dispose();
canSetSpeedNotifier.dispose();
canSelectStreamNotifier.dispose();
sarNotifier.dispose();
} }
void _startListening() { void _startListening() {