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);
_queryBarFocusNode.dispose();
_queryFocusRequestNotifier.removeListener(_onQueryFocusRequest);
_isSelectingNotifier.removeListener(_onActivityChanged);
_isSelectingNotifier.dispose();
_browseToSelectAnimation.dispose();
_subscriptions

View file

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

View file

@ -29,7 +29,7 @@ class FavouriteToggler extends StatefulWidget {
}
class _FavouriteTogglerState extends State<FavouriteToggler> {
final ValueNotifier<bool> isFavouriteNotifier = ValueNotifier(false);
final ValueNotifier<bool> _isFavouriteNotifier = ValueNotifier(false);
Set<AvesEntry> get entries => widget.entries;
@ -53,14 +53,14 @@ class _FavouriteTogglerState extends State<FavouriteToggler> {
@override
void dispose() {
favourites.removeListener(_onChanged);
isFavouriteNotifier.dispose();
_isFavouriteNotifier.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return ValueListenableBuilder<bool>(
valueListenable: isFavouriteNotifier,
valueListenable: _isFavouriteNotifier,
builder: (context, isFavourite, child) {
if (widget.isMenuItem) {
return isFavourite
@ -88,7 +88,7 @@ class _FavouriteTogglerState extends State<FavouriteToggler> {
favouriteSweeperIcon,
color: context.select<AvesColorsData, Color>((v) => v.favourite),
),
toggledNotifier: isFavouriteNotifier,
toggledNotifier: _isFavouriteNotifier,
),
],
);
@ -97,7 +97,7 @@ class _FavouriteTogglerState extends State<FavouriteToggler> {
}
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> {
final ValueNotifier<bool> isFavouriteNotifier = ValueNotifier(false);
final ValueNotifier<bool> _isFavouriteNotifier = ValueNotifier(false);
Set<AvesEntry> get entries => widget.entries;
@ -136,14 +136,14 @@ class _FavouriteTogglerCaptionState extends State<FavouriteTogglerCaption> {
@override
void dispose() {
favourites.removeListener(_onChanged);
isFavouriteNotifier.dispose();
_isFavouriteNotifier.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return ValueListenableBuilder<bool>(
valueListenable: isFavouriteNotifier,
valueListenable: _isFavouriteNotifier,
builder: (context, isFavourite, child) {
return CaptionedButtonText(
text: isFavourite ? context.l10n.entryActionRemoveFavourite : context.l10n.entryActionAddFavourite,
@ -154,6 +154,6 @@ class _FavouriteTogglerCaptionState extends State<FavouriteTogglerCaption> {
}
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;
}).toSet();
if (undatedItems.isNotEmpty) {
if (!await showSkippableConfirmationDialog(
final confirmationDialogDelegate = MoveUndatedConfirmationDialogDelegate();
final confirmed = await showSkippableConfirmationDialog(
context: context,
type: ConfirmationDialog.moveUndatedItems,
delegate: MoveUndatedConfirmationDialogDelegate(),
delegate: confirmationDialogDelegate,
confirmationButtonLabel: context.l10n.continueButtonLabel,
)) return false;
);
confirmationDialogDelegate.dispose();
if (!confirmed) return false;
if (settings.setMetadataDateBeforeFileOp) {
final entriesToDate = undatedItems.where((entry) => entry.canEditDate).toSet();
@ -461,6 +464,10 @@ class MoveUndatedConfirmationDialogDelegate extends ConfirmationDialogDelegate {
_setMetadataDate.value = settings.setMetadataDateBeforeFileOp;
}
void dispose() {
_setMetadataDate.dispose();
}
@override
List<Widget> build(BuildContext context) => [
Padding(

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -111,7 +111,6 @@ class _FilterGridAppBarState<T extends CollectionFilter, CSAD extends ChipSetAct
void dispose() {
_queryBarFocusNode.dispose();
_queryFocusRequestNotifier.removeListener(_onQueryFocusRequest);
_isSelectingNotifier.removeListener(_onActivityChanged);
_isSelectingNotifier.dispose();
_browseToSelectAnimation.dispose();
_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>> {
final ValueNotifier<double> _appBarHeightNotifier = ValueNotifier(0);
@override
void dispose() {
_appBarHeightNotifier.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return SelectionProvider<FilterGridItem<T>>(

View file

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

View file

@ -159,12 +159,18 @@ class _ContentState extends State<_Content> with SingleTickerProviderStateMixin
_subscriptions
..forEach((sub) => sub.cancel())
..clear();
_dotEntryNotifier.value?.metadataChangeNotifier.removeListener(_onMarkerEntryMetadataChanged);
_overlayAnimationController.dispose();
_overlayVisible.removeListener(_onOverlayVisibleChanged);
_mapController.dispose();
_isPageAnimatingNotifier.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
// for the `MapPage` widget, so it can be safely disposed here
widget.collection.dispose();

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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