From a00f56457504156168f2d78aaa73adf5b042b033 Mon Sep 17 00:00:00 2001 From: AIlhomov Date: Sun, 8 Nov 2026 20:26:13 +0100 Subject: [PATCH] Explain unavailable debugging for unattached Flutter apps --- .../lib/src/shared/framework/screen.dart | 19 +++++++++++ .../release_notes/NEXT_RELEASE_NOTES.md | 4 +++ .../framework/visible_screens_test.dart | 32 +++++++++++++++++++ 3 files changed, 55 insertions(+) diff --git a/packages/devtools_app/lib/src/shared/framework/screen.dart b/packages/devtools_app/lib/src/shared/framework/screen.dart index c00f88bb6ae..e0ec4c614a6 100644 --- a/packages/devtools_app/lib/src/shared/framework/screen.dart +++ b/packages/devtools_app/lib/src/shared/framework/screen.dart @@ -9,6 +9,7 @@ library; import 'package:collection/collection.dart'; import 'package:devtools_app_shared/service.dart'; +import 'package:devtools_app_shared/service_extensions.dart' as extensions; import 'package:devtools_app_shared/shared.dart'; import 'package:devtools_app_shared/ui.dart'; import 'package:flutter/foundation.dart'; @@ -558,6 +559,18 @@ abstract class Screen { return (show: false, disabledReason: ScreenDisabledReason.requiresFlutter); } if (screen.requiresDebugBuild && connectedApp.isProfileBuildNow == true) { + // Build mode is inferred from expression evaluation. A debug-only service + // extension identifies a debug app whose expressions could not be compiled, + // for example when connecting without Flutter Tools. + if (connectedApp.isFlutterAppNow == true && + serviceManager.serviceExtensionManager.isServiceExtensionAvailable( + extensions.debugAllowBanner.extension, + )) { + return ( + show: false, + disabledReason: ScreenDisabledReason.expressionEvaluationUnavailable, + ); + } _log.finest('screen requires debug build: returning false'); return ( show: false, @@ -598,6 +611,12 @@ enum ScreenDisabledReason { requiresDartLibrary(null), requiresDartVm('requires the Dart VM, but it is not available.'), requiresDebugBuild('only supports debug builds.'), + + /// A Flutter debug app was detected, but expression evaluation failed. + expressionEvaluationUnavailable( + 'could not evaluate expressions. Try running "flutter attach" and ' + 'reconnecting DevTools.', + ), requiresFlutter('only supports Flutter applications.'), requiresAdvancedDeveloperMode( 'only works when Advanced Developer Mode is enabled', diff --git a/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md b/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md index 86b2a491380..3abdfddc6d8 100644 --- a/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md +++ b/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md @@ -15,6 +15,10 @@ To learn more about DevTools, check out the ## General updates +* Added guidance to run "flutter attach" when a Flutter debug app cannot + evaluate expressions and a debug-only screen is unavailable. + [#4091](https://github.com/flutter/devtools/issues/4091) + TODO: Remove this section if there are not any updates. * Improved the readability of ANSI-colored text in the console and logging diff --git a/packages/devtools_app/test/shared/framework/visible_screens_test.dart b/packages/devtools_app/test/shared/framework/visible_screens_test.dart index 1bd1eb56559..cb904698f66 100644 --- a/packages/devtools_app/test/shared/framework/visible_screens_test.dart +++ b/packages/devtools_app/test/shared/framework/visible_screens_test.dart @@ -4,6 +4,7 @@ import 'package:devtools_app/devtools_app.dart'; import 'package:devtools_app/src/shared/framework/framework_controller.dart'; +import 'package:devtools_app_shared/service_extensions.dart' as extensions; import 'package:devtools_app_shared/utils.dart'; import 'package:devtools_shared/devtools_shared.dart'; import 'package:devtools_test/devtools_test.dart'; @@ -166,6 +167,37 @@ void main() { ); }); + testWidgets('explain unavailable evaluation in an unattached debug app', ( + WidgetTester tester, + ) async { + // Failed expression evaluation makes the build-mode heuristic report + // profile mode, even though debug-only extensions are registered. + setupMockConnectedApp(flutter: true, debugMode: false); + final extensionManager = + fakeServiceConnection.serviceManager.serviceExtensionManager; + // The extension may still be pending its first frame. + await extensionManager.fakeAddServiceExtension( + extensions.debugAllowBanner.extension, + ); + + final result = shouldShowScreen(DebuggerScreen()); + expect(result.show, isFalse); + expect(result.disabledReason?.message, contains('flutter attach')); + expect( + result.disabledReason?.message, + isNot(contains('only supports debug')), + ); + }); + + testWidgets('keep the profile-build explanation for a real profile app', ( + WidgetTester tester, + ) async { + setupMockConnectedApp(flutter: true, debugMode: false); + final result = shouldShowScreen(DebuggerScreen()); + expect(result.show, isFalse); + expect(result.disabledReason?.message, 'only supports debug builds.'); + }); + testWidgets('are correct for Flutter (non-web) profile app', ( WidgetTester tester, ) async {