Skip to content

Add a platform override dropdown to the Inspector toolbar - #10061

Open
Aadeesh-01 wants to merge 2 commits into
flutter:masterfrom
Aadeesh-01:inspector-platform-toggle
Open

Aadeesh-01 wants to merge 2 commits into
flutter:masterfrom
Aadeesh-01:inspector-platform-toggle

Conversation

@Aadeesh-01

Copy link
Copy Markdown

Fixes #2780

Adds a dropdown to the Flutter Inspector toolbar that overrides the target platform of the connected app, using the existing ext.flutter.platformOverride service extension (togglePlatformMode). This brings back the platform switch that the IntelliJ Inspector had.

Changes

  • New PlatformOverrideDropdown in service_extension_widgets.dart, built on AnalyticsDropDownButton (dense, with an icon and name for each platform).
    • Reads the current platform from getServiceExtensionState and sets it with setServiceExtensionState, so it stays in sync when the app changes the platform itself.
    • Hidden when hasServiceExtension is false, for example in profile or release mode.
    • Fuchsia is only listed when the app already uses it, as the old TogglePlatformSelector did.
    • If the app reports a platform that DevTools does not list, that platform is added to the menu so DropdownButton does not throw.
  • Placed in InspectorServiceExtensionButtonGroup, before the overlay buttons.
  • Added windows to togglePlatformMode values in devtools_app_shared, with a CHANGELOG entry. Without it, after switching a Windows app to another platform there was no way to switch back.
  • Shortened the display values to iOS, Android, macOS, … because the icon and tooltip already give context. This also fixes MacOS to macOS.
  • Removed the TODO(#2780) and ignore: unused-code comments on togglePlatformMode.
  • Added a release note.

Screenshots

Dropdown in the toolbar Menu open
Screenshot 2026-10-31 231322 Screenshot 2026-10-31 231331

Questions for reviewers

  1. No "Default (no override)" option. After a change, Flutter reports the platform now in effect, and ServiceExtensionManager treats multi-value extensions as enabled, so a "Default" entry would not stay selected. Picking your own platform works as a reset. I'm happy to add a default option if you have an approach you prefer.
  2. Legacy code. I left the commented-out ServiceExtensionSelector / TogglePlatformSelector block in service_extension_widgets.dart. This PR is the Flutter version that its TODO asks for, so I can delete it here if you'd like.
  3. Placement. I followed the issue and put the dropdown in the Inspector toolbar. Since the override applies to the whole app, it could also live in the global app bar.

Tests

  • test/service/service_extension_widgets_test.dart: switching the platform calls the extension with the new value; Windows is listed and Fuchsia is not; an unknown platform reported by the app is listed; the dropdown is hidden when the extension is unavailable.
  • test/screens/inspector/inspector_screen_test.dart: the dropdown appears in the Inspector toolbar.
  • Manually verified against a Flutter Windows desktop app: switching between all platforms, switching back to Windows, and the dropdown updating when the app changes the platform itself.

Pre-launch Checklist

General checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read the Flutter Style Guide recently, and have followed its advice.
  • I signed the CLA.
  • I updated/added relevant documentation (doc comments with ///).

Issues checklist

Tests checklist

  • I added new tests to check the change I am making...
  • OR there is a reason for not adding tests, which I explained in the PR description.

AI-tooling checklist

  • I did not use any AI tooling in creating this PR.
  • OR I did use AI tooling, and...
    • I read the AI contributions guidelines and agree to follow them.
    • I reviewed all AI-generated code before opening this PR.
    • I understand and am able to discuss the code in this PR.
    • I have verifed the accuracy of any AI-generated text included in the PR description.
    • I commit to verifying the accuracy of any AI-generated code or text that I upload in response to review comments.

Feature-change checklist

  • This PR does not change the DevTools UI or behavior and...
    • I added the release-notes-not-required label or left a comment requesting the label be added.
  • OR this PR does change the DevTools UI or behavior and...
    • I added an entry to packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md.
    • I included before/after screenshots and/or a GIF demo of the new UI to my PR description.
    • I ran the DevTools app locally to manually verify my changes.

build.yaml badge

If you need help, consider asking for help on Discord.

🤖 Generated with Claude Code

@Aadeesh-01
Aadeesh-01 requested review from a team, kenzieschmoll and srawlins as code owners October 31, 2026 17:57

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a new PlatformOverrideDropdown widget to the DevTools Inspector toolbar, allowing users to override the target platform of a connected Flutter app. It also adds 'windows' to the supported platforms and updates the relevant tests, release notes, and changelogs. The feedback suggests aligning the implementation with the DevTools Style Guide by using MultiValueListenableBuilder instead of nested ValueListenableBuilders, and replacing unawaited with safeUnawaited for fire-and-forget futures.

Comment on lines +1238 to +1265
return ValueListenableBuilder<bool>(
valueListenable: extensionManager.hasServiceExtension(extensionName),
builder: (context, available, _) {
if (!available) return const SizedBox.shrink();
return ValueListenableBuilder<ServiceExtensionState>(
valueListenable: extensionManager.getServiceExtensionState(
extensionName,
),
builder: (context, state, _) {
// The value is null until DevTools has read it from the app.
final current = state.value as String?;
return AnalyticsDropDownButton<String>(
key: dropdownKey,
gaScreen: gac.inspector,
gaDropDownId: gac.togglePlatform,
message: togglePlatformMode.tooltip,
isDense: true,
value: current,
items: [
for (final platform in _platformsToShow(current))
_buildMenuItem(platform),
],
onChanged: _onPlatformChanged,
);
},
);
},
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

[CONCERN] Use the MultiValueListenableBuilder helper instead of nesting ValueListenableBuilders, as recommended by the DevTools Style Guide.

    return MultiValueListenableBuilder(
      listenables: [
        extensionManager.hasServiceExtension(extensionName),
        extensionManager.getServiceExtensionState(extensionName),
      ],
      builder: (context, values, _) {
        final available = values[0] as bool;
        if (!available) return const SizedBox.shrink();
        final state = values[1] as ServiceExtensionState;
        // The value is null until DevTools has read it from the app.
        final current = state.value as String?;
        return AnalyticsDropDownButton<String>(
          key: dropdownKey,
          gaScreen: gac.inspector,
          gaDropDownId: gac.togglePlatform,
          message: togglePlatformMode.tooltip,
          isDense: true,
          value: current,
          items: [
            for (final platform in _platformsToShow(current))
              _buildMenuItem(platform),
          ],
          onChanged: _onPlatformChanged,
        );
      },
    );
References
  1. Prefer MultiValueListenableBuilder instead of nested ValueListenableBuilders. (link)

Comment on lines +1316 to +1323
unawaited(
serviceConnection.serviceManager.serviceExtensionManager
.setServiceExtensionState(
togglePlatformMode.extension,
enabled: true,
value: platform,
),
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

[CONCERN] Use safeUnawaited instead of unawaited for fire-and-forget futures, as recommended by the DevTools Style Guide.

    safeUnawaited(
      serviceConnection.serviceManager.serviceExtensionManager
          .setServiceExtensionState(
            togglePlatformMode.extension,
            enabled: true,
            value: platform,
          ),
    );
References
  1. Prefer safeUnawaited(...) for fire-and-forget futures. (link)

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add platform toggle to the inspector

1 participant