Skip to content

Commit d79cbb0

Browse files
authored
Fix IllegalStateException in ProjectOpenProcessor (#8633)
Migrate [FlutterProjectOpenProcessor](cci:2://file:///usr/local/google/home/jwren/src/flutter-intellij/src/io/flutter/project/FlutterProjectOpenProcessor.kt:21:0-89:1) and [FlutterStudioProjectOpenProcessor](cci:2://file:///usr/local/google/home/jwren/src/flutter-intellij/src/io/flutter/editor/FlutterStudioProjectOpenProcessor.kt:17:0-61:1) from the deprecated [doOpenProject](cci:1://file:///usr/local/google/home/jwren/src/flutter-intellij/src/io/flutter/editor/FlutterStudioProjectOpenProcessor.kt:54:2-60:3) to [openProjectAsync](cci:1://file:///usr/local/google/home/jwren/src/flutter-intellij/src/io/flutter/project/FlutterProjectOpenProcessor.kt:38:2-54:3) to fix compatibility with IntelliJ IDEA 2025.3+. This change involves: - Converting [FlutterProjectOpenProcessor](cci:2://file:///usr/local/google/home/jwren/src/flutter-intellij/src/io/flutter/project/FlutterProjectOpenProcessor.kt:21:0-89:1) to Kotlin. - Converting [FlutterStudioProjectOpenProcessor](cci:2://file:///usr/local/google/home/jwren/src/flutter-intellij/src/io/flutter/editor/FlutterStudioProjectOpenProcessor.kt:17:0-61:1) to Kotlin. - Implementing [openProjectAsync](cci:1://file:///usr/local/google/home/jwren/src/flutter-intellij/src/io/flutter/project/FlutterProjectOpenProcessor.kt:38:2-54:3) using `writeAction` for project setup. - Enabling Kotlin compilation for the main source set in [build.gradle.kts](cci:7://file:///usr/local/google/home/jwren/src/flutter-intellij/build.gradle.kts:0:0-0:0). Fixes #8629 See https://plugins.jetbrains.com/docs/intellij/project-open-processor.html
1 parent 32be9fc commit d79cbb0

File tree

7 files changed

+207
-160
lines changed

7 files changed

+207
-160
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
### Fixed
1414

1515
- Fixed crash when using 3rd party loggers that don't implement `setLevel`. (#8631)
16+
- Fixed `IllegalStateException` and "Slow operations are prohibited on EDT" when opening projects by migrating `FlutterProjectOpenProcessor` to Kotlin and using `openProjectAsync`. (#8629)
1617

1718
## 88.1.0
1819

build.gradle.kts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,12 @@ sourceSets {
141141
"third_party/vmServiceDrivers"
142142
)
143143
)
144+
kotlin.srcDirs(
145+
listOf(
146+
"src",
147+
"third_party/vmServiceDrivers"
148+
)
149+
)
144150
// Add kotlin.srcDirs if we start using Kotlin in the main plugin.
145151
resources.srcDirs(
146152
listOf(

src/io/flutter/editor/FlutterStudioProjectOpenProcessor.java

Lines changed: 0 additions & 57 deletions
This file was deleted.
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
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.editor
7+
8+
import com.intellij.openapi.application.writeAction
9+
import com.intellij.openapi.project.Project
10+
import com.intellij.openapi.vfs.VirtualFile
11+
import com.intellij.projectImport.ProjectOpenProcessor
12+
import com.intellij.ui.EditorNotifications
13+
import io.flutter.FlutterUtils
14+
import io.flutter.project.FlutterProjectOpenProcessor
15+
import io.flutter.pub.PubRoot
16+
import io.flutter.utils.FlutterModuleUtils
17+
18+
/**
19+
* Originally `FlutterStudioProjectOpenProcessor.java`.
20+
*
21+
* This processor is specific to Android Studio (or when the Flutter Studio plugin is active).
22+
* It extends `FlutterProjectOpenProcessor` to provide specialized handling for Android Studio,
23+
* particularly ensuring that the project is correctly re-detected if the opening process causes a reload.
24+
*
25+
* Converted to Kotlin to support `openProjectAsync`.
26+
*/
27+
class FlutterStudioProjectOpenProcessor : FlutterProjectOpenProcessor() {
28+
override val name: String
29+
get() = "Flutter Studio"
30+
31+
override fun canOpenProject(file: VirtualFile): Boolean =
32+
PubRoot.forDirectory(file)?.declaresFlutter() == true
33+
34+
/**
35+
* Replaces the deprecated `doOpenProject`.
36+
*
37+
* Performs the same logic as the original Java implementation but using `suspend` and `writeAction`.
38+
*
39+
* Key differences from the base class:
40+
* - Explicitly looks up the project again using `FlutterUtils.findProject` after opening, as the project instance might have changed
41+
* (e.g. if the platform closed and reopened it during import).
42+
* - Ensures Dart SDK is enabled and notifications are updated.
43+
*/
44+
override suspend fun openProjectAsync(
45+
virtualFile: VirtualFile,
46+
projectToClose: Project?,
47+
forceOpenInNewFrame: Boolean,
48+
): Project? {
49+
val importProvider = getDelegateImportProvider(virtualFile) ?: return null
50+
val project = importProvider.openProjectAsync(virtualFile, projectToClose, forceOpenInNewFrame)
51+
52+
// A callback may have caused the project to be reloaded. Find the new Project object.
53+
val newProject = FlutterUtils.findProject(virtualFile.path)
54+
if (newProject == null || newProject.isDisposed) {
55+
return newProject
56+
}
57+
58+
for (module in FlutterModuleUtils.getModules(newProject)) {
59+
if (FlutterModuleUtils.declaresFlutter(module) && !FlutterModuleUtils.isFlutterModule(module)) {
60+
writeAction {
61+
FlutterModuleUtils.setFlutterModuleType(module)
62+
}
63+
FlutterModuleUtils.enableDartSDK(module)
64+
}
65+
}
66+
newProject.save()
67+
EditorNotifications.getInstance(newProject).updateAllNotifications()
68+
69+
return newProject
70+
}
71+
72+
override fun doOpenProject(
73+
virtualFile: VirtualFile,
74+
projectToClose: Project?,
75+
forceOpenInNewFrame: Boolean,
76+
): Project? {
77+
return null
78+
}
79+
}

src/io/flutter/project/FlutterProjectOpenProcessor.java

Lines changed: 0 additions & 97 deletions
This file was deleted.
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
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.project
7+
8+
import com.intellij.openapi.application.ApplicationInfo
9+
import com.intellij.openapi.application.writeAction
10+
import com.intellij.openapi.module.Module
11+
import com.intellij.openapi.project.Project
12+
import com.intellij.openapi.vfs.VirtualFile
13+
import com.intellij.projectImport.ProjectOpenProcessor
14+
import icons.FlutterIcons
15+
import io.flutter.FlutterBundle
16+
import io.flutter.FlutterUtils
17+
import io.flutter.pub.PubRoot
18+
import io.flutter.utils.FlutterModuleUtils
19+
import java.util.Objects
20+
import javax.swing.Icon
21+
22+
/**
23+
* Originally `FlutterProjectOpenProcessor.java`.
24+
*
25+
* This processor handles opening Flutter projects when they are selected directly (e.g. via "Open" in the IDE).
26+
* It delegates the actual opening to the platform's default processor (e.g. Gradle or Maven processor if applicable,
27+
* or the generic project opener) and then ensures that any modules in the project are correctly configured as Flutter modules.
28+
*
29+
* Converted to Kotlin to support `openProjectAsync` which is a suspend function.
30+
*/
31+
open class FlutterProjectOpenProcessor : ProjectOpenProcessor() {
32+
override val name: String
33+
get() = FlutterBundle.message("flutter.module.name")
34+
35+
override fun getIcon(file: VirtualFile): Icon? {
36+
return FlutterIcons.Flutter
37+
}
38+
39+
override fun canOpenProject(file: VirtualFile): Boolean {
40+
val info = ApplicationInfo.getInstance()
41+
if (FlutterUtils.isAndroidStudio()) {
42+
return false
43+
}
44+
val root = PubRoot.forDirectory(file)
45+
return root != null && root.declaresFlutter()
46+
}
47+
48+
/**
49+
* Replaces the deprecated `doOpenProject`.
50+
*
51+
* This method is `suspend` and must be used instead of `doOpenProject` to avoid `IllegalStateException` in newer IDE versions.
52+
*
53+
* It performs the following steps:
54+
* 1. Finds a delegate processor (e.g. Gradle) to open the project.
55+
* 2. Opens the project asynchronously.
56+
* 3. Once opened, checks if the project contains Flutter modules that are not yet configured as such (e.g. missing module type).
57+
* 4. Configures these modules as Flutter modules within a write action.
58+
*/
59+
override suspend fun openProjectAsync(
60+
virtualFile: VirtualFile,
61+
projectToClose: Project?,
62+
forceOpenInNewFrame: Boolean,
63+
): Project? {
64+
// Delegate opening to the platform open processor.
65+
val importProvider = getDelegateImportProvider(virtualFile) ?: return null
66+
val project = importProvider.openProjectAsync(virtualFile, projectToClose, forceOpenInNewFrame)
67+
if (project == null || project.isDisposed) return project
68+
69+
// Convert any modules that use Flutter but don't have IntelliJ Flutter metadata.
70+
convertToFlutterProject(project)
71+
72+
return project
73+
}
74+
75+
/**
76+
* Deprecated method, kept to satisfy the compiler/interface.
77+
*
78+
* We return `null` to indicate that this processor does not support the synchronous opening method
79+
* and that `openProjectAsync` should be used instead.
80+
*/
81+
override fun doOpenProject(
82+
virtualFile: VirtualFile,
83+
projectToClose: Project?,
84+
forceOpenInNewFrame: Boolean,
85+
): Project? {
86+
return null
87+
}
88+
89+
protected open fun getDelegateImportProvider(file: VirtualFile): ProjectOpenProcessor? {
90+
return EXTENSION_POINT_NAME.extensionList.stream().filter { processor: ProjectOpenProcessor ->
91+
processor.canOpenProject(file) && !Objects.equals(
92+
processor.name,
93+
name
94+
)
95+
}.findFirst().orElse(null)
96+
}
97+
98+
99+
companion object {
100+
/**
101+
* Sets up a project that doesn't have any Flutter modules.
102+
*
103+
*
104+
* (It probably wasn't created with "flutter create" and probably didn't have any IntelliJ configuration before.)
105+
*/
106+
private fun convertToFlutterProject(project: Project) {
107+
for (module in FlutterModuleUtils.getModules(project)) {
108+
if (FlutterModuleUtils.declaresFlutter(module) && !FlutterModuleUtils.isFlutterModule(module)) {
109+
FlutterModuleUtils.setFlutterModuleAndReload(module, project)
110+
}
111+
}
112+
}
113+
}
114+
}

src/io/flutter/utils/FlutterModuleUtils.java

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -375,13 +375,14 @@ public static void setFlutterModuleType(@NotNull Module module) {
375375

376376
public static void setFlutterModuleAndReload(@NotNull Module module, @NotNull Project project) {
377377
if (project.isDisposed()) return;
378+
ApplicationManager.getApplication().invokeLater(() -> {
379+
ApplicationManager.getApplication().runWriteAction(() -> setFlutterModuleType(module));
380+
enableDartSDK(module);
381+
project.save();
378382

379-
setFlutterModuleType(module);
380-
enableDartSDK(module);
381-
project.save();
382-
383-
EditorNotifications.getInstance(project).updateAllNotifications();
384-
ProjectManager.getInstance().reloadProject(project);
383+
EditorNotifications.getInstance(project).updateAllNotifications();
384+
ProjectManager.getInstance().reloadProject(project);
385+
});
385386
}
386387

387388
public static void enableDartSDK(final @NotNull Module module) {

0 commit comments

Comments
 (0)