Skip to content

Fixes the issue of DevTools windows not appearing #8151

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion flutter-idea/src/io/flutter/FlutterInitializer.java
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,7 @@ public void actionPerformed(@NotNull AnActionEvent event) {
});
}

private void initializeToolWindows(@NotNull Project project) {
private void initializeToolWindows(@NotNull final Project project) {
// Start watching for Flutter debug active events.
FlutterViewFactory.init(project);
RemainingDevToolsViewFactory.init(project);
Expand Down
88 changes: 31 additions & 57 deletions flutter-idea/src/io/flutter/deeplinks/DeepLinksViewFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,75 +6,49 @@
package io.flutter.deeplinks;

import com.intellij.openapi.project.Project;
import com.intellij.openapi.wm.ToolWindow;
import com.intellij.openapi.wm.ToolWindowFactory;
import io.flutter.FlutterUtils;
import io.flutter.actions.RefreshToolWindowAction;
import io.flutter.bazel.WorkspaceCache;
import io.flutter.devtools.AbstractDevToolsViewFactory;
import io.flutter.devtools.DevToolsIdeFeature;
import io.flutter.devtools.DevToolsUrl;
import io.flutter.run.daemon.DevToolsService;
import io.flutter.sdk.FlutterSdk;
import io.flutter.run.daemon.DevToolsInstance;
import io.flutter.sdk.FlutterSdkVersion;
import io.flutter.utils.AsyncUtils;
import io.flutter.utils.OpenApiUtils;
import kotlin.coroutines.Continuation;
import org.jetbrains.annotations.NotNull;

import java.util.List;
import java.util.Optional;
public class DeepLinksViewFactory extends AbstractDevToolsViewFactory {
@NotNull public static String TOOL_WINDOW_ID = "Flutter Deep Links";

public class DeepLinksViewFactory implements ToolWindowFactory {
@NotNull private static String TOOL_WINDOW_ID = "Flutter Deep Links";
@NotNull public static String DEVTOOLS_PAGE_ID = "deep-links";

@Override
public Object isApplicableAsync(@NotNull Project project, @NotNull Continuation<? super Boolean> $completion) {
FlutterSdk sdk = FlutterSdk.getFlutterSdk(project);
FlutterSdkVersion sdkVersion = sdk == null ? null : sdk.getVersion();
return sdkVersion != null && sdkVersion.canUseDeepLinksTool();
public boolean versionSupportsThisTool(@NotNull final FlutterSdkVersion flutterSdkVersion) {
return flutterSdkVersion.canUseDeepLinksTool();
}

@Override
public void createToolWindowContent(@NotNull Project project, @NotNull ToolWindow toolWindow) {
FlutterSdk sdk = FlutterSdk.getFlutterSdk(project);
FlutterSdkVersion sdkVersion = sdk == null ? null : sdk.getVersion();

AsyncUtils.whenCompleteUiThread(
DevToolsService.getInstance(project).getDevToolsInstance(),
(instance, error) -> {
// Skip displaying if the project has been closed.
if (!project.isOpen()) {
return;
}

if (error != null) {
return;
}

if (instance == null) {
return;
}

final DevToolsUrl devToolsUrl = new DevToolsUrl.Builder()
.setDevToolsHost(instance.host())
.setDevToolsPort(instance.port())
.setPage("deep-links")
.setEmbed(true)
.setFlutterSdkVersion(sdkVersion)
.setWorkspaceCache(WorkspaceCache.getInstance(project))
.setIdeFeature(DevToolsIdeFeature.TOOL_WINDOW)
.build();
@NotNull
public String getToolWindowId() {
return TOOL_WINDOW_ID;
}

OpenApiUtils.safeInvokeLater(() -> {
Optional.ofNullable(
FlutterUtils.embeddedBrowser(project))
.ifPresent(embeddedBrowser -> embeddedBrowser.openPanel(toolWindow, "Deep Links", devToolsUrl, System.out::println));
});
}
);
@Override
@NotNull
public String getToolWindowTitle() {
return "Deep Links";
}

// TODO(helin24): It may be better to add this to the gear actions or to attach as a mouse event on individual tabs within a tool
// window, but I wasn't able to get either working immediately.
toolWindow.setTitleActions(List.of(new RefreshToolWindowAction(TOOL_WINDOW_ID)));
@Override
@NotNull
public DevToolsUrl getDevToolsUrl(@NotNull final Project project,
@NotNull final FlutterSdkVersion flutterSdkVersion,
@NotNull final DevToolsInstance instance) {
return new DevToolsUrl.Builder()
.setDevToolsHost(instance.host())
.setDevToolsPort(instance.port())
.setPage(DEVTOOLS_PAGE_ID)
.setEmbed(true)
.setFlutterSdkVersion(flutterSdkVersion)
.setWorkspaceCache(WorkspaceCache.getInstance(project))
.setIdeFeature(DevToolsIdeFeature.TOOL_WINDOW)
.build();
}
}
}
135 changes: 135 additions & 0 deletions flutter-idea/src/io/flutter/devtools/AbstractDevToolsViewFactory.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/*
* Copyright 2025 The Chromium Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
package io.flutter.devtools;

import com.intellij.openapi.project.Project;
import com.intellij.openapi.wm.ToolWindow;
import com.intellij.openapi.wm.ToolWindowFactory;
import io.flutter.FlutterUtils;
import io.flutter.actions.RefreshToolWindowAction;
import io.flutter.run.daemon.DevToolsInstance;
import io.flutter.run.daemon.DevToolsService;
import io.flutter.sdk.FlutterSdk;
import io.flutter.sdk.FlutterSdkVersion;
import io.flutter.utils.AsyncUtils;
import io.flutter.utils.OpenApiUtils;
import io.flutter.view.EmbeddedBrowser;
import io.flutter.view.ViewUtils;
import kotlin.coroutines.Continuation;
import org.jetbrains.annotations.NotNull;

import java.util.List;
import java.util.Optional;

public abstract class AbstractDevToolsViewFactory implements ToolWindowFactory {

@NotNull
protected final ViewUtils viewUtils = new ViewUtils();

public abstract boolean versionSupportsThisTool(@NotNull FlutterSdkVersion flutterSdkVersion);

@NotNull
public abstract String getToolWindowId();

@NotNull
public abstract String getToolWindowTitle();

@NotNull
public abstract DevToolsUrl getDevToolsUrl(@NotNull Project project,
@NotNull FlutterSdkVersion flutterSdkVersion,
@NotNull DevToolsInstance instance);

protected void doAfterBrowserOpened(@NotNull Project project, @NotNull EmbeddedBrowser browser) {}

@Override
public Object isApplicableAsync(@NotNull Project project, @NotNull Continuation<? super Boolean> $completion) {
// Due to https://github.com/flutter/flutter/issues/142521, this always returns true when the
// Flutter IJ plugin is installed.

// The logic which asserts that the Flutter SDK is up to date enough for this particular feature
// is captured in the implementation of createToolWindowContent() below.

return true;
}

@Override
public void createToolWindowContent(@NotNull Project project, @NotNull ToolWindow toolWindow) {
final FlutterSdk flutterSdk = FlutterSdk.getFlutterSdk(project);
final FlutterSdkVersion flutterSdkVersion = flutterSdk == null ? null : flutterSdk.getVersion();

// There are four potential states for the Flutter SDK:
// 1. The Flutter SDK is null (flutterSdk == null) (an SDK path is invalid or not configured)
// 2. The Flutter SDK exists, but version file information is unavailable (flutterSdkVersion == null),
// see https://github.com/flutter/flutter/issues/142521.
// 3. The Flutter SDK exists, and the version file information is available, but this tool is not
// available on the version of the SDK that the user has.
// 4. The Flutter SDK exists with valid version information.

// First case:
if (flutterSdk == null) {
viewUtils.presentLabels(toolWindow, List.of("Set the Flutter SDK path in",
"Settings > Languages & Frameworks > Flutter,",
"and then restart the IDE."));
return;
}

// Second case:
if (flutterSdk.getVersion().fullVersion().equals(FlutterSdkVersion.UNKNOWN_VERSION)) {
viewUtils.presentLabels(toolWindow, List.of("A Flutter SDK was found at the location",
"specified in the settings, however the directory",
"is in an incomplete state. To fix, shut down the IDE,",
"run `flutter doctor` or `flutter --version`",
"and then restart the IDE."));
return;
}

// Third case:
if (!versionSupportsThisTool(flutterSdkVersion)) {
final String versionText = flutterSdkVersion.fullVersion();
viewUtils.presentLabels(toolWindow, List.of("The version of your Flutter SDK,",
versionText + ",",
"is not recent enough to use this tool.",
"Update the Flutter SDK, `flutter upgrade`,",
"and then restart the IDE."));
return;
}

// Final case:
AsyncUtils.whenCompleteUiThread(
DevToolsService.getInstance(project).getDevToolsInstance(),
(instance, error) -> {
// Skip displaying if the project has been closed.
if (!project.isOpen()) {
return;
}

if (error != null) {
return;
}

if (instance == null) {
return;
}

final DevToolsUrl devToolsUrl = getDevToolsUrl(project, flutterSdkVersion, instance);

OpenApiUtils.safeInvokeLater(() -> {
Optional.ofNullable(
FlutterUtils.embeddedBrowser(project))
.ifPresent(embeddedBrowser ->
{
embeddedBrowser.openPanel(toolWindow, getToolWindowTitle(), devToolsUrl, System.out::println);
doAfterBrowserOpened(project, embeddedBrowser);
});
});
}
);

// TODO(helin24): It may be better to add this to the gear actions or to attach as a mouse event on individual tabs within a tool
// window, but I wasn't able to get either working immediately.
toolWindow.setTitleActions(List.of(new RefreshToolWindowAction(getToolWindowId())));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,92 +6,64 @@
package io.flutter.devtools;

import com.intellij.openapi.project.Project;
import com.intellij.openapi.wm.ToolWindow;
import com.intellij.openapi.wm.ToolWindowFactory;
import com.intellij.ui.content.ContentManager;
import io.flutter.FlutterUtils;
import io.flutter.actions.RefreshToolWindowAction;
import io.flutter.bazel.WorkspaceCache;
import io.flutter.run.daemon.DevToolsService;
import io.flutter.sdk.FlutterSdk;
import io.flutter.run.daemon.DevToolsInstance;
import io.flutter.sdk.FlutterSdkVersion;
import io.flutter.utils.AsyncUtils;
import io.flutter.utils.OpenApiUtils;
import io.flutter.view.EmbeddedBrowser;
import io.flutter.view.FlutterViewMessages;
import kotlin.coroutines.Continuation;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.List;
import java.util.Optional;
public class DevToolsExtensionsViewFactory extends AbstractDevToolsViewFactory {
@NotNull public static String TOOL_WINDOW_ID = "Flutter DevTools Extensions";

public class DevToolsExtensionsViewFactory implements ToolWindowFactory {
@NotNull private static String TOOL_WINDOW_ID = "Flutter DevTools Extensions";

public static void init(Project project) {
public static void init(@NotNull final Project project) {
project.getMessageBus().connect().subscribe(
FlutterViewMessages.FLUTTER_DEBUG_TOPIC, (FlutterViewMessages.FlutterDebugNotifier)event -> initView(project, event)
);
}

private static void initView(Project project, FlutterViewMessages.FlutterDebugEvent event) {
private static void initView(@NotNull final Project project, FlutterViewMessages.FlutterDebugEvent event) {
DevToolsExtensionsViewService service = project.getService(DevToolsExtensionsViewService.class);
String vmServiceUri = event.app.getConnector().getBrowserUrl();
if (vmServiceUri == null) return;
if (service == null || vmServiceUri == null) return;
service.updateVmServiceUri(vmServiceUri);
}

@Override
public void createToolWindowContent(@NotNull Project project, @NotNull ToolWindow window) {
final ContentManager contentManager = window.getContentManager();
FlutterSdk sdk = FlutterSdk.getFlutterSdk(project);
FlutterSdkVersion sdkVersion = sdk == null ? null : sdk.getVersion();
DevToolsExtensionsViewService service = project.getService(DevToolsExtensionsViewService.class);

AsyncUtils.whenCompleteUiThread(
DevToolsService.getInstance(project).getDevToolsInstance(),
(instance, error) -> {
// Skip displaying if the project has been closed.
if (!project.isOpen()) {
return;
}

if (error != null) {
return;
}

if (instance == null) {
return;
}
public boolean versionSupportsThisTool(@NotNull final FlutterSdkVersion flutterSdkVersion) {
return flutterSdkVersion.canUseDevToolsMultiEmbed();
}

final DevToolsUrl devToolsUrl = new DevToolsUrl.Builder()
.setDevToolsHost(instance.host())
.setDevToolsPort(instance.port())
.setHide("all-except-extensions")
.setEmbed(true).setFlutterSdkVersion(sdkVersion)
.setWorkspaceCache(WorkspaceCache.getInstance(project))
.setIdeFeature(DevToolsIdeFeature.TOOL_WINDOW)
.build();
@Override
@NotNull
public String getToolWindowId() {
return TOOL_WINDOW_ID;
}

OpenApiUtils.safeInvokeLater(() -> {
Optional.ofNullable(
FlutterUtils.embeddedBrowser(project))
.ifPresent(embeddedBrowser -> {
embeddedBrowser.openPanel(window, "Flutter DevTools", devToolsUrl, System.out::println);
service.setEmbeddedBrowser(embeddedBrowser);
});
});
}
);
@NotNull
public String getToolWindowTitle() {
return "Flutter DevTools";
}

window.setTitleActions(List.of(new RefreshToolWindowAction(TOOL_WINDOW_ID)));
@Override
protected void doAfterBrowserOpened(@NotNull final Project project, @NotNull final EmbeddedBrowser embeddedBrowser) {
DevToolsExtensionsViewService service = project.getService(DevToolsExtensionsViewService.class);
if (service == null) return;
service.setEmbeddedBrowser(embeddedBrowser);
}

@Nullable
@Override
public Object isApplicableAsync(@NotNull Project project, @NotNull Continuation<? super Boolean> $completion) {
FlutterSdk sdk = FlutterSdk.getFlutterSdk(project);
FlutterSdkVersion sdkVersion = sdk == null ? null : sdk.getVersion();
return sdkVersion != null && sdkVersion.canUseDevToolsMultiEmbed();
@NotNull
public DevToolsUrl getDevToolsUrl(@NotNull final Project project,
@NotNull final FlutterSdkVersion flutterSdkVersion,
@NotNull final DevToolsInstance instance) {
return new DevToolsUrl.Builder()
.setDevToolsHost(instance.host())
.setDevToolsPort(instance.port())
.setHide("all-except-extensions")
.setEmbed(true).setFlutterSdkVersion(flutterSdkVersion)
.setWorkspaceCache(WorkspaceCache.getInstance(project))
.setIdeFeature(DevToolsIdeFeature.TOOL_WINDOW)
.build();
}
}
Loading