memory leak tracking & fixes

This commit is contained in:
Thibault Deckers 2023-10-21 18:07:19 +03:00
parent 506190f882
commit e883be1787
22 changed files with 107 additions and 13 deletions

View file

@ -19,6 +19,7 @@ class Query extends ChangeNotifier {
@override @override
void dispose() { void dispose() {
_focusRequestNotifier.dispose(); _focusRequestNotifier.dispose();
_queryNotifier.dispose();
super.dispose(); super.dispose();
} }

View file

@ -37,6 +37,7 @@ class CollectionLens with ChangeNotifier {
bool listenToSource, groupBursts, fixedSort; bool listenToSource, groupBursts, fixedSort;
List<AvesEntry>? fixedSelection; List<AvesEntry>? fixedSelection;
final Set<AvesEntry> _syntheticEntries = {};
List<AvesEntry> _filteredSortedEntries = []; List<AvesEntry> _filteredSortedEntries = [];
Map<SectionKey, List<AvesEntry>> sections = Map.unmodifiable({}); Map<SectionKey, List<AvesEntry>> sections = Map.unmodifiable({});
@ -101,6 +102,7 @@ class CollectionLens with ChangeNotifier {
favourites.removeListener(_onFavouritesChanged); favourites.removeListener(_onFavouritesChanged);
filterChangeNotifier.dispose(); filterChangeNotifier.dispose();
sortSectionChangeNotifier.dispose(); sortSectionChangeNotifier.dispose();
_disposeSyntheticEntries();
super.dispose(); super.dispose();
} }
@ -118,6 +120,11 @@ class CollectionLens with ChangeNotifier {
fixedSelection: fixedSelection ?? this.fixedSelection, fixedSelection: fixedSelection ?? this.fixedSelection,
); );
void _disposeSyntheticEntries() {
_syntheticEntries.forEach((v) => v.dispose());
_syntheticEntries.clear();
}
bool get isEmpty => _filteredSortedEntries.isEmpty; bool get isEmpty => _filteredSortedEntries.isEmpty;
int get entryCount => _filteredSortedEntries.length; int get entryCount => _filteredSortedEntries.length;
@ -182,6 +189,7 @@ class CollectionLens with ChangeNotifier {
void _applyFilters() { void _applyFilters() {
final entries = fixedSelection ?? (filters.contains(TrashFilter.instance) ? source.trashedEntries : source.visibleEntries); final entries = fixedSelection ?? (filters.contains(TrashFilter.instance) ? source.trashedEntries : source.visibleEntries);
_disposeSyntheticEntries();
_filteredSortedEntries = List.of(filters.isEmpty ? entries : entries.where((entry) => filters.every((filter) => filter.test(entry)))); _filteredSortedEntries = List.of(filters.isEmpty ? entries : entries.where((entry) => filters.every((filter) => filter.test(entry))));
if (groupBursts) { if (groupBursts) {
@ -196,6 +204,7 @@ class CollectionLens with ChangeNotifier {
entries.sort(AvesEntrySort.compareByName); entries.sort(AvesEntrySort.compareByName);
final mainEntry = entries.first; final mainEntry = entries.first;
final burstEntry = mainEntry.copyWith(burstEntries: entries); final burstEntry = mainEntry.copyWith(burstEntries: entries);
_syntheticEntries.add(burstEntry);
entries.skip(1).toList().forEach((subEntry) { entries.skip(1).toList().forEach((subEntry) {
_filteredSortedEntries.remove(subEntry); _filteredSortedEntries.remove(subEntry);

View file

@ -41,6 +41,12 @@ class _QueryBarState extends State<QueryBar> {
_controller = TextEditingController(text: queryNotifier.value); _controller = TextEditingController(text: queryNotifier.value);
} }
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final clearButton = IconButton( final clearButton = IconButton(

View file

@ -55,6 +55,12 @@ class _GridScaleGestureDetectorState<T> extends State<GridScaleGestureDetector<T
TileLayout get tileLayout => widget.tileLayout; TileLayout get tileLayout => widget.tileLayout;
@override
void dispose() {
_scaledSizeNotifier?.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final gestureSettings = MediaQuery.gestureSettingsOf(context); final gestureSettings = MediaQuery.gestureSettingsOf(context);
@ -166,6 +172,7 @@ class _GridScaleGestureDetectorState<T> extends State<GridScaleGestureDetector<T
void _onScaleUpdate(ScaleUpdateDetails details) { void _onScaleUpdate(ScaleUpdateDetails details) {
if (_scaledSizeNotifier == null) return; if (_scaledSizeNotifier == null) return;
final s = details.scale; final s = details.scale;
switch (tileLayout) { switch (tileLayout) {
case TileLayout.mosaic: case TileLayout.mosaic:
@ -181,6 +188,10 @@ class _GridScaleGestureDetectorState<T> extends State<GridScaleGestureDetector<T
void _onScaleEnd(ScaleEndDetails details) { void _onScaleEnd(ScaleEndDetails details) {
if (_scaledSizeNotifier == null) return; if (_scaledSizeNotifier == null) return;
final scaledSize = _scaledSizeNotifier!.value;
_scaledSizeNotifier!.dispose();
_scaledSizeNotifier = null;
final overlayEntry = _overlayEntry; final overlayEntry = _overlayEntry;
_overlayEntry = null; _overlayEntry = null;
if (overlayEntry != null) { if (overlayEntry != null) {
@ -196,12 +207,11 @@ class _GridScaleGestureDetectorState<T> extends State<GridScaleGestureDetector<T
switch (tileLayout) { switch (tileLayout) {
case TileLayout.mosaic: case TileLayout.mosaic:
case TileLayout.grid: case TileLayout.grid:
preferredExtent = _scaledSizeNotifier!.value.width; preferredExtent = scaledSize.width;
case TileLayout.list: case TileLayout.list:
preferredExtent = _scaledSizeNotifier!.value.height; preferredExtent = scaledSize.height;
} }
final newExtent = tileExtentController.setUserPreferredExtent(preferredExtent); final newExtent = tileExtentController.setUserPreferredExtent(preferredExtent);
_scaledSizeNotifier = null;
if (newExtent == oldExtent) { if (newExtent == oldExtent) {
_applyingScale = false; _applyingScale = false;
} else { } else {

View file

@ -44,6 +44,7 @@ class _OverlayCoordinateFilterChipState extends State<OverlayCoordinateFilterChi
@override @override
void dispose() { void dispose() {
_unregisterWidget(widget); _unregisterWidget(widget);
_idleBoundsNotifier.dispose();
super.dispose(); super.dispose();
} }

View file

@ -9,6 +9,8 @@ import 'package:flutter/services.dart';
abstract class AvesSearchDelegate extends SearchDelegate { abstract class AvesSearchDelegate extends SearchDelegate {
final String routeName; final String routeName;
final bool canPop; final bool canPop;
final TextEditingController queryTextController = TextEditingController();
final ValueNotifier<SearchBody?> currentBodyNotifier = ValueNotifier(null);
AvesSearchDelegate({ AvesSearchDelegate({
required this.routeName, required this.routeName,
@ -31,6 +33,8 @@ abstract class AvesSearchDelegate extends SearchDelegate {
if (kFlutterMemoryAllocationsEnabled) { if (kFlutterMemoryAllocationsEnabled) {
MemoryAllocations.instance.dispatchObjectDisposed(object: this); MemoryAllocations.instance.dispatchObjectDisposed(object: this);
} }
queryTextController.dispose();
currentBodyNotifier.dispose();
} }
@override @override
@ -113,8 +117,6 @@ abstract class AvesSearchDelegate extends SearchDelegate {
ScrollController? get suggestionsScrollController => null; ScrollController? get suggestionsScrollController => null;
final TextEditingController queryTextController = TextEditingController();
final ProxyAnimation proxyAnimation = ProxyAnimation(kAlwaysDismissedAnimation); final ProxyAnimation proxyAnimation = ProxyAnimation(kAlwaysDismissedAnimation);
@override @override
@ -128,8 +130,6 @@ abstract class AvesSearchDelegate extends SearchDelegate {
} }
} }
final ValueNotifier<SearchBody?> currentBodyNotifier = ValueNotifier(null);
SearchBody? get currentBody => currentBodyNotifier.value; SearchBody? get currentBody => currentBodyNotifier.value;
set currentBody(SearchBody? value) { set currentBody(SearchBody? value) {

View file

@ -26,6 +26,12 @@ class _DebugAndroidAppSectionState extends State<DebugAndroidAppSection> with Au
_loader = appService.getPackages(); _loader = appService.getPackages();
} }
@override
void dispose() {
_queryNotifier.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
super.build(context); super.build(context);

View file

@ -23,6 +23,12 @@ class _DebugAndroidCodecSectionState extends State<DebugAndroidCodecSection> wit
_loader = AndroidDebugService.getCodecs(); _loader = AndroidDebugService.getCodecs();
} }
@override
void dispose() {
_queryNotifier.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
super.build(context); super.build(context);

View file

@ -71,9 +71,15 @@ class _DebugGeneralSectionState extends State<DebugGeneralSection> with Automati
debugPrint(' * report type=$reportType'); debugPrint(' * report type=$reportType');
groupBy(typedReports, (report) => report.trackedClass).forEach((trackedClass, classedReports) { groupBy(typedReports, (report) => report.trackedClass).forEach((trackedClass, classedReports) {
debugPrint(' trackedClass=$trackedClass reports=${classedReports.length}'); debugPrint(' trackedClass=$trackedClass reports=${classedReports.length}');
// classedReports.forEach((report) { classedReports.forEach((report) {
// debugPrint(' phase=${report.phase} retainingPath=${report.retainingPath} detailedPath=${report.detailedPath} context=${report.context}'); final phase = report.phase;
// }); final retainingPath = report.retainingPath;
final detailedPath = report.detailedPath;
final context = report.context;
if (phase != null || retainingPath != null || detailedPath != null || context != null) {
debugPrint(' phase=$phase retainingPath=$retainingPath detailedPath=$detailedPath context=$context');
}
});
}); });
}); });
}); });

View file

@ -34,6 +34,13 @@ class _EditEntryTitleDescriptionDialogState extends State<EditEntryTitleDescript
_descriptionTextController = TextEditingController(text: widget.initialDescription); _descriptionTextController = TextEditingController(text: widget.initialDescription);
} }
@override
void dispose() {
_titleTextController.dispose();
_descriptionTextController.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MediaQueryDataProvider( return MediaQueryDataProvider(

View file

@ -52,7 +52,8 @@ class _RenameEntrySetPageState extends State<RenameEntrySetPage> {
@override @override
void dispose() { void dispose() {
_patternTextController.removeListener(_onUserPatternChanged); _patternTextController.dispose();
_namingPatternNotifier.dispose();
super.dispose(); super.dispose();
} }

View file

@ -54,6 +54,7 @@ class _TagEditorPageState extends State<TagEditorPage> {
@override @override
void dispose() { void dispose() {
_newTagTextController.dispose();
_newTagTextFocusNode.dispose(); _newTagTextFocusNode.dispose();
_expandedSectionNotifier.dispose(); _expandedSectionNotifier.dispose();
super.dispose(); super.dispose();

View file

@ -39,6 +39,7 @@ class _CreateAlbumDialogState extends State<CreateAlbumDialog> {
@override @override
void dispose() { void dispose() {
_scrollController.dispose();
_nameController.dispose(); _nameController.dispose();
_nameFieldFocusNode.removeListener(_onFocus); _nameFieldFocusNode.removeListener(_onFocus);
_nameFieldFocusNode.dispose(); _nameFieldFocusNode.dispose();

View file

@ -28,6 +28,13 @@ class _PasswordDialogState extends State<PasswordDialog> {
_focusNode.requestFocus(); _focusNode.requestFocus();
} }
@override
void dispose() {
_controller.dispose();
_focusNode.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return AvesDialog( return AvesDialog(

View file

@ -22,6 +22,12 @@ class _PinDialogState extends State<PinDialog> {
bool _confirming = false; bool _confirming = false;
String? _firstPin; String? _firstPin;
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme; final colorScheme = Theme.of(context).colorScheme;

View file

@ -37,6 +37,12 @@ class _AppPickPageState extends State<AppPickPage> {
_loader = appService.getPackages(); _loader = appService.getPackages();
} }
@override
void dispose() {
_queryNotifier.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final useTvLayout = settings.useTvLayout; final useTvLayout = settings.useTvLayout;

View file

@ -253,6 +253,12 @@ class _AddressRowState extends State<_AddressRow> {
} }
} }
@override
void dispose() {
_addressLineNotifier.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final textScaleFactor = MediaQuery.textScaleFactorOf(context); final textScaleFactor = MediaQuery.textScaleFactorOf(context);

View file

@ -33,7 +33,7 @@ class HomeWidgetPainter {
ui.ImageByteFormat format = ui.ImageByteFormat.rawRgba, ui.ImageByteFormat format = ui.ImageByteFormat.rawRgba,
}) async { }) async {
final widgetSizePx = Size(widthPx.toDouble(), heightPx.toDouble()); final widgetSizePx = Size(widthPx.toDouble(), heightPx.toDouble());
late final ui.Image? entryImage; final ui.Image? entryImage;
if (entry != null) { if (entry != null) {
final extent = shape.extentPx(widgetSizePx, entry!) / devicePixelRatio; final extent = shape.extentPx(widgetSizePx, entry!) / devicePixelRatio;
entryImage = await _getEntryImage(entry, extent); entryImage = await _getEntryImage(entry, extent);
@ -48,6 +48,7 @@ class HomeWidgetPainter {
canvas.clipPath(path); canvas.clipPath(path);
if (entryImage != null) { if (entryImage != null) {
canvas.drawImage(entryImage, Offset(widgetSizePx.width - entryImage.width, widgetSizePx.height - entryImage.height) / 2, Paint()); canvas.drawImage(entryImage, Offset(widgetSizePx.width - entryImage.width, widgetSizePx.height - entryImage.height) / 2, Paint());
entryImage.dispose();
} else { } else {
canvas.drawPaint(Paint()..shader = backgroundGradient.createShader(rect)); canvas.drawPaint(Paint()..shader = backgroundGradient.createShader(rect));
} }

View file

@ -41,6 +41,12 @@ class _MapAddressRowState extends State<MapAddressRow> {
} }
} }
@override
void dispose() {
_addressLineNotifier.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final textScaleFactor = MediaQuery.textScaleFactorOf(context); final textScaleFactor = MediaQuery.textScaleFactorOf(context);

View file

@ -29,6 +29,12 @@ class _LocaleSelectionPageState extends State<LocaleSelectionPage> {
_selectedValue = settings.locale ?? LocaleTile.systemLocaleOption; _selectedValue = settings.locale ?? LocaleTile.systemLocaleOption;
} }
@override
void dispose() {
_queryNotifier.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final useTvLayout = settings.useTvLayout; final useTvLayout = settings.useTvLayout;

View file

@ -55,6 +55,7 @@ class _MetadataSectionSliverState extends State<MetadataSectionSliver> {
@override @override
void dispose() { void dispose() {
_unregisterWidget(widget); _unregisterWidget(widget);
_expandedDirectoryNotifier.dispose();
super.dispose(); super.dispose();
} }

View file

@ -87,7 +87,7 @@ class _EntryGoogleMapState<T> extends State<EntryGoogleMap<T>> with WidgetsBindi
_unregisterWidget(widget); _unregisterWidget(widget);
_serviceMapController?.dispose(); _serviceMapController?.dispose();
WidgetsBinding.instance.removeObserver(this); WidgetsBinding.instance.removeObserver(this);
_sizeNotifier.removeListener(_onSizeChange); _sizeNotifier.dispose();
super.dispose(); super.dispose();
} }