Skip to content

Commit 55d2162

Browse files
committed
This fixes the issue of DevTools windows not appearing (#8029).
The class AbstractDevToolsViewFactory is introduced to validate the existence of the Flutter SDK for 4 of the DevTools windows, providing actionable messages for users to fix their Flutter dev setup.
1 parent 7bae52b commit 55d2162

9 files changed

+325
-250
lines changed

flutter-idea/src/io/flutter/FlutterInitializer.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,7 @@ public void actionPerformed(@NotNull AnActionEvent event) {
364364
});
365365
}
366366

367-
private void initializeToolWindows(@NotNull Project project) {
367+
private void initializeToolWindows(@NotNull final Project project) {
368368
// Start watching for Flutter debug active events.
369369
FlutterViewFactory.init(project);
370370
RemainingDevToolsViewFactory.init(project);

flutter-idea/src/io/flutter/deeplinks/DeepLinksViewFactory.java

+31-57
Original file line numberDiff line numberDiff line change
@@ -6,75 +6,49 @@
66
package io.flutter.deeplinks;
77

88
import com.intellij.openapi.project.Project;
9-
import com.intellij.openapi.wm.ToolWindow;
10-
import com.intellij.openapi.wm.ToolWindowFactory;
11-
import io.flutter.FlutterUtils;
12-
import io.flutter.actions.RefreshToolWindowAction;
139
import io.flutter.bazel.WorkspaceCache;
10+
import io.flutter.devtools.AbstractDevToolsViewFactory;
1411
import io.flutter.devtools.DevToolsIdeFeature;
1512
import io.flutter.devtools.DevToolsUrl;
16-
import io.flutter.run.daemon.DevToolsService;
17-
import io.flutter.sdk.FlutterSdk;
13+
import io.flutter.run.daemon.DevToolsInstance;
1814
import io.flutter.sdk.FlutterSdkVersion;
19-
import io.flutter.utils.AsyncUtils;
20-
import io.flutter.utils.OpenApiUtils;
21-
import kotlin.coroutines.Continuation;
2215
import org.jetbrains.annotations.NotNull;
2316

24-
import java.util.List;
25-
import java.util.Optional;
17+
public class DeepLinksViewFactory extends AbstractDevToolsViewFactory {
18+
@NotNull public static String TOOL_WINDOW_ID = "Flutter Deep Links";
2619

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

3022
@Override
31-
public Object isApplicableAsync(@NotNull Project project, @NotNull Continuation<? super Boolean> $completion) {
32-
FlutterSdk sdk = FlutterSdk.getFlutterSdk(project);
33-
FlutterSdkVersion sdkVersion = sdk == null ? null : sdk.getVersion();
34-
return sdkVersion != null && sdkVersion.canUseDeepLinksTool();
23+
public boolean versionSupportsThisTool(@NotNull final FlutterSdkVersion flutterSdkVersion) {
24+
return flutterSdkVersion.canUseDeepLinksTool();
3525
}
3626

3727
@Override
38-
public void createToolWindowContent(@NotNull Project project, @NotNull ToolWindow toolWindow) {
39-
FlutterSdk sdk = FlutterSdk.getFlutterSdk(project);
40-
FlutterSdkVersion sdkVersion = sdk == null ? null : sdk.getVersion();
41-
42-
AsyncUtils.whenCompleteUiThread(
43-
DevToolsService.getInstance(project).getDevToolsInstance(),
44-
(instance, error) -> {
45-
// Skip displaying if the project has been closed.
46-
if (!project.isOpen()) {
47-
return;
48-
}
49-
50-
if (error != null) {
51-
return;
52-
}
53-
54-
if (instance == null) {
55-
return;
56-
}
57-
58-
final DevToolsUrl devToolsUrl = new DevToolsUrl.Builder()
59-
.setDevToolsHost(instance.host())
60-
.setDevToolsPort(instance.port())
61-
.setPage("deep-links")
62-
.setEmbed(true)
63-
.setFlutterSdkVersion(sdkVersion)
64-
.setWorkspaceCache(WorkspaceCache.getInstance(project))
65-
.setIdeFeature(DevToolsIdeFeature.TOOL_WINDOW)
66-
.build();
28+
@NotNull
29+
public String getToolWindowId() {
30+
return TOOL_WINDOW_ID;
31+
}
6732

68-
OpenApiUtils.safeInvokeLater(() -> {
69-
Optional.ofNullable(
70-
FlutterUtils.embeddedBrowser(project))
71-
.ifPresent(embeddedBrowser -> embeddedBrowser.openPanel(toolWindow, "Deep Links", devToolsUrl, System.out::println));
72-
});
73-
}
74-
);
33+
@Override
34+
@NotNull
35+
public String getToolWindowTitle() {
36+
return "Deep Links";
37+
}
7538

76-
// 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
77-
// window, but I wasn't able to get either working immediately.
78-
toolWindow.setTitleActions(List.of(new RefreshToolWindowAction(TOOL_WINDOW_ID)));
39+
@Override
40+
@NotNull
41+
public DevToolsUrl getDevToolsUrl(@NotNull final Project project,
42+
@NotNull final FlutterSdkVersion flutterSdkVersion,
43+
@NotNull final DevToolsInstance instance) {
44+
return new DevToolsUrl.Builder()
45+
.setDevToolsHost(instance.host())
46+
.setDevToolsPort(instance.port())
47+
.setPage(DEVTOOLS_PAGE_ID)
48+
.setEmbed(true)
49+
.setFlutterSdkVersion(flutterSdkVersion)
50+
.setWorkspaceCache(WorkspaceCache.getInstance(project))
51+
.setIdeFeature(DevToolsIdeFeature.TOOL_WINDOW)
52+
.build();
7953
}
80-
}
54+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
/*
2+
* Copyright 2025 The Chromium Authors. All rights reserved.
3+
* Use of this source code is governed by a BSD-style license that can be
4+
* found in the LICENSE file.
5+
*/
6+
package io.flutter.devtools;
7+
8+
import com.intellij.openapi.project.Project;
9+
import com.intellij.openapi.wm.ToolWindow;
10+
import com.intellij.openapi.wm.ToolWindowFactory;
11+
import io.flutter.FlutterUtils;
12+
import io.flutter.actions.RefreshToolWindowAction;
13+
import io.flutter.run.daemon.DevToolsInstance;
14+
import io.flutter.run.daemon.DevToolsService;
15+
import io.flutter.sdk.FlutterSdk;
16+
import io.flutter.sdk.FlutterSdkVersion;
17+
import io.flutter.utils.AsyncUtils;
18+
import io.flutter.utils.OpenApiUtils;
19+
import io.flutter.view.EmbeddedBrowser;
20+
import io.flutter.view.ViewUtils;
21+
import kotlin.coroutines.Continuation;
22+
import org.jetbrains.annotations.NotNull;
23+
24+
import java.util.List;
25+
import java.util.Optional;
26+
27+
public abstract class AbstractDevToolsViewFactory implements ToolWindowFactory {
28+
29+
@NotNull
30+
protected final ViewUtils viewUtils = new ViewUtils();
31+
32+
public abstract boolean versionSupportsThisTool(@NotNull FlutterSdkVersion flutterSdkVersion);
33+
34+
@NotNull
35+
public abstract String getToolWindowId();
36+
37+
@NotNull
38+
public abstract String getToolWindowTitle();
39+
40+
@NotNull
41+
public abstract DevToolsUrl getDevToolsUrl(@NotNull Project project,
42+
@NotNull FlutterSdkVersion flutterSdkVersion,
43+
@NotNull DevToolsInstance instance);
44+
45+
protected void doAfterBrowserOpened(@NotNull Project project, @NotNull EmbeddedBrowser browser) {}
46+
47+
@Override
48+
public Object isApplicableAsync(@NotNull Project project, @NotNull Continuation<? super Boolean> $completion) {
49+
// Due to https://github.com/flutter/flutter/issues/142521, this always returns true when the
50+
// Flutter IJ plugin is installed.
51+
52+
// The logic which asserts that the Flutter SDK is up to date enough for this particular feature
53+
// is captured in the implementation of createToolWindowContent() below.
54+
55+
return true;
56+
}
57+
58+
@Override
59+
public void createToolWindowContent(@NotNull Project project, @NotNull ToolWindow toolWindow) {
60+
final FlutterSdk flutterSdk = FlutterSdk.getFlutterSdk(project);
61+
final FlutterSdkVersion flutterSdkVersion = flutterSdk == null ? null : flutterSdk.getVersion();
62+
63+
// There are four potential states for the Flutter SDK:
64+
// 1. The Flutter SDK is null (flutterSdk == null) (an SDK path is invalid or not configured)
65+
// 2. The Flutter SDK exists, but version file information is unavailable (flutterSdkVersion == null),
66+
// see https://github.com/flutter/flutter/issues/142521.
67+
// 3. The Flutter SDK exists, and the version file information is available, but this tool is not
68+
// available on the version of the SDK that the user has.
69+
// 4. The Flutter SDK exists with valid version information.
70+
71+
// First case:
72+
if (flutterSdk == null) {
73+
viewUtils.presentLabels(toolWindow, List.of("Set the Flutter SDK path in",
74+
"Settings > Languages & Frameworks > Flutter,",
75+
"and then restart the IDE."));
76+
return;
77+
}
78+
79+
// Second case:
80+
if (flutterSdk.getVersion().fullVersion().equals(FlutterSdkVersion.UNKNOWN_VERSION)) {
81+
viewUtils.presentLabels(toolWindow, List.of("A Flutter SDK was found at the location",
82+
"specified in the settings, however the directory",
83+
"is in an incomplete state. To fix, shut down the IDE,",
84+
"run `flutter doctor` or `flutter --version`",
85+
"and then restart the IDE."));
86+
return;
87+
}
88+
89+
// Third case:
90+
if (!versionSupportsThisTool(flutterSdkVersion)) {
91+
final String versionText = flutterSdkVersion.fullVersion();
92+
viewUtils.presentLabels(toolWindow, List.of("The version of your Flutter SDK,",
93+
versionText + ",",
94+
"is not recent enough to use this tool.",
95+
"Update the Flutter SDK, `flutter upgrade`,",
96+
"and then restart the IDE."));
97+
return;
98+
}
99+
100+
// Final case:
101+
AsyncUtils.whenCompleteUiThread(
102+
DevToolsService.getInstance(project).getDevToolsInstance(),
103+
(instance, error) -> {
104+
// Skip displaying if the project has been closed.
105+
if (!project.isOpen()) {
106+
return;
107+
}
108+
109+
if (error != null) {
110+
return;
111+
}
112+
113+
if (instance == null) {
114+
return;
115+
}
116+
117+
final DevToolsUrl devToolsUrl = getDevToolsUrl(project, flutterSdkVersion, instance);
118+
119+
OpenApiUtils.safeInvokeLater(() -> {
120+
Optional.ofNullable(
121+
FlutterUtils.embeddedBrowser(project))
122+
.ifPresent(embeddedBrowser ->
123+
{
124+
embeddedBrowser.openPanel(toolWindow, getToolWindowTitle(), devToolsUrl, System.out::println);
125+
doAfterBrowserOpened(project, embeddedBrowser);
126+
});
127+
});
128+
}
129+
);
130+
131+
// 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
132+
// window, but I wasn't able to get either working immediately.
133+
toolWindow.setTitleActions(List.of(new RefreshToolWindowAction(getToolWindowId())));
134+
}
135+
}

flutter-idea/src/io/flutter/devtools/DevToolsExtensionsViewFactory.java

+36-64
Original file line numberDiff line numberDiff line change
@@ -6,92 +6,64 @@
66
package io.flutter.devtools;
77

88
import com.intellij.openapi.project.Project;
9-
import com.intellij.openapi.wm.ToolWindow;
10-
import com.intellij.openapi.wm.ToolWindowFactory;
11-
import com.intellij.ui.content.ContentManager;
12-
import io.flutter.FlutterUtils;
13-
import io.flutter.actions.RefreshToolWindowAction;
149
import io.flutter.bazel.WorkspaceCache;
15-
import io.flutter.run.daemon.DevToolsService;
16-
import io.flutter.sdk.FlutterSdk;
10+
import io.flutter.run.daemon.DevToolsInstance;
1711
import io.flutter.sdk.FlutterSdkVersion;
18-
import io.flutter.utils.AsyncUtils;
19-
import io.flutter.utils.OpenApiUtils;
12+
import io.flutter.view.EmbeddedBrowser;
2013
import io.flutter.view.FlutterViewMessages;
21-
import kotlin.coroutines.Continuation;
2214
import org.jetbrains.annotations.NotNull;
23-
import org.jetbrains.annotations.Nullable;
2415

25-
import java.util.List;
26-
import java.util.Optional;
16+
public class DevToolsExtensionsViewFactory extends AbstractDevToolsViewFactory {
17+
@NotNull public static String TOOL_WINDOW_ID = "Flutter DevTools Extensions";
2718

28-
public class DevToolsExtensionsViewFactory implements ToolWindowFactory {
29-
@NotNull private static String TOOL_WINDOW_ID = "Flutter DevTools Extensions";
30-
31-
public static void init(Project project) {
19+
public static void init(@NotNull final Project project) {
3220
project.getMessageBus().connect().subscribe(
3321
FlutterViewMessages.FLUTTER_DEBUG_TOPIC, (FlutterViewMessages.FlutterDebugNotifier)event -> initView(project, event)
3422
);
3523
}
3624

37-
private static void initView(Project project, FlutterViewMessages.FlutterDebugEvent event) {
25+
private static void initView(@NotNull final Project project, FlutterViewMessages.FlutterDebugEvent event) {
3826
DevToolsExtensionsViewService service = project.getService(DevToolsExtensionsViewService.class);
3927
String vmServiceUri = event.app.getConnector().getBrowserUrl();
40-
if (vmServiceUri == null) return;
28+
if (service == null || vmServiceUri == null) return;
4129
service.updateVmServiceUri(vmServiceUri);
4230
}
4331

4432
@Override
45-
public void createToolWindowContent(@NotNull Project project, @NotNull ToolWindow window) {
46-
final ContentManager contentManager = window.getContentManager();
47-
FlutterSdk sdk = FlutterSdk.getFlutterSdk(project);
48-
FlutterSdkVersion sdkVersion = sdk == null ? null : sdk.getVersion();
49-
DevToolsExtensionsViewService service = project.getService(DevToolsExtensionsViewService.class);
50-
51-
AsyncUtils.whenCompleteUiThread(
52-
DevToolsService.getInstance(project).getDevToolsInstance(),
53-
(instance, error) -> {
54-
// Skip displaying if the project has been closed.
55-
if (!project.isOpen()) {
56-
return;
57-
}
58-
59-
if (error != null) {
60-
return;
61-
}
62-
63-
if (instance == null) {
64-
return;
65-
}
33+
public boolean versionSupportsThisTool(@NotNull final FlutterSdkVersion flutterSdkVersion) {
34+
return flutterSdkVersion.canUseDevToolsMultiEmbed();
35+
}
6636

67-
final DevToolsUrl devToolsUrl = new DevToolsUrl.Builder()
68-
.setDevToolsHost(instance.host())
69-
.setDevToolsPort(instance.port())
70-
.setHide("all-except-extensions")
71-
.setEmbed(true).setFlutterSdkVersion(sdkVersion)
72-
.setWorkspaceCache(WorkspaceCache.getInstance(project))
73-
.setIdeFeature(DevToolsIdeFeature.TOOL_WINDOW)
74-
.build();
37+
@Override
38+
@NotNull
39+
public String getToolWindowId() {
40+
return TOOL_WINDOW_ID;
41+
}
7542

76-
OpenApiUtils.safeInvokeLater(() -> {
77-
Optional.ofNullable(
78-
FlutterUtils.embeddedBrowser(project))
79-
.ifPresent(embeddedBrowser -> {
80-
embeddedBrowser.openPanel(window, "Flutter DevTools", devToolsUrl, System.out::println);
81-
service.setEmbeddedBrowser(embeddedBrowser);
82-
});
83-
});
84-
}
85-
);
43+
@NotNull
44+
public String getToolWindowTitle() {
45+
return "Flutter DevTools";
46+
}
8647

87-
window.setTitleActions(List.of(new RefreshToolWindowAction(TOOL_WINDOW_ID)));
48+
@Override
49+
protected void doAfterBrowserOpened(@NotNull final Project project, @NotNull final EmbeddedBrowser embeddedBrowser) {
50+
DevToolsExtensionsViewService service = project.getService(DevToolsExtensionsViewService.class);
51+
if (service == null) return;
52+
service.setEmbeddedBrowser(embeddedBrowser);
8853
}
8954

90-
@Nullable
9155
@Override
92-
public Object isApplicableAsync(@NotNull Project project, @NotNull Continuation<? super Boolean> $completion) {
93-
FlutterSdk sdk = FlutterSdk.getFlutterSdk(project);
94-
FlutterSdkVersion sdkVersion = sdk == null ? null : sdk.getVersion();
95-
return sdkVersion != null && sdkVersion.canUseDevToolsMultiEmbed();
56+
@NotNull
57+
public DevToolsUrl getDevToolsUrl(@NotNull final Project project,
58+
@NotNull final FlutterSdkVersion flutterSdkVersion,
59+
@NotNull final DevToolsInstance instance) {
60+
return new DevToolsUrl.Builder()
61+
.setDevToolsHost(instance.host())
62+
.setDevToolsPort(instance.port())
63+
.setHide("all-except-extensions")
64+
.setEmbed(true).setFlutterSdkVersion(flutterSdkVersion)
65+
.setWorkspaceCache(WorkspaceCache.getInstance(project))
66+
.setIdeFeature(DevToolsIdeFeature.TOOL_WINDOW)
67+
.build();
9668
}
9769
}

0 commit comments

Comments
 (0)