diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 88e8f7d3d5d..160f33fd550 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -74,7 +74,7 @@ jobs: wget -qO- https://dcm.dev/pgp-key.public | sudo gpg --dearmor -o /usr/share/keyrings/dcm.gpg echo 'deb [signed-by=/usr/share/keyrings/dcm.gpg arch=amd64] https://dcm.dev/debian stable main' | sudo tee /etc/apt/sources.list.d/dart_stable.list sudo apt-get update - sudo apt-get install dcm=1.36.0-1 # To avoid errors add `-1` (build number) to the version + sudo apt-get install dcm=1.38.0-1 # To avoid errors add `-1` (build number) to the version sudo chmod +x /usr/bin/dcm echo "$(dcm --version)" - name: Setup Dart SDK diff --git a/analysis_options.yaml b/analysis_options.yaml index f219cfddeae..f59d8d0ede5 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -186,6 +186,8 @@ dart_code_metrics: - avoid-explicit-type-declaration # - ban-name # TODO(polina-c): add configuration # - binary-expression-operand-order Some nice catches but too many false positives to enable. + - dispose-class-fields + - dispose-fields - double-literal-format # - format-comment TODO(jacobr): enable this one after fixing violations. # TODO(jacobr): enable member-ordering. This catches a bunch of real style diff --git a/packages/devtools_app/lib/src/app.dart b/packages/devtools_app/lib/src/app.dart index d2661868b1b..272f1fc06cd 100644 --- a/packages/devtools_app/lib/src/app.dart +++ b/packages/devtools_app/lib/src/app.dart @@ -202,6 +202,7 @@ class DevToolsAppState extends State with AutoDisposeMixin { FrameworkCore.dispose(); // Workaround for https://github.com/flutter/flutter/issues/155265. removeTextFieldFocusFixHandler(); + routerDelegate.dispose(); super.dispose(); } diff --git a/packages/devtools_app/lib/src/extensions/embedded/_view_web.dart b/packages/devtools_app/lib/src/extensions/embedded/_view_web.dart index 3135be38703..79017530a1b 100644 --- a/packages/devtools_app/lib/src/extensions/embedded/_view_web.dart +++ b/packages/devtools_app/lib/src/extensions/embedded/_view_web.dart @@ -46,6 +46,7 @@ class _EmbeddedExtensionState extends State @override void dispose() { + _embeddedExtensionController.dispose(); iFrameController.dispose(); super.dispose(); } diff --git a/packages/devtools_app/lib/src/framework/notifications_view.dart b/packages/devtools_app/lib/src/framework/notifications_view.dart index 83c5b69a6d3..e3c2331ae27 100644 --- a/packages/devtools_app/lib/src/framework/notifications_view.dart +++ b/packages/devtools_app/lib/src/framework/notifications_view.dart @@ -86,7 +86,10 @@ class _NotificationsState extends State<_Notifications> with AutoDisposeMixin { @override void dispose() { - _overlayEntry!.remove(); + _overlayEntry + ?..remove() + ..dispose(); + _overlayEntry = null; super.dispose(); } @@ -218,6 +221,7 @@ class _NotificationState extends State<_Notification> void dispose() { controller.dispose(); _dismissTimer?.cancel(); + curve.dispose(); super.dispose(); } diff --git a/packages/devtools_app/lib/src/screens/debugger/debugger_controller.dart b/packages/devtools_app/lib/src/screens/debugger/debugger_controller.dart index aacc0ec31a1..485fed02bd8 100644 --- a/packages/devtools_app/lib/src/screens/debugger/debugger_controller.dart +++ b/packages/devtools_app/lib/src/screens/debugger/debugger_controller.dart @@ -89,6 +89,8 @@ class DebuggerController extends DevToolsScreenController _selectedBreakpoint.dispose(); _exceptionPauseMode.dispose(); _hasTruncatedFrames.dispose(); + unawaited(_getStackOperation?.cancel()); + _lastService = null; super.dispose(); } diff --git a/packages/devtools_app/lib/src/screens/deep_link_validation/deep_link_list_view.dart b/packages/devtools_app/lib/src/screens/deep_link_validation/deep_link_list_view.dart index 75a90841656..7770cb06b00 100644 --- a/packages/devtools_app/lib/src/screens/deep_link_validation/deep_link_list_view.dart +++ b/packages/devtools_app/lib/src/screens/deep_link_validation/deep_link_list_view.dart @@ -33,6 +33,7 @@ class DeepLinkListView extends StatefulWidget { } class _DeepLinkListViewState extends State { + // ignore: dispose-fields, screen controller disposal is handled by the [ScreenControllers] class. late DeepLinksController controller; @override diff --git a/packages/devtools_app/lib/src/screens/dtd/events.dart b/packages/devtools_app/lib/src/screens/dtd/events.dart index e00539f25f0..fe6944873b0 100644 --- a/packages/devtools_app/lib/src/screens/dtd/events.dart +++ b/packages/devtools_app/lib/src/screens/dtd/events.dart @@ -13,6 +13,7 @@ import 'shared.dart'; /// Manages business logic for the [EventsView] widget, which displays /// information about events sent and received over DTD event streams. class EventsController extends FeatureController { + // ignore: dispose-class-fields, this class is not the owner of this object. late DartToolingDaemon dtd; @visibleForTesting diff --git a/packages/devtools_app/lib/src/screens/dtd/services.dart b/packages/devtools_app/lib/src/screens/dtd/services.dart index a9dc28da97b..442248b30b0 100644 --- a/packages/devtools_app/lib/src/screens/dtd/services.dart +++ b/packages/devtools_app/lib/src/screens/dtd/services.dart @@ -14,6 +14,7 @@ import 'dtd_tools_model.dart'; /// information about service methods registered on DTD and provides /// functionality for calling them. class ServicesController extends FeatureController { + // ignore: dispose-class-fields, this class is not the owner of this object. late DartToolingDaemon dtd; @visibleForTesting diff --git a/packages/devtools_app/lib/src/screens/inspector/inspector_controller.dart b/packages/devtools_app/lib/src/screens/inspector/inspector_controller.dart index e1db0b59c2a..dce3b31f439 100644 --- a/packages/devtools_app/lib/src/screens/inspector/inspector_controller.dart +++ b/packages/devtools_app/lib/src/screens/inspector/inspector_controller.dart @@ -201,7 +201,9 @@ class InspectorController extends DisposableController /// for now mainly to minimize risk. static const refreshFramesPerSecond = 5.0; + // ignore: dispose-class-fields, set from the constructor. This class is not the owner of this object. InspectorTreeController inspectorTree; + final FlutterTreeType treeType; late RateLimiter _refreshRateLimiter; diff --git a/packages/devtools_app/lib/src/screens/inspector/inspector_tree_controller.dart b/packages/devtools_app/lib/src/screens/inspector/inspector_tree_controller.dart index 67c39baebfd..130bec6a5a2 100644 --- a/packages/devtools_app/lib/src/screens/inspector/inspector_tree_controller.dart +++ b/packages/devtools_app/lib/src/screens/inspector/inspector_tree_controller.dart @@ -914,6 +914,8 @@ class _InspectorTreeState extends State Rect? _currentAnimateTarget; AnimationController? _constraintDisplayController; + + // ignore: dispose-fields, false positive. Disposed via autoDisposeFocusNode. late FocusNode _focusNode; /// When autoscrolling, the number of rows to pad the target location with. diff --git a/packages/devtools_app/lib/src/screens/inspector/layout_explorer/flex/flex.dart b/packages/devtools_app/lib/src/screens/inspector/layout_explorer/flex/flex.dart index 3c3cb55e2ec..dd16fdec685 100644 --- a/packages/devtools_app/lib/src/screens/inspector/layout_explorer/flex/flex.dart +++ b/packages/devtools_app/lib/src/screens/inspector/layout_explorer/flex/flex.dart @@ -394,6 +394,12 @@ class FlexLayoutExplorerWidgetState ), ); } + + @override + void dispose() { + scrollController.dispose(); + super.dispose(); + } } class VisualizeFlexChildren extends StatefulWidget { diff --git a/packages/devtools_app/lib/src/screens/inspector/layout_explorer/ui/layout_explorer_widget.dart b/packages/devtools_app/lib/src/screens/inspector/layout_explorer/ui/layout_explorer_widget.dart index 1a9840f9ead..9c8bf182944 100644 --- a/packages/devtools_app/lib/src/screens/inspector/layout_explorer/ui/layout_explorer_widget.dart +++ b/packages/devtools_app/lib/src/screens/inspector/layout_explorer/ui/layout_explorer_widget.dart @@ -162,6 +162,9 @@ abstract class LayoutExplorerWidgetState< entranceController.dispose(); changeController.dispose(); _unregisterInspectorControllerService(); + entranceCurve.dispose(); + changeAnimation.dispose(); + rateLimiter.dispose(); super.dispose(); } diff --git a/packages/devtools_app/lib/src/screens/logging/_log_details.dart b/packages/devtools_app/lib/src/screens/logging/_log_details.dart index f277c54873c..19cba6690c1 100644 --- a/packages/devtools_app/lib/src/screens/logging/_log_details.dart +++ b/packages/devtools_app/lib/src/screens/logging/_log_details.dart @@ -43,6 +43,12 @@ class _LogDetailsState extends State unawaited(_computeLogDetails()); } + @override + void dispose() { + scrollController.dispose(); + super.dispose(); + } + @override void didUpdateWidget(LogDetails oldWidget) { super.didUpdateWidget(oldWidget); diff --git a/packages/devtools_app/lib/src/screens/memory/shared/widgets/class_filter.dart b/packages/devtools_app/lib/src/screens/memory/shared/widgets/class_filter.dart index afae49d1454..3374785c762 100644 --- a/packages/devtools_app/lib/src/screens/memory/shared/widgets/class_filter.dart +++ b/packages/devtools_app/lib/src/screens/memory/shared/widgets/class_filter.dart @@ -82,6 +82,13 @@ class _ClassFilterDialogState extends State { _loadStateFromFilter(widget.classFilter); } + @override + void dispose() { + _except.dispose(); + _only.dispose(); + super.dispose(); + } + @override void didUpdateWidget(covariant ClassFilterDialog oldWidget) { super.didUpdateWidget(oldWidget); diff --git a/packages/devtools_app/lib/src/screens/network/network_controller.dart b/packages/devtools_app/lib/src/screens/network/network_controller.dart index deecab62c7e..dfb9a90ca35 100644 --- a/packages/devtools_app/lib/src/screens/network/network_controller.dart +++ b/packages/devtools_app/lib/src/screens/network/network_controller.dart @@ -203,6 +203,10 @@ class NetworkController extends DevToolsScreenController selectedRequest.dispose(); _recordingNotifier.dispose(); _currentNetworkRequests.dispose(); + for (final r in _httpRequests ?? []) { + r.dispose(); + } + _httpRequests?.clear(); super.dispose(); } diff --git a/packages/devtools_app/lib/src/screens/performance/panes/timeline_events/perfetto/_perfetto_controller_web.dart b/packages/devtools_app/lib/src/screens/performance/panes/timeline_events/perfetto/_perfetto_controller_web.dart index ed9bc3a6f3e..c032ba10887 100644 --- a/packages/devtools_app/lib/src/screens/performance/panes/timeline_events/perfetto/_perfetto_controller_web.dart +++ b/packages/devtools_app/lib/src/screens/performance/panes/timeline_events/perfetto/_perfetto_controller_web.dart @@ -191,6 +191,7 @@ class PerfettoControllerImpl extends PerfettoController { await perfettoPostEventStream.close(); processor.dispose(); _activeScrollToTimeRange.dispose(); + activeTrace.dispose(); super.dispose(); } diff --git a/packages/devtools_app/lib/src/screens/performance/panes/timeline_events/perfetto/_perfetto_web.dart b/packages/devtools_app/lib/src/screens/performance/panes/timeline_events/perfetto/_perfetto_web.dart index 2d5cafa0c84..22842170d60 100644 --- a/packages/devtools_app/lib/src/screens/performance/panes/timeline_events/perfetto/_perfetto_web.dart +++ b/packages/devtools_app/lib/src/screens/performance/panes/timeline_events/perfetto/_perfetto_web.dart @@ -34,14 +34,14 @@ class Perfetto extends StatefulWidget { } class _PerfettoState extends State with AutoDisposeMixin { - late final PerfettoControllerImpl _perfettoController; + PerfettoControllerImpl get _perfettoController => + widget.perfettoController as PerfettoControllerImpl; late final _PerfettoViewController _viewController; @override void initState() { super.initState(); - _perfettoController = widget.perfettoController as PerfettoControllerImpl; _viewController = _PerfettoViewController(_perfettoController)..init(); // If [_perfettoController.activeTrace.trace] has a null value, the trace diff --git a/packages/devtools_app/lib/src/screens/performance/performance_controller.dart b/packages/devtools_app/lib/src/screens/performance/performance_controller.dart index 8bed01cc548..e93c590e0ba 100644 --- a/packages/devtools_app/lib/src/screens/performance/performance_controller.dart +++ b/packages/devtools_app/lib/src/screens/performance/performance_controller.dart @@ -48,12 +48,16 @@ class PerformanceController extends DevToolsScreenController @override final screenId = ScreenMetaData.performance.id; + // ignore: dispose-class-fields, false positive. See `applyToFeatureControllers` in the dispose() method. late final FlutterFramesController flutterFramesController; + // ignore: dispose-class-fields, false positive. See `applyToFeatureControllers` in the dispose() method. late final TimelineEventsController timelineEventsController; + // ignore: dispose-class-fields, false positive. See `applyToFeatureControllers` in the dispose() method. late final RebuildStatsController rebuildStatsController; + // ignore: dispose-class-fields, false positive. See `applyToFeatureControllers` in the dispose() method. late List _featureControllers; // TODO(jacobr): add the recount controller to [_featureControllers] once your diff --git a/packages/devtools_app/lib/src/screens/performance/tabbed_performance_view.dart b/packages/devtools_app/lib/src/screens/performance/tabbed_performance_view.dart index 05eef54cd1c..a0536e0c5a9 100644 --- a/packages/devtools_app/lib/src/screens/performance/tabbed_performance_view.dart +++ b/packages/devtools_app/lib/src/screens/performance/tabbed_performance_view.dart @@ -13,7 +13,6 @@ import '../../shared/globals.dart'; import '../../shared/ui/common_widgets.dart'; import '../../shared/ui/tab.dart'; import 'panes/flutter_frames/flutter_frame_model.dart'; -import 'panes/flutter_frames/flutter_frames_controller.dart'; import 'panes/frame_analysis/frame_analysis.dart'; import 'panes/rebuild_stats/rebuild_stats.dart'; import 'panes/timeline_events/timeline_events_view.dart'; @@ -32,20 +31,18 @@ class _TabbedPerformanceViewState extends State late PerformanceController controller; - late FlutterFramesController _flutterFramesController; - FlutterFrame? _selectedFlutterFrame; @override void initState() { super.initState(); controller = screenControllers.lookup(); - _flutterFramesController = controller.flutterFramesController; + final flutterFramesController = controller.flutterFramesController; - _selectedFlutterFrame = _flutterFramesController.selectedFrame.value; - addAutoDisposeListener(_flutterFramesController.selectedFrame, () { + _selectedFlutterFrame = flutterFramesController.selectedFrame.value; + addAutoDisposeListener(flutterFramesController.selectedFrame, () { setState(() { - _selectedFlutterFrame = _flutterFramesController.selectedFrame.value; + _selectedFlutterFrame = flutterFramesController.selectedFrame.value; }); }); } diff --git a/packages/devtools_app/lib/src/screens/vm_developer/isolate_statistics/isolate_statistics_view.dart b/packages/devtools_app/lib/src/screens/vm_developer/isolate_statistics/isolate_statistics_view.dart index 77bd8d42c2f..5c25fede612 100644 --- a/packages/devtools_app/lib/src/screens/vm_developer/isolate_statistics/isolate_statistics_view.dart +++ b/packages/devtools_app/lib/src/screens/vm_developer/isolate_statistics/isolate_statistics_view.dart @@ -340,6 +340,12 @@ class _IsolatePortsWidgetState extends State { final selectedPort = ValueNotifier(null); + @override + void dispose() { + selectedPort.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { final ports = widget.controller.ports; diff --git a/packages/devtools_app/lib/src/screens/vm_developer/object_inspector/object_inspector_view.dart b/packages/devtools_app/lib/src/screens/vm_developer/object_inspector/object_inspector_view.dart index 76249c3402e..277e7dd99a7 100644 --- a/packages/devtools_app/lib/src/screens/vm_developer/object_inspector/object_inspector_view.dart +++ b/packages/devtools_app/lib/src/screens/vm_developer/object_inspector/object_inspector_view.dart @@ -78,6 +78,7 @@ class ObjectInspectorSelector extends StatefulWidget { class _ObjectInspectorSelectorState extends State { String value = ObjectInspectorSelector.kProgramExplorer; + late ObjectInspectorViewController controller; @override diff --git a/packages/devtools_app/lib/src/screens/vm_developer/process_memory/process_memory_view.dart b/packages/devtools_app/lib/src/screens/vm_developer/process_memory/process_memory_view.dart index 9ae5d1d7554..2fb5ad78131 100644 --- a/packages/devtools_app/lib/src/screens/vm_developer/process_memory/process_memory_view.dart +++ b/packages/devtools_app/lib/src/screens/vm_developer/process_memory/process_memory_view.dart @@ -87,6 +87,16 @@ class _VMProcessMemoryViewBodyState extends State _initTabController(); } + @override + void dispose() { + if (_tabControllerInitialized) { + _tabController.removeListener(_onTabChanged); + _tabController.dispose(); + } + controller.dispose(); + super.dispose(); + } + @override void didUpdateWidget(VMProcessMemoryViewBody oldWidget) { super.didUpdateWidget(oldWidget); diff --git a/packages/devtools_app/lib/src/service/connected_app/connected_app.dart b/packages/devtools_app/lib/src/service/connected_app/connected_app.dart index 04c25f58c83..f6e64194c71 100644 --- a/packages/devtools_app/lib/src/service/connected_app/connected_app.dart +++ b/packages/devtools_app/lib/src/service/connected_app/connected_app.dart @@ -87,6 +87,7 @@ class AppState extends DisposableController with AutoDisposeControllerMixin { void dispose() { _variables.dispose(); _currentFrame.dispose(); + _dapVariables.dispose(); super.dispose(); } } diff --git a/packages/devtools_app/lib/src/service/service_extension_widgets.dart b/packages/devtools_app/lib/src/service/service_extension_widgets.dart index 7fdd4b4ad9a..10fab338b7a 100644 --- a/packages/devtools_app/lib/src/service/service_extension_widgets.dart +++ b/packages/devtools_app/lib/src/service/service_extension_widgets.dart @@ -534,6 +534,13 @@ class _ServiceExtensionCheckboxState extends State _initExtensionState(); } + @override + void dispose() { + value.dispose(); + extensionAvailable.dispose(); + super.dispose(); + } + @override void _onMainIsolateChanged() => _initExtensionState(); @@ -704,6 +711,12 @@ class _ServiceExtensionCheckboxGroupButtonState _initExtensionState(); } + @override + void dispose() { + _enabled.dispose(); + super.dispose(); + } + @override void _onMainIsolateChanged() => _initExtensionState(); diff --git a/packages/devtools_app/lib/src/shared/charts/flame_chart.dart b/packages/devtools_app/lib/src/shared/charts/flame_chart.dart index 37200e06ffd..bff073a30dd 100644 --- a/packages/devtools_app/lib/src/shared/charts/flame_chart.dart +++ b/packages/devtools_app/lib/src/shared/charts/flame_chart.dart @@ -114,6 +114,7 @@ abstract class FlameChartState< final sections = []; + // ignore: dispose-fields, false positive. Disposed via autoDisposeFocusNode. final focusNode = FocusNode(debugLabel: 'flame-chart'); double? mouseHoverX; @@ -277,6 +278,7 @@ abstract class FlameChartState< void dispose() { zoomController.dispose(); _verticalFlameChartScrollController.dispose(); + _hoveredNodeNotifier.dispose(); super.dispose(); } diff --git a/packages/devtools_app/lib/src/shared/config_specific/drag_and_drop/drag_and_drop.dart b/packages/devtools_app/lib/src/shared/config_specific/drag_and_drop/drag_and_drop.dart index 2b26dd69e65..233de4798b0 100644 --- a/packages/devtools_app/lib/src/shared/config_specific/drag_and_drop/drag_and_drop.dart +++ b/packages/devtools_app/lib/src/shared/config_specific/drag_and_drop/drag_and_drop.dart @@ -38,6 +38,7 @@ abstract class DragAndDropManager { @mustCallSuper void dispose() { _dragAndDropStates.clear(); + activeState = null; } void registerDragAndDrop(DragAndDropState state) { @@ -113,6 +114,7 @@ class DragAndDrop extends StatefulWidget { class DragAndDropState extends State { final _dragging = ValueNotifier(false); + DragAndDropManager? _dragAndDropManager; bool _isActive = false; @@ -140,6 +142,8 @@ class DragAndDropState extends State { @override void dispose() { _dragAndDropManager?.unregisterDragAndDrop(this); + _dragAndDropManager = null; + _dragging.dispose(); super.dispose(); } diff --git a/packages/devtools_app/lib/src/shared/console/console.dart b/packages/devtools_app/lib/src/shared/console/console.dart index 91ead2a0640..30e9913ae67 100644 --- a/packages/devtools_app/lib/src/shared/console/console.dart +++ b/packages/devtools_app/lib/src/shared/console/console.dart @@ -90,6 +90,12 @@ class _ConsoleOutputState extends State<_ConsoleOutput> _initHelper(); } + @override + void dispose() { + _scroll.dispose(); + super.dispose(); + } + void _onScrollChanged() { // Detect if the user has scrolled up and stop scrolling to the bottom if // they have scrolled up. diff --git a/packages/devtools_app/lib/src/shared/diagnostics/inspector_service.dart b/packages/devtools_app/lib/src/shared/diagnostics/inspector_service.dart index 805e83f22ef..2d4e8a794ff 100644 --- a/packages/devtools_app/lib/src/shared/diagnostics/inspector_service.dart +++ b/packages/devtools_app/lib/src/shared/diagnostics/inspector_service.dart @@ -245,6 +245,7 @@ class InspectorService extends InspectorServiceBase { void dispose() { _cachedSelectionGroups?.clear(false); _cachedSelectionGroups = null; + _rootDirectories.dispose(); super.dispose(); } diff --git a/packages/devtools_app/lib/src/shared/editor/editor_client.dart b/packages/devtools_app/lib/src/shared/editor/editor_client.dart index ef03c446f47..951790de13d 100644 --- a/packages/devtools_app/lib/src/shared/editor/editor_client.dart +++ b/packages/devtools_app/lib/src/shared/editor/editor_client.dart @@ -120,6 +120,15 @@ class EditorClient extends DisposableController } } + @override + void dispose() { + _editableArgumentsApiIsRegistered.dispose(); + unawaited(_activeLocationChangedController.close()); + unawaited(_eventController.close()); + unawaited(_editorServiceChangedController.close()); + super.dispose(); + } + void _handleServiceRegistration({ required String service, required String method, diff --git a/packages/devtools_app/lib/src/shared/preferences/preferences.dart b/packages/devtools_app/lib/src/shared/preferences/preferences.dart index a15d6d47072..403c4e43319 100644 --- a/packages/devtools_app/lib/src/shared/preferences/preferences.dart +++ b/packages/devtools_app/lib/src/shared/preferences/preferences.dart @@ -282,13 +282,17 @@ class PreferencesController extends DisposableController @override void dispose() { - cpuProfiler.dispose(); - devToolsExtensions.dispose(); - inspector.dispose(); - logging.dispose(); - memory.dispose(); - network.dispose(); - performance.dispose(); + _cpuProfiler.dispose(); + _extensions.dispose(); + _inspector.dispose(); + _logging.dispose(); + _memory.dispose(); + _network.dispose(); + _performance.dispose(); + darkModeEnabled.dispose(); + advancedDeveloperModeEnabled.dispose(); + wasmEnabled.dispose(); + verboseLoggingEnabled.dispose(); super.dispose(); } diff --git a/packages/devtools_app/lib/src/shared/primitives/custom_pointer_scroll_view.dart b/packages/devtools_app/lib/src/shared/primitives/custom_pointer_scroll_view.dart index f6cc1b52de7..3794f1c02be 100644 --- a/packages/devtools_app/lib/src/shared/primitives/custom_pointer_scroll_view.dart +++ b/packages/devtools_app/lib/src/shared/primitives/custom_pointer_scroll_view.dart @@ -387,7 +387,11 @@ class CustomPointerScrollableState extends State @override void dispose() { widget.controller?.detach(position!); - position!.dispose(); + _position!.dispose(); + _hold?.cancel(); + _hold = null; + _drag?.cancel(); + _drag = null; super.dispose(); } diff --git a/packages/devtools_app/lib/src/shared/table/_tree_table.dart b/packages/devtools_app/lib/src/shared/table/_tree_table.dart index 1880f50a34a..22db562d84c 100644 --- a/packages/devtools_app/lib/src/shared/table/_tree_table.dart +++ b/packages/devtools_app/lib/src/shared/table/_tree_table.dart @@ -122,6 +122,8 @@ class TreeTable> extends StatefulWidget { class TreeTableState> extends State> with TickerProviderStateMixin, AutoDisposeMixin { FocusNode? get focusNode => _focusNode; + + // ignore: dispose-fields, false positive. Disposed via autoDisposeFocusNode. late FocusNode _focusNode; TreeTableController get tableController => _tableController!; @@ -165,6 +167,7 @@ class TreeTableState> extends State> @override void dispose() { + _tableController?.dispose(); _tableController = null; super.dispose(); } diff --git a/packages/devtools_app/lib/src/shared/table/table.dart b/packages/devtools_app/lib/src/shared/table/table.dart index 9e01cf2283b..b6197282394 100644 --- a/packages/devtools_app/lib/src/shared/table/table.dart +++ b/packages/devtools_app/lib/src/shared/table/table.dart @@ -115,9 +115,14 @@ class DevToolsTableState extends State> with AutoDisposeMixin { static const _resizingDebounceDuration = Duration(milliseconds: 200); - late ScrollController scrollController; + @visibleForTesting + // ignore: dispose-fields, this class is not the owner of this object. + late final ScrollController verticalScrollController; + + // ignore: dispose-fields, this class is not the owner of this object. + late final ScrollController _horizontalScrollbarController; + late ScrollController pinnedScrollController; - late ScrollController _horizontalScrollbarController; late List _data; @@ -142,13 +147,13 @@ class DevToolsTableState extends State> ? widget.tableController.tableUiState.scrollOffset : 0.0; widget.tableController.initScrollController(initialScrollOffset); - scrollController = widget.tableController.verticalScrollController!; + verticalScrollController = widget.tableController.verticalScrollController!; _horizontalScrollbarController = widget.tableController.horizontalScrollController!; if (widget.startScrolledAtBottom) { WidgetsBinding.instance.addPostFrameCallback((_) { - unawaited(scrollController.autoScrollToBottom(jump: true)); + unawaited(verticalScrollController.autoScrollToBottom(jump: true)); }); } @@ -208,7 +213,7 @@ class DevToolsTableState extends State> final newPos = selectedDisplayRow * defaultRowHeight; - maybeScrollToPosition(scrollController, newPos); + maybeScrollToPosition(verticalScrollController, newPos); } }); }); @@ -232,13 +237,15 @@ class DevToolsTableState extends State> final index = _data.indexOf(activeSearch); if (index == -1) return; - + final verticalScrollController = this.verticalScrollController; final y = index * defaultRowHeight; final indexInView = - y > scrollController.offset && - y < scrollController.offset + scrollController.position.extentInside; + y > verticalScrollController.offset && + y < + verticalScrollController.offset + + verticalScrollController.position.extentInside; if (!indexInView) { - await scrollController.animateTo( + await verticalScrollController.animateTo( index * defaultRowHeight, duration: defaultDuration, curve: defaultCurve, @@ -257,6 +264,7 @@ class DevToolsTableState extends State> @override void dispose() { pinnedScrollController.dispose(); + _resizingDebouncer.dispose(); super.dispose(); } @@ -388,10 +396,13 @@ class DevToolsTableState extends State> @override Widget build(BuildContext context) { + final verticalScrollController = this.verticalScrollController; + // If we're at the end already, scroll to expose the new content. if (widget.autoScrollContent) { - if (scrollController.hasClients && scrollController.atScrollBottom) { - unawaited(scrollController.autoScrollToBottom()); + if (verticalScrollController.hasClients && + verticalScrollController.atScrollBottom) { + unawaited(verticalScrollController.autoScrollToBottom()); } } @@ -405,8 +416,9 @@ class DevToolsTableState extends State> final tableUiState = widget.tableController.tableUiState; final sortColumn = widget.tableController.columns[tableUiState.sortColumnIndex]; - if (widget.preserveVerticalScrollPosition && scrollController.hasClients) { - scrollController.jumpTo(tableUiState.scrollOffset); + if (widget.preserveVerticalScrollPosition && + verticalScrollController.hasClients) { + verticalScrollController.jumpTo(tableUiState.scrollOffset); } return LayoutBuilder( @@ -478,7 +490,7 @@ class DevToolsTableState extends State> Expanded( child: Scrollbar( thumbVisibility: true, - controller: scrollController, + controller: verticalScrollController, child: GestureDetector( behavior: HitTestBehavior.translucent, onTapDown: (a) => widget.focusNode?.requestFocus(), @@ -488,13 +500,13 @@ class DevToolsTableState extends State> widget.handleKeyEvent != null ? widget.handleKeyEvent!( event, - scrollController, + verticalScrollController, constraints, ) : KeyEventResult.ignored, focusNode: widget.focusNode, child: ListView.builder( - controller: scrollController, + controller: verticalScrollController, itemCount: _dataRowCount( constraints, showColumnGroupHeader, diff --git a/packages/devtools_app/lib/src/shared/ui/editable_list.dart b/packages/devtools_app/lib/src/shared/ui/editable_list.dart index b72f5866cbf..392950b5803 100644 --- a/packages/devtools_app/lib/src/shared/ui/editable_list.dart +++ b/packages/devtools_app/lib/src/shared/ui/editable_list.dart @@ -80,6 +80,7 @@ class _EditableListState extends State { @override void dispose() { textFieldController.dispose(); + textFieldFocusNode.dispose(); super.dispose(); } diff --git a/packages/devtools_app/lib/src/shared/ui/vm_flag_widgets.dart b/packages/devtools_app/lib/src/shared/ui/vm_flag_widgets.dart index 187ebff1f04..8ddbce44500 100644 --- a/packages/devtools_app/lib/src/shared/ui/vm_flag_widgets.dart +++ b/packages/devtools_app/lib/src/shared/ui/vm_flag_widgets.dart @@ -175,6 +175,12 @@ class _VMFlagsDialogState extends State with AutoDisposeMixin { }); } + @override + void dispose() { + filterController.dispose(); + super.dispose(); + } + void _updateFromController() { flags = (serviceConnection.vmFlagManager.flags.value?.flags ?? []) .map((flag) => _DialogFlag(flag)) diff --git a/packages/devtools_app/lib/src/standalone_ui/ide_shared/property_editor/property_editor_controller.dart b/packages/devtools_app/lib/src/standalone_ui/ide_shared/property_editor/property_editor_controller.dart index bad9c16e6aa..c8bf0e17a5a 100644 --- a/packages/devtools_app/lib/src/standalone_ui/ide_shared/property_editor/property_editor_controller.dart +++ b/packages/devtools_app/lib/src/standalone_ui/ide_shared/property_editor/property_editor_controller.dart @@ -123,6 +123,7 @@ class PropertyEditorController extends DisposableController @override void dispose() { _requestDebouncer.dispose(); + _editableWidgetData.dispose(); super.dispose(); } diff --git a/packages/devtools_app/lib/src/standalone_ui/ide_shared/property_editor/property_editor_inputs.dart b/packages/devtools_app/lib/src/standalone_ui/ide_shared/property_editor/property_editor_inputs.dart index 037d46088bb..9d01365c63d 100644 --- a/packages/devtools_app/lib/src/standalone_ui/ide_shared/property_editor/property_editor_inputs.dart +++ b/packages/devtools_app/lib/src/standalone_ui/ide_shared/property_editor/property_editor_inputs.dart @@ -206,6 +206,7 @@ class _TextInputState extends State<_TextInput> with _PropertyInputMixin<_TextInput, T>, AutoDisposeMixin { static const _paddingDiffComparedToDropdown = 1.0; + // ignore: dispose-fields, false positive. Disposed via autoDisposeFocusNode. late final FocusNode _focusNode; late final TextEditingController _controller; @@ -224,6 +225,13 @@ class _TextInputState extends State<_TextInput> // Edit property when clicking or tabbing away from input. await _editProperty(); }); + autoDisposeFocusNode(_focusNode); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); } @override diff --git a/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md b/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md index 2cd2c928fe0..14d0b6d408a 100644 --- a/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md +++ b/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md @@ -15,7 +15,7 @@ To learn more about DevTools, check out the ## General updates -TODO: Remove this section if there are not any updates. +* Resolve several memory leaks. - [#9857](https://github.com/flutter/devtools/pull/9857) ## Inspector updates diff --git a/packages/devtools_app/test/shared/managers/error_badge_manager_test.dart b/packages/devtools_app/test/shared/managers/error_badge_manager_test.dart index e5f17d19665..e6c48df08c3 100644 --- a/packages/devtools_app/test/shared/managers/error_badge_manager_test.dart +++ b/packages/devtools_app/test/shared/managers/error_badge_manager_test.dart @@ -34,7 +34,7 @@ void main() { late ErrorBadgeManager errorBadgeManager; group('ErrorBadgeManager', () { - int getActiveErrorCount(screenId) => + int getActiveErrorCount(String screenId) => errorBadgeManager.erroredItemsForPage(screenId).value.entries.length; setUp(() { diff --git a/packages/devtools_app/test/shared/primitives/linked_scroll_controller_test.dart b/packages/devtools_app/test/shared/primitives/linked_scroll_controller_test.dart index 13f004632ee..2ec8be6348a 100644 --- a/packages/devtools_app/test/shared/primitives/linked_scroll_controller_test.dart +++ b/packages/devtools_app/test/shared/primitives/linked_scroll_controller_test.dart @@ -345,6 +345,13 @@ class TestState extends State { _numbers = _controllers.addAndGet(); } + @override + void dispose() { + _letters.dispose(); + _numbers.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { return Directionality( diff --git a/packages/devtools_app/test/shared/table/table_test.dart b/packages/devtools_app/test/shared/table/table_test.dart index dd2dcd97446..f977bb60302 100644 --- a/packages/devtools_app/test/shared/table/table_test.dart +++ b/packages/devtools_app/test/shared/table/table_test.dart @@ -1193,7 +1193,7 @@ void main() { final tableState = tester.state( find.byType(DevToolsTable), ); - final scrollController = tableState.scrollController; + final scrollController = tableState.verticalScrollController; expect(scrollController.offset, 0.0); expect(scrollController.position.maxScrollExtent, greaterThan(0.0)); @@ -1221,7 +1221,7 @@ void main() { final tableState = tester.state( find.byType(DevToolsTable), ); - final scrollController = tableState.scrollController; + final scrollController = tableState.verticalScrollController; expect(scrollController.offset, isNot(0.0)); expect(scrollController.position.maxScrollExtent, greaterThan(0.0)); diff --git a/packages/devtools_app/test/test_infra/fixtures/flutter_error_app/lib/missing_material_error.dart b/packages/devtools_app/test/test_infra/fixtures/flutter_error_app/lib/missing_material_error.dart index 1c48982d9b5..04110b1e5c5 100644 --- a/packages/devtools_app/test/test_infra/fixtures/flutter_error_app/lib/missing_material_error.dart +++ b/packages/devtools_app/test/test_infra/fixtures/flutter_error_app/lib/missing_material_error.dart @@ -33,6 +33,12 @@ class ExampleWidget extends StatefulWidget { class _ExampleWidgetState extends State { final _controller = TextEditingController(); + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { return Column( diff --git a/packages/devtools_app/test/test_infra/scenes/standalone_ui/editor_service/simulated_editor.dart b/packages/devtools_app/test/test_infra/scenes/standalone_ui/editor_service/simulated_editor.dart index c17709e6ad9..a762658e145 100644 --- a/packages/devtools_app/test/test_infra/scenes/standalone_ui/editor_service/simulated_editor.dart +++ b/packages/devtools_app/test/test_infra/scenes/standalone_ui/editor_service/simulated_editor.dart @@ -35,6 +35,11 @@ class SimulatedEditor { return editor; } + void dispose() { + unawaited(_logger.close()); + unawaited(close()); + } + /// The URI of the DTD instance we are connecting/connected to. final Uri _dtdUri; diff --git a/packages/devtools_app/test/test_infra/scenes/standalone_ui/mock_editor_widget.dart b/packages/devtools_app/test/test_infra/scenes/standalone_ui/mock_editor_widget.dart index 08329bab45c..d13c09055af 100644 --- a/packages/devtools_app/test/test_infra/scenes/standalone_ui/mock_editor_widget.dart +++ b/packages/devtools_app/test/test_infra/scenes/standalone_ui/mock_editor_widget.dart @@ -87,6 +87,13 @@ class _MockEditorWidgetState extends State ); } + @override + void dispose() { + clientLogRing.dispose(); + editorLogRing.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { final editorTheme = VsCodeTheme.of(context); diff --git a/packages/devtools_app/test/test_infra/scenes/standalone_ui/property_editor_sidebar.dart b/packages/devtools_app/test/test_infra/scenes/standalone_ui/property_editor_sidebar.dart index 889c170f248..7f679252897 100644 --- a/packages/devtools_app/test/test_infra/scenes/standalone_ui/property_editor_sidebar.dart +++ b/packages/devtools_app/test/test_infra/scenes/standalone_ui/property_editor_sidebar.dart @@ -67,8 +67,9 @@ class _PropertyEditorState extends State<_PropertyEditorSidebar> { SimulatedEditor? editor; @override - void initState() { - super.initState(); + void dispose() { + unawaited(editor?.close()); + super.dispose(); } @override diff --git a/packages/devtools_app_shared/lib/src/service/dtd_manager.dart b/packages/devtools_app_shared/lib/src/service/dtd_manager.dart index 0035f7ac5e3..83f99705cc0 100644 --- a/packages/devtools_app_shared/lib/src/service/dtd_manager.dart +++ b/packages/devtools_app_shared/lib/src/service/dtd_manager.dart @@ -276,6 +276,9 @@ class DTDManager { await disconnect(); await _currentServiceRegistrationSubscription?.cancel(); await _serviceRegistrationController.close(); + _periodicConnectionCheck?.cancel(); + _periodicConnectionCheck = null; + _connectionState.dispose(); _connection.dispose(); } diff --git a/packages/devtools_app_shared/lib/src/service/isolate_state.dart b/packages/devtools_app_shared/lib/src/service/isolate_state.dart index ae8ec45c3d6..33764766c36 100644 --- a/packages/devtools_app_shared/lib/src/service/isolate_state.dart +++ b/packages/devtools_app_shared/lib/src/service/isolate_state.dart @@ -48,6 +48,7 @@ class IsolateState { null, () => _isolateLoadCompleter = Completer()..complete(null), ); + _isPaused.dispose(); } void handleDebugEvent(String? kind) { diff --git a/packages/devtools_extensions/example/packages_with_extensions/foo/packages/foo_devtools_extension/lib/src/feature_examples/dtd_example.dart b/packages/devtools_extensions/example/packages_with_extensions/foo/packages/foo_devtools_extension/lib/src/feature_examples/dtd_example.dart index 8ab07259f9f..114902214b0 100644 --- a/packages/devtools_extensions/example/packages_with_extensions/foo/packages/foo_devtools_extension/lib/src/feature_examples/dtd_example.dart +++ b/packages/devtools_extensions/example/packages_with_extensions/foo/packages/foo_devtools_extension/lib/src/feature_examples/dtd_example.dart @@ -169,6 +169,12 @@ class _ReadWriteTmpFileState extends State<_ReadWriteTmpFile> { textEditingController = TextEditingController(); } + @override + void dispose() { + textEditingController.dispose(); + super.dispose(); + } + Future _writeFilesAndUpdate() async { await dtdManager.writeFile(tmpFileUri, textEditingController.text); await _readTmpFile(); diff --git a/packages/devtools_extensions/example/packages_with_extensions/foo/packages/foo_devtools_extension/lib/src/feature_examples/service_extension_example.dart b/packages/devtools_extensions/example/packages_with_extensions/foo/packages/foo_devtools_extension/lib/src/feature_examples/service_extension_example.dart index 1ee30629707..f82d6774061 100644 --- a/packages/devtools_extensions/example/packages_with_extensions/foo/packages/foo_devtools_extension/lib/src/feature_examples/service_extension_example.dart +++ b/packages/devtools_extensions/example/packages_with_extensions/foo/packages/foo_devtools_extension/lib/src/feature_examples/service_extension_example.dart @@ -108,6 +108,12 @@ class _TableOfThingsState extends State { unawaited(_refreshThings()); } + @override + void dispose() { + things.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { return Column( diff --git a/packages/devtools_extensions/lib/src/template/_simulated_devtools_environment/_simulated_devtools_controller.dart b/packages/devtools_extensions/lib/src/template/_simulated_devtools_environment/_simulated_devtools_controller.dart index 2853cbb7a1e..024550eb7b1 100644 --- a/packages/devtools_extensions/lib/src/template/_simulated_devtools_environment/_simulated_devtools_controller.dart +++ b/packages/devtools_extensions/lib/src/template/_simulated_devtools_environment/_simulated_devtools_controller.dart @@ -48,6 +48,7 @@ class SimulatedDevToolsController extends DisposableController void dispose() { window.removeEventListener('message', _handleMessageListener); _handleMessageListener = null; + messageLogs.dispose(); super.dispose(); } diff --git a/pubspec.lock b/pubspec.lock index 090a02187c1..cc8b58e6e9b 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -173,10 +173,10 @@ packages: dependency: transitive description: name: coverage - sha256: "5da775aa218eaf2151c721b16c01c7676fbfdd99cebba2bf64e8b807a28ff94d" + sha256: "956a3de0725ca232ad353565a8290d3357592bf4250f6f298a185e2d949c5d3d" url: "https://pub.dev" source: hosted - version: "1.15.0" + version: "1.15.1" cross_file: dependency: transitive description: @@ -286,10 +286,10 @@ packages: dependency: transitive description: name: file_selector_android - sha256: ff9f9a900e0b0cf546f05abe86c91a280d611005b977c2c46a446436d7a776e3 + sha256: "6a26687fa65cbc28a5345c7ae6f227e89f0b47740978a4c475b1a625da7a331b" url: "https://pub.dev" source: hosted - version: "0.5.2+7" + version: "0.5.2+8" file_selector_ios: dependency: transitive description: @@ -555,10 +555,10 @@ packages: dependency: transitive description: name: meta - sha256: df0c643f44ad098eb37988027a8e2b2b5a031fd3977f06bbfd3a76637e8df739 + sha256: c82594181e3312f3d0695fc95aaaf7758d75b8d4ae2bbecf223b9fd5109a059d url: "https://pub.dev" source: hosted - version: "1.18.2" + version: "1.18.3" mime: dependency: transitive description: @@ -911,10 +911,10 @@ packages: dependency: transitive description: name: url_launcher_android - sha256: "8a46fcbcd5b865e87053fc5096101d12f56f3fe352c42aa621e7fec9290054b7" + sha256: b413d49b73867ac08dd2f9890efd3cc11f2a0e577618d50843440a1fb3776c32 url: "https://pub.dev" source: hosted - version: "6.3.31" + version: "6.3.32" url_launcher_ios: dependency: transitive description: @@ -967,10 +967,10 @@ packages: dependency: transitive description: name: vector_math - sha256: "47a1b32ee755c3fcffa33db52a7258c137f97bdb2209a1075be847809fac4ccf" + sha256: "1d774bbdf6b72a0b12122fc1560c9c2d2a67db5a4a4cc2bd8a5c990ab20e3188" url: "https://pub.dev" source: hosted - version: "2.3.0" + version: "2.4.0" vm_service: dependency: transitive description: