Skip to content

Commit f71321a

Browse files
committed
Add emulator deletion support to CLI
Introduces the --delete option to the emulators command, allowing users to delete Android emulators by ID or name. Refactors output formatting to optionally remove panel borders, updates device and emulator table layouts, and adds the delete_avd method to AndroidSDK for managing AVDs.
1 parent 02877c5 commit f71321a

File tree

5 files changed

+98
-34
lines changed

5 files changed

+98
-34
lines changed

sdk/python/packages/flet-cli/src/flet_cli/commands/debug.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66
from rich.console import Group
77
from rich.live import Live
8-
from rich.panel import Panel
98

109
from flet_cli.commands.build_base import BaseBuildCommand, console, verbose2_style
1110

@@ -105,7 +104,7 @@ def handle(self, options: argparse.Namespace) -> None:
105104
self.customize_icons()
106105
self.customize_splash_images()
107106
self.run_flutter()
108-
self.cleanup(0, message=Panel("Debug session ended."))
107+
self.cleanup(0, message="Debug session ended.")
109108

110109
def check_device_id(self):
111110
if self.device_id is None and self.debug_platform in [

sdk/python/packages/flet-cli/src/flet_cli/commands/devices.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,9 @@ def run_flutter_devices(self):
123123

124124
if not devices:
125125
self.cleanup(
126-
0, Group(Panel(f"No {self.platform_label} devices found."), footer)
126+
0,
127+
Group(Panel(f"No {self.platform_label} devices found."), footer),
128+
no_border=True,
127129
)
128130

129131
devices_table = Table(
@@ -144,7 +146,7 @@ def run_flutter_devices(self):
144146
device["details"],
145147
)
146148

147-
self.cleanup(0, message=Group(devices_table, footer))
149+
self.cleanup(0, message=Group(devices_table, footer), no_border=True)
148150

149151
def _parse_devices_output(self, output: str) -> list[dict]:
150152
devices = []

sdk/python/packages/flet-cli/src/flet_cli/commands/emulators.py

Lines changed: 66 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import argparse
22
import os
3+
from pathlib import Path
34

45
from rich.console import Group
56
from rich.live import Live
67
from rich.panel import Panel
78
from rich.table import Column, Table
89

910
from flet_cli.commands.build_base import BaseFlutterCommand, console, verbose2_style
11+
from flet_cli.utils.android_sdk import AndroidSDK
1012

1113

1214
class Command(BaseFlutterCommand):
@@ -19,6 +21,7 @@ def __init__(self, parser: argparse.ArgumentParser) -> None:
1921
self.launch_target = None
2022
self.cold_boot = False
2123
self.create_emulator = False
24+
self.delete_emulator = None
2225
self.emulator_name = None
2326

2427
def add_arguments(self, parser: argparse.ArgumentParser) -> None:
@@ -42,6 +45,12 @@ def add_arguments(self, parser: argparse.ArgumentParser) -> None:
4245
default=False,
4346
help="Create a new emulator using Flutter defaults.",
4447
)
48+
parser.add_argument(
49+
"--delete",
50+
dest="delete",
51+
type=str,
52+
help="Delete an emulator by ID or name (Android only).",
53+
)
4554
parser.add_argument(
4655
"--name",
4756
dest="name",
@@ -56,23 +65,28 @@ def handle(self, options: argparse.Namespace) -> None:
5665
self.launch_target = self.options.launch
5766
self.cold_boot = bool(self.options.cold)
5867
self.create_emulator = bool(self.options.create)
68+
self.delete_emulator = self.options.delete
5969
self.emulator_name = self.options.name
6070

61-
if self.create_emulator and self.launch_target:
62-
console.log(
63-
"Please choose either --create or --launch, not both.",
64-
style=verbose2_style,
65-
)
66-
self.cleanup(
67-
1, Panel("Please choose either --create or --launch, not both.")
68-
)
71+
selected_actions = [
72+
bool(self.create_emulator),
73+
bool(self.launch_target),
74+
bool(self.delete_emulator),
75+
]
76+
if sum(selected_actions) > 1:
77+
msg = "Please choose only one action: --create, --launch, or --delete."
78+
console.log(msg, style=verbose2_style)
79+
self.cleanup(1, msg)
6980

7081
self.status = console.status(
7182
"[bold blue]Initializing environment...",
7283
spinner="bouncingBall",
7384
)
7485
with Live(Group(self.status, self.progress), console=console) as self.live:
7586
self.initialize_command()
87+
if self.delete_emulator:
88+
self._delete_emulator()
89+
return
7690
if self.create_emulator:
7791
self._create_emulator()
7892
return
@@ -120,11 +134,11 @@ def _list_emulators(self):
120134
'\nRun [green]"flet emulators --create"[/green] '
121135
"to create a new emulator.\n"
122136
)
123-
self.cleanup(0, Group(Panel("No emulators found."), footer))
137+
self.cleanup(0, Group(Panel("No emulators found."), footer), no_border=True)
124138

125139
table = Table(
126-
Column("Name", style="cyan", justify="left"),
127140
Column("ID", style="magenta", justify="left", no_wrap=True),
141+
Column("Name", style="cyan", justify="left"),
128142
Column("Platform", style="green", justify="center"),
129143
Column("Manufacturer", style="yellow", justify="left"),
130144
title="Available emulators",
@@ -134,8 +148,8 @@ def _list_emulators(self):
134148

135149
for emulator in emulators:
136150
table.add_row(
137-
emulator["name"],
138151
emulator["id"],
152+
emulator["name"],
139153
emulator["platform_label"],
140154
emulator["manufacturer"],
141155
)
@@ -146,13 +160,15 @@ def _list_emulators(self):
146160
'[green]"flet emulators --launch <emulator-id>"[/green].\n'
147161
"Create a new emulator with "
148162
'[green]"flet emulators --create [--name <name>]"[/green].\n'
163+
"Delete an Android emulator with "
164+
'[green]"flet emulators --delete <emulator-id>"[/green].\n'
149165
"\n"
150166
"You can find more information on managing emulators at the links below:\n"
151167
" https://developer.android.com/studio/run/managing-avds\n"
152168
" https://developer.android.com/studio/command-line/avdmanager"
153169
)
154170

155-
self.cleanup(0, message=Group(table, footer))
171+
self.cleanup(0, message=Group(table, footer), no_border=True)
156172

157173
def _launch_emulator(self):
158174
assert self.launch_target
@@ -181,8 +197,10 @@ def _launch_emulator(self):
181197
error_output = launch_result.stderr or output
182198
self.cleanup(
183199
launch_result.returncode,
184-
Panel(
185-
error_output or f"Failed to launch emulator '{self.launch_target}'."
200+
(
201+
error_output
202+
if error_output
203+
else f"Failed to launch emulator '{self.launch_target}'."
186204
),
187205
)
188206
return
@@ -191,12 +209,33 @@ def _launch_emulator(self):
191209
console.log(output, style=verbose2_style)
192210

193211
mode = " (cold boot)" if self.cold_boot else ""
194-
self.cleanup(
195-
0,
196-
Panel(
197-
f"Emulator [cyan]{self.launch_target}[/cyan] launched{mode}.",
198-
),
212+
self.cleanup(0, f"Emulator [cyan]{self.launch_target}[/cyan] launched{mode}.")
213+
214+
def _delete_emulator(self):
215+
assert self.delete_emulator
216+
self.update_status(
217+
f"[bold blue]Deleting emulator [cyan]{self.delete_emulator}[/cyan]..."
199218
)
219+
home_dir = self.env.get("ANDROID_HOME") or AndroidSDK.android_home_dir()
220+
if not home_dir:
221+
self.cleanup(
222+
1, "ANDROID_HOME is not set and Android SDK location cannot be found."
223+
)
224+
225+
sdk = AndroidSDK(
226+
self.env.get("JAVA_HOME", ""), self.log_stdout, progress=self.progress
227+
)
228+
229+
try:
230+
sdk.delete_avd(Path(home_dir), self.delete_emulator)
231+
except Exception as exc: # pragma: no cover - defensive
232+
self.skip_flutter_doctor = True
233+
self.cleanup(
234+
1, f"Failed to delete emulator '{self.delete_emulator}': {exc}"
235+
)
236+
return
237+
238+
self.cleanup(0, f"Deleted emulator [cyan]{self.delete_emulator}[/cyan].")
200239

201240
def _create_emulator(self):
202241
self.update_status("[bold blue]Creating emulator...")
@@ -220,7 +259,7 @@ def _create_emulator(self):
220259
error_output = create_result.stderr or output
221260
self.cleanup(
222261
create_result.returncode,
223-
Panel(error_output or "Failed to create emulator."),
262+
error_output or "Failed to create emulator.",
224263
)
225264
return
226265

@@ -230,11 +269,9 @@ def _create_emulator(self):
230269
created_name = self.emulator_name or "emulator"
231270
self.cleanup(
232271
0,
233-
Panel(
234-
f"Created emulator [cyan]{created_name}[/cyan]. "
235-
"Use `flet emulators` to list it or "
236-
f"`flet emulators --launch {created_name}` to start it.",
237-
),
272+
f"Created emulator [cyan]{created_name}[/cyan]. "
273+
"Use `flet emulators` to list it or "
274+
f"`flet emulators --launch {created_name}` to start it.",
238275
)
239276

240277
def _parse_emulators_output(self, output: str) -> list[dict]:
@@ -247,10 +284,11 @@ def _parse_emulators_output(self, output: str) -> list[dict]:
247284
if len(parts) < 2:
248285
continue
249286

250-
name = parts[0]
251-
emulator_id = parts[1]
287+
emulator_id = parts[0]
288+
name = parts[1]
252289
# Skip header rows printed by `flutter emulators` (Id • Name • Platform ...)
253-
if name.lower() == "id" and emulator_id.lower() == "name":
290+
lower_head = {p.lower() for p in parts[:4]}
291+
if {"id", "name", "platform"}.issubset(lower_head):
254292
continue
255293
details_segments = parts[2:]
256294
platform_raw = details_segments[-1] if details_segments else ""

sdk/python/packages/flet-cli/src/flet_cli/commands/flutter_base.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -305,10 +305,10 @@ def run(self, args, cwd, env: Optional[dict] = None, capture_output=True):
305305
log=self.log_stdout,
306306
)
307307

308-
def cleanup(self, exit_code: int, message: Any = None):
308+
def cleanup(self, exit_code: int, message: Any = None, no_border: bool = False):
309309
if exit_code == 0:
310310
self.live.update(
311-
message if message else "",
311+
(message if no_border else Panel(message)) if message else "",
312312
refresh=True,
313313
)
314314
else:

sdk/python/packages/flet-cli/src/flet_cli/utils/android_sdk.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,11 @@ def sdkmanager_exe(self, home_dir):
8484
assert bin
8585
return bin / self.tool_exe("sdkmanager", ".bat")
8686

87+
def avdmanager_exe(self, home_dir):
88+
bin = self.cmdline_tools_bin(home_dir)
89+
assert bin
90+
return bin / self.tool_exe("avdmanager", ".bat")
91+
8792
@staticmethod
8893
def has_minimal_packages_installed() -> bool:
8994
home_dir = AndroidSDK.android_home_dir()
@@ -100,6 +105,26 @@ def has_minimal_packages_installed() -> bool:
100105

101106
return True
102107

108+
def delete_avd(self, home_dir: Path, avd_name: str) -> None:
109+
"""
110+
Deletes an Android Virtual Device using avdmanager.
111+
"""
112+
self.log(f'Deleting Android emulator "{avd_name}"')
113+
result = self.run(
114+
[
115+
self.avdmanager_exe(home_dir),
116+
"delete",
117+
"avd",
118+
"-n",
119+
avd_name,
120+
],
121+
env={"ANDROID_HOME": str(home_dir)},
122+
capture_output=True,
123+
)
124+
if result.returncode != 0:
125+
self.log(result.stderr or result.stdout)
126+
raise RuntimeError(f'Failed to delete Android emulator "{avd_name}"')
127+
103128
def cmdline_tools_url(self):
104129
try:
105130
url_platform = {

0 commit comments

Comments
 (0)