Repository: flutter/devtools
Pull request #9117: Re-enable `discarded_futures` lint
Author: login: elliette
Labels: name: release-notes-not-required
Created: 2025-04-07T20:24:58Z | Merged: 2025-04-10T19:12:07Z

Fixes https://github.com/flutter/devtools/issues/9102

Follow-up to https://github.com/flutter/devtools/pull/9100 which disabled the `discarded_futures` lint.


Diff:
diff --git a/analysis_options.yaml b/analysis_options.yaml
index 866a2b7d3d0..3fa6d9df9dc 100644
--- a/analysis_options.yaml
+++ b/analysis_options.yaml
@@ -71,7 +71,7 @@ linter:
     # - constant_identifier_names # https://github.com/dart-lang/linter/issues/204
     - control_flow_in_finally
     - directives_ordering
-    # - discarded_futures # TODO(https://github.com/flutter/devtools/issues/9102): Re-enable.
+    - discarded_futures
     - empty_catches
     - empty_constructor_bodies
     - empty_statements
diff --git a/packages/devtools_app/lib/src/framework/home_screen.dart b/packages/devtools_app/lib/src/framework/home_screen.dart
index 540cf09bdd5..6e9b7ed2877 100644
--- a/packages/devtools_app/lib/src/framework/home_screen.dart
+++ b/packages/devtools_app/lib/src/framework/home_screen.dart
@@ -22,6 +22,7 @@ import '../shared/primitives/blocking_action_mixin.dart';
 import '../shared/primitives/utils.dart';
 import '../shared/title.dart';
 import '../shared/ui/vm_flag_widgets.dart';
+import '../shared/utils/utils.dart';
 import 'framework_core.dart';
 
 class HomeScreen extends Screen {
@@ -259,7 +260,7 @@ class _ConnectInputState extends State<ConnectInput> with BlockingActionMixin {
     }
 
     assert(() {
-      storage.setValue(_debugVmServiceUriKey, uri);
+      safeUnawaited(storage.setValue(_debugVmServiceUriKey, uri));
       return true;
     }());
 
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 3c60d941933..d69ebc1ee42 100644
--- a/packages/devtools_app/lib/src/screens/debugger/debugger_controller.dart
+++ b/packages/devtools_app/lib/src/screens/debugger/debugger_controller.dart
@@ -26,6 +26,7 @@ import '../../shared/framework/screen_controllers.dart';
 import '../../shared/globals.dart';
 import '../../shared/primitives/message_bus.dart';
 import '../../shared/primitives/utils.dart';
+import '../../shared/utils/utils.dart';
 import 'codeview_controller.dart';
 import 'debugger_model.dart';
 
@@ -399,9 +400,9 @@ class DebuggerController extends DevToolsScreenController
   /// This method ensures that the source for the script is populated in our
   /// cache, in order to reduce flashing in the editor view.
   void _populateScriptAndShowLocation(ScriptRef scriptRef) {
-    unawaited(
-      scriptManager.getScript(scriptRef).then((script) {
-        codeViewController.showScriptLocation(ScriptLocation(scriptRef));
+    safeUnawaited(
+      scriptManager.getScript(scriptRef).then((script) async {
+        await codeViewController.showScriptLocation(ScriptLocation(scriptRef));
       }),
     );
   }
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 99a23166cce..2716ef3393e 100644
--- a/packages/devtools_app/lib/src/screens/inspector/inspector_controller.dart
+++ b/packages/devtools_app/lib/src/screens/inspector/inspector_controller.dart
@@ -34,6 +34,7 @@ import '../../shared/diagnostics/primitives/instance_ref.dart';
 import '../../shared/globals.dart';
 import '../../shared/primitives/query_parameters.dart';
 import '../../shared/primitives/utils.dart';
+import '../../shared/utils/utils.dart';
 import '../inspector_shared/inspector_screen.dart';
 import 'inspector_tree_controller.dart';
 
@@ -105,12 +106,14 @@ class InspectorController extends DisposableController
       // won't interfere with users.
       addAutoDisposeListener(_supportsToggleSelectWidgetMode, () {
         if (_supportsToggleSelectWidgetMode.value) {
-          serviceConnection.serviceManager.serviceExtensionManager
-              .setServiceExtensionState(
-                extensions.enableOnDeviceInspector.extension,
-                enabled: true,
-                value: true,
-              );
+          safeUnawaited(
+            serviceConnection.serviceManager.serviceExtensionManager
+                .setServiceExtensionState(
+                  extensions.enableOnDeviceInspector.extension,
+                  enabled: true,
+                  value: true,
+                ),
+          );
         }
       });
     }
diff --git a/packages/devtools_app/lib/src/screens/inspector_v2/inspector_controller.dart b/packages/devtools_app/lib/src/screens/inspector_v2/inspector_controller.dart
index 83b3305a50f..a66cffff278 100644
--- a/packages/devtools_app/lib/src/screens/inspector_v2/inspector_controller.dart
+++ b/packages/devtools_app/lib/src/screens/inspector_v2/inspector_controller.dart
@@ -35,6 +35,7 @@ import '../../shared/globals.dart';
 import '../../shared/managers/notifications.dart';
 import '../../shared/primitives/query_parameters.dart';
 import '../../shared/primitives/utils.dart';
+import '../../shared/utils/utils.dart';
 import '../inspector_shared/inspector_screen.dart';
 import 'inspector_data_models.dart';
 import 'inspector_tree_controller.dart';
@@ -112,12 +113,14 @@ class InspectorController extends DisposableController
     // won't interfere with users.
     addAutoDisposeListener(_supportsToggleSelectWidgetMode, () {
       if (_supportsToggleSelectWidgetMode.value) {
-        serviceConnection.serviceManager.serviceExtensionManager
-            .setServiceExtensionState(
-              extensions.enableOnDeviceInspector.extension,
-              enabled: true,
-              value: true,
-            );
+        safeUnawaited(
+          serviceConnection.serviceManager.serviceExtensionManager
+              .setServiceExtensionState(
+                extensions.enableOnDeviceInspector.extension,
+                enabled: true,
+                value: true,
+              ),
+        );
       }
     });
 
diff --git a/packages/devtools_app/lib/src/screens/inspector_v2/inspector_screen_body.dart b/packages/devtools_app/lib/src/screens/inspector_v2/inspector_screen_body.dart
index 074ce7fce14..91903f21b1e 100644
--- a/packages/devtools_app/lib/src/screens/inspector_v2/inspector_screen_body.dart
+++ b/packages/devtools_app/lib/src/screens/inspector_v2/inspector_screen_body.dart
@@ -19,6 +19,7 @@ import '../../shared/managers/error_badge_manager.dart';
 import '../../shared/primitives/blocking_action_mixin.dart';
 import '../../shared/ui/common_widgets.dart';
 import '../../shared/ui/search.dart';
+import '../../shared/utils/utils.dart';
 import '../inspector_shared/inspector_controls.dart';
 import '../inspector_shared/inspector_screen.dart';
 import 'inspector_controller.dart';
@@ -97,7 +98,7 @@ class InspectorScreenBodyState extends State<InspectorScreenBody>
     addAutoDisposeListener(preferences.inspector.pubRootDirectories, () {
       if (serviceConnection.serviceManager.connectedState.value.connected &&
           controller.firstInspectorTreeLoadCompleted) {
-        controller.refreshInspector();
+        safeUnawaited(controller.refreshInspector());
       }
     });
 
diff --git a/packages/devtools_app/lib/src/screens/performance/panes/controls/enhance_tracing/enhance_tracing.dart b/packages/devtools_app/lib/src/screens/performance/panes/controls/enhance_tracing/enhance_tracing.dart
index 575d910decc..68776c60de0 100644
--- a/packages/devtools_app/lib/src/screens/performance/panes/controls/enhance_tracing/enhance_tracing.dart
+++ b/packages/devtools_app/lib/src/screens/performance/panes/controls/enhance_tracing/enhance_tracing.dart
@@ -14,6 +14,7 @@ import '../../../../../service/service_extensions.dart' as extensions;
 import '../../../../../shared/globals.dart';
 import '../../../../../shared/primitives/utils.dart';
 import '../../../../../shared/ui/common_widgets.dart';
+import '../../../../../shared/utils/utils.dart';
 import '../performance_controls.dart';
 import 'enhance_tracing_controller.dart';
 
@@ -142,7 +143,7 @@ class _TraceWidgetBuildsSettingState extends State<TraceWidgetBuildsSetting>
     for (final type in TraceWidgetBuildsScope.values) {
       final extension = _traceWidgetBuildsExtensions[type]!;
 
-      unawaited(
+      safeUnawaited(
         serviceConnection.serviceManager.serviceExtensionManager
             .waitForServiceExtensionAvailable(extension.extension)
             .then((isServiceAvailable) {
@@ -154,9 +155,13 @@ class _TraceWidgetBuildsSettingState extends State<TraceWidgetBuildsSetting>
                     .serviceExtensionManager
                     .getServiceExtensionState(extension.extension);
 
-                _updateForServiceExtensionState(state.value, type);
+                safeUnawaited(
+                  _updateForServiceExtensionState(state.value, type),
+                );
                 addAutoDisposeListener(state, () {
-                  _updateForServiceExtensionState(state.value, type);
+                  safeUnawaited(
+                    _updateForServiceExtensionState(state.value, type),
+                  );
                 });
               }
             }),
diff --git a/packages/devtools_app/lib/src/service/vm_service_wrapper.dart b/packages/devtools_app/lib/src/service/vm_service_wrapper.dart
index d5ec53cc73e..4da7282b238 100644
--- a/packages/devtools_app/lib/src/service/vm_service_wrapper.dart
+++ b/packages/devtools_app/lib/src/service/vm_service_wrapper.dart
@@ -25,6 +25,7 @@ import '../screens/vm_developer/vm_service_private_extensions.dart';
 import '../shared/feature_flags.dart';
 import '../shared/globals.dart';
 import '../shared/primitives/utils.dart';
+import '../shared/utils/utils.dart';
 import 'json_to_service_cache.dart';
 
 final _log = Logger('vm_service_wrapper');
@@ -188,8 +189,8 @@ class VmServiceWrapper extends VmService {
   }
 
   @override
-  Future<Success> streamCancel(String streamId) {
-    _activeStreams.remove(streamId);
+  Future<Success> streamCancel(String streamId) async {
+    await _activeStreams.remove(streamId);
     return super.streamCancel(streamId);
   }
 
@@ -380,9 +381,11 @@ class VmServiceWrapper extends VmService {
       }
     }
 
-    localFuture.then(
-      (value) => futureComplete(),
-      onError: (error) => futureComplete(),
+    safeUnawaited(
+      localFuture.then(
+        (value) => futureComplete(),
+        onError: (error) => futureComplete(),
+      ),
     );
     return localFuture;
   }
diff --git a/packages/devtools_app/lib/src/shared/managers/survey.dart b/packages/devtools_app/lib/src/shared/managers/survey.dart
index 6ac83fccd16..d7554937e03 100644
--- a/packages/devtools_app/lib/src/shared/managers/survey.dart
+++ b/packages/devtools_app/lib/src/shared/managers/survey.dart
@@ -95,7 +95,7 @@ class SurveyService {
           allowDuplicates: false,
         );
         if (didPush) {
-          server.incrementSurveyShownCount();
+          safeUnawaited(server.incrementSurveyShownCount());
         }
       });
     }
diff --git a/packages/devtools_app/lib/src/shared/preferences/_extension_preferences.dart b/packages/devtools_app/lib/src/shared/preferences/_extension_preferences.dart
index b96210a1010..42c80f0183e 100644
--- a/packages/devtools_app/lib/src/shared/preferences/_extension_preferences.dart
+++ b/packages/devtools_app/lib/src/shared/preferences/_extension_preferences.dart
@@ -15,9 +15,11 @@ class ExtensionsPreferencesController extends DisposableController
   @override
   Future<void> init() async {
     addAutoDisposeListener(showOnlyEnabledExtensions, () {
-      storage.setValue(
-        _showOnlyEnabledExtensionsId,
-        showOnlyEnabledExtensions.value.toString(),
+      safeUnawaited(
+        storage.setValue(
+          _showOnlyEnabledExtensionsId,
+          showOnlyEnabledExtensions.value.toString(),
+        ),
       );
       ga.select(
         gac.DevToolsExtensionEvents.extensionScreenId.name,
diff --git a/packages/devtools_app/lib/src/shared/preferences/_inspector_preferences.dart b/packages/devtools_app/lib/src/shared/preferences/_inspector_preferences.dart
index ff279aa1815..261af777c7b 100644
--- a/packages/devtools_app/lib/src/shared/preferences/_inspector_preferences.dart
+++ b/packages/devtools_app/lib/src/shared/preferences/_inspector_preferences.dart
@@ -168,9 +168,11 @@ class InspectorPreferencesController extends DisposableController
     await _updateInspectorDetailsViewSelection();
 
     addAutoDisposeListener(_defaultDetailsView, () {
-      storage.setValue(
-        _defaultDetailsViewStorageId,
-        _defaultDetailsView.value.name.toString(),
+      safeUnawaited(
+        storage.setValue(
+          _defaultDetailsViewStorageId,
+          _defaultDetailsView.value.name.toString(),
+        ),
       );
     });
   }
diff --git a/packages/devtools_app/lib/src/shared/preferences/_logging_preferences.dart b/packages/devtools_app/lib/src/shared/preferences/_logging_preferences.dart
index c4d5f8638bd..713c6ece0ed 100644
--- a/packages/devtools_app/lib/src/shared/preferences/_logging_preferences.dart
+++ b/packages/devtools_app/lib/src/shared/preferences/_logging_preferences.dart
@@ -39,9 +39,11 @@ class LoggingPreferencesController extends DisposableController
         int.tryParse(await storage.getValue(_retentionLimitStorageId) ?? '') ??
         _defaultRetentionLimit;
     addAutoDisposeListener(retentionLimit, () {
-      storage.setValue(
-        _retentionLimitStorageId,
-        retentionLimit.value.toString(),
+      safeUnawaited(
+        storage.setValue(
+          _retentionLimitStorageId,
+          retentionLimit.value.toString(),
+        ),
       );
       ga.select(
         gac.logging,
@@ -59,7 +61,9 @@ class LoggingPreferencesController extends DisposableController
         ) ??
         _defaultDetailsFormat;
     addAutoDisposeListener(detailsFormat, () {
-      storage.setValue(detailsFormatStorageId, detailsFormat.value.name);
+      safeUnawaited(
+        storage.setValue(detailsFormatStorageId, detailsFormat.value.name),
+      );
       ga.select(
         gac.logging,
         gac.LoggingEvents.changeDetailsFormat.name,
diff --git a/packages/devtools_app/lib/src/shared/preferences/_memory_preferences.dart b/packages/devtools_app/lib/src/shared/preferences/_memory_preferences.dart
index 5c55b5445e9..da641573d20 100644
--- a/packages/devtools_app/lib/src/shared/preferences/_memory_preferences.dart
+++ b/packages/devtools_app/lib/src/shared/preferences/_memory_preferences.dart
@@ -26,9 +26,11 @@ class MemoryPreferencesController extends DisposableController
   @override
   Future<void> init() async {
     addAutoDisposeListener(androidCollectionEnabled, () {
-      storage.setValue(
-        _androidCollectionEnabledStorageId,
-        androidCollectionEnabled.value.toString(),
+      safeUnawaited(
+        storage.setValue(
+          _androidCollectionEnabledStorageId,
+          androidCollectionEnabled.value.toString(),
+        ),
       );
       if (androidCollectionEnabled.value) {
         ga.select(gac.memory, gac.MemoryEvents.androidChart.name);
@@ -40,7 +42,9 @@ class MemoryPreferencesController extends DisposableController
     );
 
     addAutoDisposeListener(showChart, () {
-      storage.setValue(_showChartStorageId, showChart.value.toString());
+      safeUnawaited(
+        storage.setValue(_showChartStorageId, showChart.value.toString()),
+      );
 
       ga.select(
         gac.memory,
@@ -55,7 +59,9 @@ class MemoryPreferencesController extends DisposableController
     );
 
     addAutoDisposeListener(refLimit, () {
-      storage.setValue(_refLimitStorageId, refLimit.value.toString());
+      safeUnawaited(
+        storage.setValue(_refLimitStorageId, refLimit.value.toString()),
+      );
 
       ga.select(gac.memory, gac.MemoryEvents.browseRefLimit.name);
     });
diff --git a/packages/devtools_app/lib/src/shared/preferences/_performance_preferences.dart b/packages/devtools_app/lib/src/shared/preferences/_performance_preferences.dart
index 2eed61e53db..32d1abafd88 100644
--- a/packages/devtools_app/lib/src/shared/preferences/_performance_preferences.dart
+++ b/packages/devtools_app/lib/src/shared/preferences/_performance_preferences.dart
@@ -22,9 +22,11 @@ class PerformancePreferencesController extends DisposableController
   @override
   Future<void> init() async {
     addAutoDisposeListener(showFlutterFramesChart, () {
-      storage.setValue(
-        _showFlutterFramesChartId,
-        showFlutterFramesChart.value.toString(),
+      safeUnawaited(
+        storage.setValue(
+          _showFlutterFramesChartId,
+          showFlutterFramesChart.value.toString(),
+        ),
       );
       ga.select(
         gac.performance,
@@ -38,9 +40,11 @@ class PerformancePreferencesController extends DisposableController
     );
 
     addAutoDisposeListener(includeCpuSamplesInTimeline, () {
-      storage.setValue(
-        _includeCpuSamplesInTimelineId,
-        includeCpuSamplesInTimeline.value.toString(),
+      safeUnawaited(
+        storage.setValue(
+          _includeCpuSamplesInTimelineId,
+          includeCpuSamplesInTimeline.value.toString(),
+        ),
       );
       ga.select(
         gac.performance,
diff --git a/packages/devtools_app/lib/src/shared/preferences/preferences.dart b/packages/devtools_app/lib/src/shared/preferences/preferences.dart
index 1602dfd0d76..4f4b436de35 100644
--- a/packages/devtools_app/lib/src/shared/preferences/preferences.dart
+++ b/packages/devtools_app/lib/src/shared/preferences/preferences.dart
@@ -135,9 +135,11 @@ class PreferencesController extends DisposableController
     ga.impression(gac.devToolsMain, gac.startingTheme(darkMode: useDarkMode));
     toggleDarkModeTheme(useDarkMode);
     addAutoDisposeListener(darkModeEnabled, () {
-      storage.setValue(
-        _UiPreferences.darkMode.storageKey,
-        '${darkModeEnabled.value}',
+      safeUnawaited(
+        storage.setValue(
+          _UiPreferences.darkMode.storageKey,
+          '${darkModeEnabled.value}',
+        ),
       );
     });
   }
@@ -149,9 +151,11 @@ class PreferencesController extends DisposableController
     );
     toggleVmDeveloperMode(vmDeveloperModeValue);
     addAutoDisposeListener(vmDeveloperModeEnabled, () {
-      storage.setValue(
-        _UiPreferences.vmDeveloperMode.storageKey,
-        '${vmDeveloperModeEnabled.value}',
+      safeUnawaited(
+        storage.setValue(
+          _UiPreferences.vmDeveloperMode.storageKey,
+          '${vmDeveloperModeEnabled.value}',
+        ),
       );
     });
   }
@@ -237,9 +241,11 @@ class PreferencesController extends DisposableController
     );
     toggleVerboseLogging(verboseLoggingEnabledValue);
     addAutoDisposeListener(verboseLoggingEnabled, () {
-      storage.setValue(
-        _GeneralPreferences.verboseLogging.name,
-        verboseLoggingEnabled.value.toString(),
+      safeUnawaited(
+        storage.setValue(
+          _GeneralPreferences.verboseLogging.name,
+          verboseLoggingEnabled.value.toString(),
+        ),
       );
     });
   }
diff --git a/packages/devtools_app/lib/src/shared/utils/utils.dart b/packages/devtools_app/lib/src/shared/utils/utils.dart
index 8308a32e7f2..f41bf23af67 100644
--- a/packages/devtools_app/lib/src/shared/utils/utils.dart
+++ b/packages/devtools_app/lib/src/shared/utils/utils.dart
@@ -20,6 +20,7 @@ import 'package:vm_service/vm_service.dart';
 
 import '../../../devtools.dart' as devtools;
 import '../../service/connected_app/connected_app.dart';
+import '../framework/app_error_handling.dart';
 import '../globals.dart';
 import '../primitives/query_parameters.dart';
 import '../primitives/utils.dart';
@@ -343,7 +344,7 @@ class InterruptableChunkWorker {
     }
 
     progressCallback(0.0);
-    doChunkWork(0);
+    safeUnawaited(doChunkWork(0));
 
     return completer.future;
   }
@@ -354,3 +355,13 @@ class InterruptableChunkWorker {
 }
 
 String get devToolsVersion => devtools.version;
+
+/// Unawaits the given [future] and catches any errors thrown.
+void safeUnawaited(
+  Future<void> future, {
+  void Function(Object?, StackTrace)? onError,
+}) {
+  onError ??=
+      (e, st) => reportError('Error in unawaited Future: $e', stack: st);
+  unawaited(future.catchError(onError));
+}
diff --git a/packages/devtools_app/test/screens/vm_developer/object_inspector/vm_developer_common_widgets_test.dart b/packages/devtools_app/test/screens/vm_developer/object_inspector/vm_developer_common_widgets_test.dart
index e886f590d66..f2fe081c20e 100644
--- a/packages/devtools_app/test/screens/vm_developer/object_inspector/vm_developer_common_widgets_test.dart
+++ b/packages/devtools_app/test/screens/vm_developer/object_inspector/vm_developer_common_widgets_test.dart
@@ -193,8 +193,8 @@ void main() {
           RetainingPathWidget(
             controller: testObjectInspectorViewController,
             retainingPath: mockClassObject.retainingPath,
-            onExpanded: (bool _) {
-              mockClassObject.requestRetainingPath();
+            onExpanded: (bool _) async {
+              await mockClassObject.requestRetainingPath();
             },
           ),
         ),
diff --git a/packages/devtools_app/test/test_infra/fixtures/networking_app/bin/main.dart b/packages/devtools_app/test/test_infra/fixtures/networking_app/bin/main.dart
index 29b525d1e51..48dd5da9214 100644
--- a/packages/devtools_app/test/test_infra/fixtures/networking_app/bin/main.dart
+++ b/packages/devtools_app/test/test_infra/fixtures/networking_app/bin/main.dart
@@ -21,10 +21,10 @@ void main() async {
 /// This server can receive requests, and responds to them.
 Future<io.HttpServer> _bindTestServer() async {
   final server = await io.HttpServer.bind(io.InternetAddress.loopbackIPv4, 0);
-  server.listen((request) {
+  server.listen((request) async {
     request.response.write('fallthrough');
     if (request.uri.path.contains('complete/')) {
-      request.response.close();
+      await request.response.close();
     }
   });
   return server;
@@ -39,7 +39,7 @@ Future<io.HttpServer> _bindControlServer(io.HttpServer testServer) async {
 
   final server = await io.HttpServer.bind(io.InternetAddress.loopbackIPv4, 0);
   print(json.encode({'controlPort': server.port}));
-  server.listen((request) {
+  server.listen((request) async {
     request.response.headers
       ..add('Access-Control-Allow-Origin', '*')
       ..add('Access-Control-Allow-Methods', 'POST,GET,DELETE,PUT,OPTIONS');
@@ -69,7 +69,7 @@ Future<io.HttpServer> _bindControlServer(io.HttpServer testServer) async {
       client.close();
       io.exit(0);
     }
-    request.response.close();
+    await request.response.close();
   });
   return server;
 }
diff --git a/packages/devtools_app_shared/test/ui/split_pane_test.dart b/packages/devtools_app_shared/test/ui/split_pane_test.dart
index 62a512cd02a..326d3c477e9 100644
--- a/packages/devtools_app_shared/test/ui/split_pane_test.dart
+++ b/packages/devtools_app_shared/test/ui/split_pane_test.dart
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd.
 
+import 'dart:async';
+
 import 'package:devtools_app_shared/ui.dart';
 import 'package:devtools_app_shared/utils.dart';
 import 'package:flutter/material.dart';
@@ -1161,7 +1163,8 @@ void main() {
             wrap(
               Builder(
                 builder: (context) {
-                  expectLater(SplitPane.axisFor(context, 1.0), Axis.horizontal);
+                  unawaited(expectLater(
+                      SplitPane.axisFor(context, 1.0), Axis.horizontal));
                   return Container();
                 },
               ),
@@ -1177,7 +1180,8 @@ void main() {
             wrap(
               Builder(
                 builder: (context) {
-                  expectLater(SplitPane.axisFor(context, 1.0), Axis.vertical);
+                  unawaited(expectLater(
+                      SplitPane.axisFor(context, 1.0), Axis.vertical));
                   return Container();
                 },
               ),
diff --git a/packages/devtools_shared/lib/src/service/service.dart b/packages/devtools_shared/lib/src/service/service.dart
index d8719b5a9cc..91db01f0514 100644
--- a/packages/devtools_shared/lib/src/service/service.dart
+++ b/packages/devtools_shared/lib/src/service/service.dart
@@ -34,9 +34,9 @@ Future<T> _connectWithSse<T extends VmService>({
   );
 
   unawaited(
-    client.sink!.done.whenComplete(() {
+    client.sink!.done.whenComplete(() async {
       finishedCompleter.complete();
-      service.dispose();
+      await service.dispose();
     }),
   );
   serviceCompleter.complete(service);
@@ -70,9 +70,9 @@ Future<T> _connectWithWebSocket<T extends VmService>({
   }
   unawaited(
     ws.sink.done.then(
-      (_) {
+      (_) async {
         finishedCompleter.complete();
-        service.dispose();
+        await service.dispose();
       },
       onError: onError,
     ),
@@ -113,15 +113,15 @@ Future<T> connect<T extends VmService>({
     return service;
   }
 
-  connectHelper().then(
+  unawaited(connectHelper().then(
     (service) => connectedCompleter.safeComplete(service),
     onError: onError,
-  );
-  finishedCompleter.future.then((_) {
+  ));
+  unawaited(finishedCompleter.future.then((_) {
     // It is an error if we finish before we are connected.
     if (!connectedCompleter.isCompleted) {
       onError(null);
     }
-  });
+  }));
   return connectedCompleter.future;
 }


Reviews:
user: login: kenzieschmoll
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-07T21:04:24Z
user: login: kenzieschmoll
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-07T21:04:32Z
user: login: kenzieschmoll
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-07T21:05:22Z
user: login: kenzieschmoll
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-07T21:06:04Z
user: login: elliette
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-08T18:23:11Z
user: login: elliette
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-08T18:23:31Z
user: login: elliette
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-08T18:24:03Z
user: login: elliette
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-08T18:24:15Z
user: login: kenzieschmoll
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-08T19:17:36Z
user: login: elliette
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-08T20:16:01Z
user: login: elliette
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-08T20:16:40Z
user: login: kenzieschmoll
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-08T21:47:41Z
user: login: elliette
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-08T22:00:59Z
user: login: kenzieschmoll
body: lgtm with one question
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-08T23:53:14Z
user: login: elliette
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-10T19:11:56Z

Review comments:
diff_hunk: @@ -103,9 +103,9 @@ class InspectorController extends DisposableController
     if (parent == null) {
       // If select mode is available, enable the on device inspector as it
       // won't interfere with users.
-      addAutoDisposeListener(_supportsToggleSelectWidgetMode, () {
+      addAutoDisposeListener(_supportsToggleSelectWidgetMode, () async {
         if (_supportsToggleSelectWidgetMode.value) {
-          serviceConnection.serviceManager.serviceExtensionManager
+          await serviceConnection.serviceManager.serviceExtensionManager
path: packages/devtools_app/lib/src/screens/inspector/inspector_controller.dart
user: login: kenzieschmoll
body: do we want to await this or was this intentionally unawaited?
created_at: 2025-04-07T21:04:24Z
author_association: MEMBER
diff_hunk: @@ -110,9 +110,9 @@ class InspectorController extends DisposableController
 
     // If select mode is available, enable the on device inspector as it
     // won't interfere with users.
-    addAutoDisposeListener(_supportsToggleSelectWidgetMode, () {
+    addAutoDisposeListener(_supportsToggleSelectWidgetMode, () async {
       if (_supportsToggleSelectWidgetMode.value) {
-        serviceConnection.serviceManager.serviceExtensionManager
+        await serviceConnection.serviceManager.serviceExtensionManager
path: packages/devtools_app/lib/src/screens/inspector_v2/inspector_controller.dart
user: login: kenzieschmoll
body: same
created_at: 2025-04-07T21:04:32Z
author_association: MEMBER
diff_hunk: @@ -354,3 +355,12 @@ class InterruptableChunkWorker {
 }
 
 String get devToolsVersion => devtools.version;
+
+void safeUnawaited(
path: packages/devtools_app/lib/src/shared/utils/utils.dart
user: login: kenzieschmoll
body: add dartdoc
created_at: 2025-04-07T21:05:22Z
author_association: MEMBER
diff_hunk: @@ -154,9 +155,9 @@ class _TraceWidgetBuildsSettingState extends State<TraceWidgetBuildsSetting>
                     .serviceExtensionManager
                     .getServiceExtensionState(extension.extension);
 
-                _updateForServiceExtensionState(state.value, type);
-                addAutoDisposeListener(state, () {
-                  _updateForServiceExtensionState(state.value, type);
+                await _updateForServiceExtensionState(state.value, type);
+                addAutoDisposeListener(state, () async {
+                  await _updateForServiceExtensionState(state.value, type);
path: packages/devtools_app/lib/src/screens/performance/panes/controls/enhance_tracing/enhance_tracing.dart
user: login: kenzieschmoll
body: same question regarding whether these were intentionally unawaited
created_at: 2025-04-07T21:06:04Z
author_association: MEMBER
diff_hunk: @@ -94,10 +94,10 @@ class InspectorScreenBodyState extends State<InspectorScreenBody>
         searchPreventClose = false;
       }
     });
-    addAutoDisposeListener(preferences.inspector.pubRootDirectories, () {
+    addAutoDisposeListener(preferences.inspector.pubRootDirectories, () async {
       if (serviceConnection.serviceManager.connectedState.value.connected &&
           controller.firstInspectorTreeLoadCompleted) {
-        controller.refreshInspector();
+        await controller.refreshInspector();
path: packages/devtools_app/lib/src/screens/inspector_v2/inspector_screen_body.dart
user: login: elliette
body: I believe it is better to await the tree refresh here to avoid race conditions
created_at: 2025-04-08T18:23:10Z
author_association: MEMBER
diff_hunk: @@ -188,8 +189,8 @@ class VmServiceWrapper extends VmService {
   }
 
   @override
-  Future<Success> streamCancel(String streamId) {
-    _activeStreams.remove(streamId);
+  Future<Success> streamCancel(String streamId) async {
+    await _activeStreams.remove(streamId);
path: packages/devtools_app/lib/src/service/vm_service_wrapper.dart
user: login: elliette
body: This looks like it should have always been awaited
created_at: 2025-04-08T18:23:30Z
author_association: MEMBER
diff_hunk: @@ -34,9 +34,9 @@ Future<T> _connectWithSse<T extends VmService>({
   );
 
   unawaited(
-    client.sink!.done.whenComplete(() {
+    client.sink!.done.whenComplete(() async {
       finishedCompleter.complete();
-      service.dispose();
+      await service.dispose();
path: packages/devtools_shared/lib/src/service/service.dart
user: login: elliette
body: I think we should wait for dispose after the connection is closed here
created_at: 2025-04-08T18:24:03Z
author_association: MEMBER
diff_hunk: @@ -70,9 +70,9 @@ Future<T> _connectWithWebSocket<T extends VmService>({
   }
   unawaited(
     ws.sink.done.then(
-      (_) {
+      (_) async {
         finishedCompleter.complete();
-        service.dispose();
+        await service.dispose();
path: packages/devtools_shared/lib/src/service/service.dart
user: login: elliette
body: Same comment as above here
created_at: 2025-04-08T18:24:15Z
author_association: MEMBER
diff_hunk: @@ -94,10 +94,10 @@ class InspectorScreenBodyState extends State<InspectorScreenBody>
         searchPreventClose = false;
       }
     });
-    addAutoDisposeListener(preferences.inspector.pubRootDirectories, () {
+    addAutoDisposeListener(preferences.inspector.pubRootDirectories, () async {
       if (serviceConnection.serviceManager.connectedState.value.connected &&
           controller.firstInspectorTreeLoadCompleted) {
-        controller.refreshInspector();
+        await controller.refreshInspector();
path: packages/devtools_app/lib/src/screens/inspector_v2/inspector_screen_body.dart
user: login: kenzieschmoll
body: Is the closure awaited when the listener is called in the auto dispose logic? If not, this await won't matter and we should add a comment or explicitly mark unawaited
created_at: 2025-04-08T19:14:13Z
author_association: MEMBER
diff_hunk: @@ -149,9 +151,11 @@ class PreferencesController extends DisposableController
     );
     toggleVmDeveloperMode(vmDeveloperModeValue);
     addAutoDisposeListener(vmDeveloperModeEnabled, () {
-      storage.setValue(
-        _UiPreferences.vmDeveloperMode.storageKey,
-        '${vmDeveloperModeEnabled.value}',
+      safeUnawaited(
+        storage.setValue(
path: packages/devtools_app/lib/src/shared/preferences/preferences.dart
user: login: kenzieschmoll
body: do we ever await storage.setValue? If not, we could change the return type of setValue to void and then add the safeUnawatied wrapper inside that method.
created_at: 2025-04-08T19:15:54Z
author_association: MEMBER
diff_hunk: @@ -94,10 +94,10 @@ class InspectorScreenBodyState extends State<InspectorScreenBody>
         searchPreventClose = false;
       }
     });
-    addAutoDisposeListener(preferences.inspector.pubRootDirectories, () {
+    addAutoDisposeListener(preferences.inspector.pubRootDirectories, () async {
       if (serviceConnection.serviceManager.connectedState.value.connected &&
           controller.firstInspectorTreeLoadCompleted) {
-        controller.refreshInspector();
+        await controller.refreshInspector();
path: packages/devtools_app/lib/src/screens/inspector_v2/inspector_screen_body.dart
user: login: elliette
body: Ah good point, it's not. Switched to `unawaited`
created_at: 2025-04-08T20:16:01Z
author_association: MEMBER
diff_hunk: @@ -149,9 +151,11 @@ class PreferencesController extends DisposableController
     );
     toggleVmDeveloperMode(vmDeveloperModeValue);
     addAutoDisposeListener(vmDeveloperModeEnabled, () {
-      storage.setValue(
-        _UiPreferences.vmDeveloperMode.storageKey,
-        '${vmDeveloperModeEnabled.value}',
+      safeUnawaited(
+        storage.setValue(
path: packages/devtools_app/lib/src/shared/preferences/preferences.dart
user: login: elliette
body: Good point, `setValue` now returns `void` instead of `Future<void>`
created_at: 2025-04-08T20:16:40Z
author_association: MEMBER
diff_hunk: @@ -149,9 +151,11 @@ class PreferencesController extends DisposableController
     );
     toggleVmDeveloperMode(vmDeveloperModeValue);
     addAutoDisposeListener(vmDeveloperModeEnabled, () {
-      storage.setValue(
-        _UiPreferences.vmDeveloperMode.storageKey,
-        '${vmDeveloperModeEnabled.value}',
+      safeUnawaited(
+        storage.setValue(
path: packages/devtools_app/lib/src/shared/preferences/preferences.dart
user: login: kenzieschmoll
body: It looks like we were awaiting `storage.setValue` in some places though. My comment was only if we weren't  awaiting this method call anywhere but it looks like we were. I don't know if we want to change the behavior of places where we were awaiting.
created_at: 2025-04-08T21:47:41Z
author_association: MEMBER
diff_hunk: @@ -149,9 +151,11 @@ class PreferencesController extends DisposableController
     );
     toggleVmDeveloperMode(vmDeveloperModeValue);
     addAutoDisposeListener(vmDeveloperModeEnabled, () {
-      storage.setValue(
-        _UiPreferences.vmDeveloperMode.storageKey,
-        '${vmDeveloperModeEnabled.value}',
+      safeUnawaited(
+        storage.setValue(
path: packages/devtools_app/lib/src/shared/preferences/preferences.dart
user: login: elliette
body: Ah got it! Reverted those changes
created_at: 2025-04-08T22:00:59Z
author_association: MEMBER
diff_hunk: @@ -142,10 +143,10 @@ class _TraceWidgetBuildsSettingState extends State<TraceWidgetBuildsSetting>
     for (final type in TraceWidgetBuildsScope.values) {
       final extension = _traceWidgetBuildsExtensions[type]!;
 
-      unawaited(
+      safeUnawaited(
         serviceConnection.serviceManager.serviceExtensionManager
             .waitForServiceExtensionAvailable(extension.extension)
-            .then((isServiceAvailable) {
+            .then((isServiceAvailable) async {
path: packages/devtools_app/lib/src/screens/performance/panes/controls/enhance_tracing/enhance_tracing.dart
user: login: kenzieschmoll
body: is this async still needed?
created_at: 2025-04-08T23:52:19Z
author_association: MEMBER
diff_hunk: @@ -142,10 +143,10 @@ class _TraceWidgetBuildsSettingState extends State<TraceWidgetBuildsSetting>
     for (final type in TraceWidgetBuildsScope.values) {
       final extension = _traceWidgetBuildsExtensions[type]!;
 
-      unawaited(
+      safeUnawaited(
         serviceConnection.serviceManager.serviceExtensionManager
             .waitForServiceExtensionAvailable(extension.extension)
-            .then((isServiceAvailable) {
+            .then((isServiceAvailable) async {
path: packages/devtools_app/lib/src/screens/performance/panes/controls/enhance_tracing/enhance_tracing.dart
user: login: elliette
body: good catch, no!
created_at: 2025-04-10T19:11:56Z
author_association: MEMBER

PR comments:
user: login: kenzieschmoll
created_at: 2025-04-07T22:57:21Z
body: Looking at this PR further, I worry we are changing the functionality of the code to enable this lint. Some of this code looks like it should be intentionally unawaited so that it does not block the UI thread.
author_association: MEMBER
user: login: elliette
created_at: 2025-04-07T23:09:42Z
body: > Looking at this PR further, I worry we are changing the functionality of the code to enable this lint. Some of this code looks like it should be intentionally unawaited so that it does not block the UI thread.

Hmm I will go through these changes again and update the ones which like they should be `unawaited`. Will respond when this is ready for re-review!
author_association: MEMBER
user: login: elliette
created_at: 2025-04-08T18:25:22Z
body: Switched most futures from `awaited` to `unawaited`, the ones where I didn't I've left comments for why I think they should be `awaited`
author_association: MEMBER