Uninstall a Mob app from one or more connected devices.
By default, uninstalls the current project's app from the auto-detected device (when exactly one device is connected). For other scopes, pass the relevant flag.
Usage
mix mob.uninstall # this app, one device
mix mob.uninstall --all-devices # this app, all emulators/sims (NOT phones)
mix mob.uninstall --all-physical # this app, every physical device
mix mob.uninstall --all-devices --all-physical # literally everything
mix mob.uninstall --device emulator-5554 # this app, one named device
mix mob.uninstall --device foo --device bar # this app, several
mix mob.uninstall --all-apps # one device, every mob app
mix mob.uninstall --all-devices --all-apps # every mob app on every emulator/sim
mix mob.uninstall --bundle-id com.x.y # override the project's bundle_id
mix mob.uninstall --bundle-prefix com.acme # override the prefix for --all-apps
mix mob.uninstall --yes # skip the confirmation prompt
mix mob.uninstall --help # this help
mix mob.uninstall -h # this helpOptions
--device <id>— Target a specific device by serial or name. Repeat for multiple devices:--device a --device b. Works for any device type —--deviceis the explicit override that bypasses the emulator/physical filter.--all-devices— Target every connected emulator or simulator. Physical devices are NEVER swept by this flag — that requires--all-physicalor--device <id>explicitly. Rationale: emulators are disposable; physical devices are someone's personal phone, with potential to do real damage.--all-physical— Target every connected physical device (iPhones over USB/Wi-Fi, Android devices via adb). Opt-in. Composes with--all-devicesto mean "literally everything."--bundle-id <id>— Uninstall this specific bundle id instead of auto-detecting the project's.--all-apps— Match every installed package whose id starts withbundle_prefix(defaults toMOB_BUNDLE_PREFIXenv orcom.example). Useful for clearing stale test apps in one shot.--bundle-prefix <prefix>— Override the prefix used by--all-apps. Defaults toMobDev.Config.bundle_prefix/0.--yes— Skip the y/N confirmation prompt. The prompt is skipped automatically when targeting exactly one app on exactly one device (low blast radius).--help/-h— Print this help text.
Default scope behaviour
When you pass no flags:
- 0 devices connected → exit with "no devices found"
- 1 emulator/sim connected → auto-target it (physical devices are never the auto-target — they require explicit selection)
1 devices or only physical devices → exit with an instruction to pass
--device,--all-devices, or--all-physical. Never silently fans out without consent.
Per-platform mechanics
- Android —
adb -s <serial> uninstall <pkg>. "Unknown package" responses bucket as skipped (the app wasn't installed), everything else non-zero is a failure. - iOS simulator —
xcrun simctl uninstall <udid> <bundle>, followed by a probe throughsimctl listappsto distinguish actual uninstall from "wasn't installed in the first place". - iOS physical device —
xcrun devicectl device uninstall app --device <udid> <bundle>.ContainerLookupErrorDomainin output → not installed (skipped). Requires the device to be paired and trusted via Xcode.
Summary
Functions
Build the summary lines for an uninstall run. Pure — pinned by tests
in test/mix/tasks/mob_uninstall_test.exs. Public so the rendering
invariants (skipped never gets counted as failed, etc.) can be
asserted independent of connected hardware.
Decides whether to skip the y/N confirmation prompt before executing
a plan. Returns true when
Functions
@spec format_summary([MobDev.Uninstaller.result()], [MobDev.Uninstaller.result()], [ MobDev.Uninstaller.result() ]) :: [String.t()]
Build the summary lines for an uninstall run. Pure — pinned by tests
in test/mix/tasks/mob_uninstall_test.exs. Public so the rendering
invariants (skipped never gets counted as failed, etc.) can be
asserted independent of connected hardware.
@spec should_skip_prompt?( MobDev.Uninstaller.plan(), keyword() ) :: boolean()
Decides whether to skip the y/N confirmation prompt before executing
a plan. Returns true when:
- the user passed
--yes(any truthy value atopts[:yes]), OR - the plan targets exactly one device with exactly one bundle id (low blast radius — destructive intent is unambiguous and the preview already showed exactly what's about to happen).
Pure for testability — the original inline opts[:yes] or single_target? form crashed on BadBooleanError because
opts[:yes] is nil when the flag isn't passed and Elixir 1.20's
type checker enforces boolean operands on or. Pinning this as a
helper means the boolean-coercion mistake can't happen again.