Skip to content

Commit 36ef16e

Browse files
authored
[DeviceSelectorAction] šŸ› Replace the action list without concurrent modification (#8550)
Fixes #8544 --- - [x] I’ve reviewed the contributor guide and applied the relevant portions to this PR. <details> <summary>Contribution guidelines:</summary><br> - See our [contributor guide]([https://github.com/dart-lang/sdk/blob/main/CONTRIBUTING.md](https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview) for general expectations for PRs. - Larger or significant changes should be discussed in an issue before creating a PR. - Dart contributions to our repos should follow the [Dart style guide](https://dart.dev/guides/language/effective-dart) and use `dart format`. - Java and Kotlin contributions should strive to follow Java and Kotlin best practices ([discussion](#8098)). </details>
1 parent 9ba074a commit 36ef16e

File tree

2 files changed

+27
-12
lines changed

2 files changed

+27
-12
lines changed

ā€ŽCHANGELOG.mdā€Ž

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
- Refactored `DeviceSelectorAction` and add rich icons to different platform devices (#8475)
1616
- Fix DTD freezes when opening projects, and EDT freezes when the theme is changed and opening embedded DevTools (#8477)
1717
- Fix `DeviceSelectorAction` `NoSuchElementException` in the toolbar layout (#8515)
18+
- Fix `DeviceSelectorAction`'s concurrent modification exception. (#8550)
1819

1920
## 87.1.0
2021

ā€Žsrc/io/flutter/actions/DeviceSelectorAction.javaā€Ž

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import com.intellij.openapi.application.Application;
1414
import com.intellij.openapi.application.ApplicationManager;
1515
import com.intellij.openapi.application.ModalityState;
16+
import com.intellij.openapi.diagnostic.Logger;
1617
import com.intellij.openapi.project.DumbAware;
1718
import com.intellij.openapi.project.Project;
1819
import com.intellij.openapi.project.ProjectManager;
@@ -28,6 +29,7 @@
2829
import com.intellij.util.ui.JBUI;
2930
import icons.FlutterIcons;
3031
import io.flutter.FlutterBundle;
32+
import io.flutter.logging.PluginLogger;
3133
import io.flutter.run.FlutterDevice;
3234
import io.flutter.run.daemon.DeviceService;
3335
import io.flutter.sdk.AndroidEmulatorManager;
@@ -43,14 +45,16 @@
4345
import java.util.List;
4446

4547
public class DeviceSelectorAction extends AnAction implements CustomComponentAction, DumbAware {
48+
private static final @NotNull Logger LOG = PluginLogger.createLogger(DeviceSelectorAction.class);
49+
4650
private static final Key<JButton> CUSTOM_COMPONENT_KEY = Key.create("customComponent");
4751
private static final Key<JBLabel> ICON_LABEL_KEY = Key.create("iconLabel");
4852
private static final Key<JBLabel> TEXT_LABEL_KEY = Key.create("textLabel");
4953
private static final Key<JBLabel> ARROW_LABEL_KEY = Key.create("arrowLabel");
5054
private static final @NotNull Icon DEFAULT_DEVICE_ICON = FlutterIcons.Mobile;
5155
private static final @NotNull Icon DEFAULT_ARROW_ICON = IconUtil.scale(AllIcons.General.ChevronDown, null, 1.2f);
5256

53-
private final List<AnAction> actions = new ArrayList<>();
57+
private volatile @NotNull List<AnAction> actions = new ArrayList<>();
5458
private final List<Project> knownProjects = Collections.synchronizedList(new ArrayList<>());
5559

5660
private @Nullable SelectDeviceAction selectedDeviceAction;
@@ -386,7 +390,11 @@ private static boolean isSelectorVisible(@Nullable Project project) {
386390
}
387391

388392
private void updateActions(@NotNull Project project, @NotNull Presentation presentation) {
389-
actions.clear();
393+
final String projectName = project.getName();
394+
LOG.debug("[" + projectName + "] Building device selector actions");
395+
396+
// Create a new list instead of modifying the existing one
397+
final List<AnAction> newActions = new ArrayList<>();
390398

391399
final DeviceService deviceService = DeviceService.getInstance(project);
392400

@@ -399,7 +407,8 @@ private void updateActions(@NotNull Project project, @NotNull Presentation prese
399407
if (device == null) continue;
400408

401409
final SelectDeviceAction deviceAction = new SelectDeviceAction(device, devices);
402-
actions.add(deviceAction);
410+
newActions.add(deviceAction);
411+
LOG.debug("[" + projectName + "] Device action added for " + device);
403412

404413
if (Objects.equals(device, selectedDevice)) {
405414
selectedDeviceAction = deviceAction;
@@ -411,37 +420,42 @@ private void updateActions(@NotNull Project project, @NotNull Presentation prese
411420
// Show the 'Open iOS Simulator' action.
412421
if (SystemInfo.isMac) {
413422
boolean simulatorOpen = false;
414-
for (AnAction action : actions) {
423+
for (AnAction action : newActions) {
415424
if (action instanceof SelectDeviceAction deviceAction) {
416425
final FlutterDevice device = deviceAction.device;
417426
if (device.isIOS() && device.emulator()) {
418427
simulatorOpen = true;
428+
break;
419429
}
420430
}
421431
}
422-
423-
actions.add(new Separator());
424-
actions.add(new OpenSimulatorAction(!simulatorOpen));
432+
newActions.add(new Separator());
433+
newActions.add(new OpenSimulatorAction(!simulatorOpen));
434+
LOG.debug("[" + projectName + "] 'Open iOS Simulator' action added");
425435
}
426436

427437
// Add Open Android emulators actions.
428438
final List<OpenEmulatorAction> emulatorActions = OpenEmulatorAction.getEmulatorActions(project);
429439
if (emulatorActions != null && !emulatorActions.isEmpty()) {
430-
actions.add(new Separator());
431-
actions.addAll(emulatorActions);
440+
newActions.add(new Separator());
441+
newActions.addAll(emulatorActions);
442+
LOG.debug("[" + projectName + "] Emulator action added: " + emulatorActions);
432443
}
433444
if (!FlutterModuleUtils.hasInternalDartSdkPath(project)) {
434-
actions.add(new Separator());
435-
actions.add(RestartFlutterDaemonAction.forDeviceSelector());
445+
newActions.add(new Separator());
446+
newActions.add(RestartFlutterDaemonAction.forDeviceSelector());
436447
}
437448

449+
// Atomically replace the action list
450+
LOG.debug("[" + projectName + "] Replacing device selector actions");
451+
this.actions = newActions;
452+
438453
var tracker = ActivityTracker.getInstance();
439454
if (tracker != null) {
440455
tracker.inc();
441456
}
442457
}
443458

444-
445459
private static class SelectDeviceAction extends AnAction {
446460
@NotNull private final FlutterDevice device;
447461

0 commit comments

Comments
Ā (0)