Simulators, emulators and devices
A desktop target has one obvious place to run: this machine. A mobile target does not — you might have two simulators booted, a phone plugged in, and an emulator running, all at once.
So ios-uikit, android-mdc and harmony-arkui each hold a list of devices in the Day view,
which you build up yourself and which stays put between sessions.
Adding one
Hover a mobile target row and press +. The picker opens straight away and spins while the CLI looks, so the click always feels like it did something — enumerating simulators and phones takes a moment.
You will see:
- every booted simulator, connected phone and running emulator for that platform
- any simulator or AVD that exists but is not running, which the picker offers to start
Devices already on the list are shown with a check and cannot be added twice. Each device carries
the flag that selects it, so iOS picks the right one of --ios-simulator and --ios-device on its
own. Nothing is guessed by the extension.
The device you add becomes a row under the target. It stays there whether or not that phone is
plugged in — the row says connected, not running or not found from whatever the extension
last learned, rather than disappearing when you unplug something.
Choosing which ones run
Every device row has a checkbox. The target’s Play — and the project’s Run — launch onto the ticked devices, one task and one terminal each, so you can watch two simulators side by side while a third stays configured but idle.
The target’s own checkbox is the all-or-nothing switch for the devices under it: ticking it ticks them all, unticking it clears them. Untick the last device and the target unticks itself, since there is nothing left for it to launch onto.
A partly ticked target reads 1 of 2 devices in its row. VS Code’s tree checkboxes are
two-state — there is no half-ticked box to show — so the count is where partial selection is
visible. The box itself stays ticked while any device is, which is exactly when the target still
runs.
Running them
Each device row has its own Play, which launches on that device alone whether or not it is ticked. Remove a device with Remove Device on its right-click menu; removing one that is running stops it first.
Play first asks the CLI where the device stands, which is adb or simctl and can take a
few seconds. For that long the row reads checking… with a spinner and its Play button is
put away, so the click has visibly landed and a second one cannot queue a second launch.
Press Play on a simulator or emulator that is not running and you are asked first:
The “iPad (A16)” iOS simulator is not currently running.
Launch It starts it, waits for it to finish booting, and then runs the app on it. Cancel leaves everything as it was. Without the question this was a build’s worth of waiting followed by “not connected” in the terminal, for something the row had been saying all along. A plugged-in phone is never asked about — there is nothing to start — and neither is a target whose devices have not been enumerated yet.
Starting and stopping the device itself
A simulator or emulator row’s right-click menu offers Start Simulator when it is not running and Stop Simulator when it is — Start Emulator and Stop Emulator on Android — so a device you configured once is one click from being up, and one click from giving back the memory it holds. Stopping also stops any app running on it first, since a run left attached to a device that has gone has no row to stop it from.
Physical phones get neither entry. There is no software to start, and unplugging one is the real way to stop it.
The row itself says which state it is in, so the menu never surprises you: connected,
not running, or not found for a simulator that has since been deleted. That reading comes from
the CLI, and the extension asks only about the platform whose rows are on screen.
An Android emulator that comes back on a different adb serial keeps its row. The serial is a console port rather than a name — it slides when another emulator holds it — so the row remembers the AVD and follows the emulator to wherever it lands, keeping its place in the list and its tick.
A row that predates that will say not found once its emulator stops, since a serial on its own
names nothing. Its menu offers Start Emulator… — with the ellipsis, because it asks which AVD
the row is — and it only asks once: the answer is stored, and from then on the row behaves like
any other. Rows whose emulator is running are repaired without being asked, the moment the
extension next looks at that platform.
An empty list means every connected device
A target with no devices configured launches onto every runtime of that kind the CLI can see — its own default. That is usually what you want when you have exactly one, and it is what makes a capture sweep across several simulators work. Add devices when you mean particular ones.
Starting a simulator that is not running
iOS cannot install onto a shut-down simulator, so picking one used to be a dead end. Now the picker offers it and starts it for you.
The row appears immediately, reading Booting… with a spinner, and stays there while the
device comes up — the CLI waits for the real thing (simctl bootstatus on iOS,
sys.boot_completed on Android), not merely for the boot to have been asked for. An emulator that
lands on a different adb serial than expected takes its row with it.
If it does not start, you get the CLI’s own diagnosis in a dialog — for an emulator that includes
the tail of its log — and the row reads failed to start until the device is actually seen. The
row stays, so Start Emulator on it is the retry.
A slow emulator is the interesting case, because “gave up waiting” and “never coming” are not the
same thing. After a boot the extension keeps looking for that one device for a few minutes, so an
emulator that arrives late corrects its own row without being asked. It watches the device it was
told to start and nothing else: enumerating Android starts an adb server that outlives the
command, and putting that on a timer would keep one alive on every machine with a Day project
open, including those whose author is working on iOS.
One platform at a time
Opening the iOS picker asks about iOS only. It does not run adb, and the Android picker does not
run simctl. Beyond being faster, this matters because adb starts a background server the moment
it is invoked, and taking a look at your simulators is no reason to start one.
What it needs installed
The picker shows what the CLI can see, so an empty list usually means a missing SDK rather than a missing device:
| Target | Needs | Where to set it |
|---|---|---|
ios-uikit |
Xcode, and a booted simulator or a trusted device | day.xcodeDeveloperDirectory |
android-mdc |
The Android SDK, with platform-tools |
day.androidSDKHome |
harmony-arkui |
The OpenHarmony SDK, with hdc |
day.harmonyNDKHome |
Those settings are exported for every day command the extension runs, including
Day: Doctor — so Doctor reports on the same toolchains your builds will use. That matters most
when VS Code was launched from the Dock or Start menu and inherited none of your shell’s
environment.
Run Day: Doctor (check toolchains) first if the list is empty; it names what is missing.
From a terminal
The same enumeration the picker uses:
day devices list -p ios-uikit
day devices boot -p ios-uikit "iPhone 16 Pro"
day launch -p ios-uikit --ios-simulator "iPhone 16 Pro"
day devices shutdown -p ios-uikit "iPhone 16 Pro"
See the CLI reference for every flag, and Platforms for what each mobile target expects.