fix(android): respect --device on run-android when multiple devices connected#2796
Open
janicduplessis wants to merge 2 commits intoreact-native-community:mainfrom
Open
Conversation
…onnected When `react-native run-android --device <serial>` is invoked with more than one device or emulator attached, Gradle's `installDebug` task silently installs on a device of its own choosing instead of the one named by `--device`. The flag is honored only for the post-Gradle `adb install` step in `tryInstallAppOnDevice`, which is too late: the APK has already been pushed to the wrong device, and on a fresh emulator the second adb install can fail outright due to a debug-keystore mismatch left behind by the Gradle install. Root cause: `runOnSpecificDevice` builds the Gradle task list with the `'install'` prefix (yielding `app:installDebug`) but neither sets `ANDROID_SERIAL` on the gradle spawn nor passes `-Pandroid.injected.serial=<device>`, so AGP picks a device by its own logic. The interactive path already side-steps this by swapping the build task to `assemble*` (line ~230), but the non-interactive path keeps using `install*`. This change mirrors that swap for the non-interactive path: Gradle builds `app:assembleDebug` (or the corresponding flavored variant) and `tryInstallAppOnDevice` then runs `adb -s <device> install -r -d <apk>` as it always has, so the install lands on the device the user asked for. No environment variables, no AGP-injected properties, and no behavior change when only one device is connected. `runOnAllDevices` is unchanged: it still uses `install*` because it intentionally fans the install across every connected device.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
react-native run-android --device <serial>(or the deprecated--deviceId) doesn't reliably install on the requested device when more than one is attached.runOnSpecificDevicebuilds the APK and runs two installs back to back:The Gradle step uses
getTaskNames(..., 'install')(soapp:installDebug) but never setsANDROID_SERIALon the spawn nor passes-Pandroid.injected.serial=<device>(source), so AGP picks a device by its own logic. With a single device that's harmless (the redundant adb install is a no-op via-r). With two devices the Gradle step lands on the wrong one — the user-selected device still receives the APK via the subsequent adb install, but a stray install also ends up on the device the user did not ask for.The interactive branch already swaps
install*->assemble*for exactly this reason; the non-interactive path doesn't. This PR mirrors that swap. Gradle stops atapp:assembleDebug(or the corresponding flavored variant) andtryInstallAppOnDevice, which has always doneadb -s <device> install -r -d <apk>, becomes the single source of truth for which device the APK lands on. Side benefit: every single-devicerun-android --deviceinvocation now stops doing one redundant adb install per run.runOnAllDevicesis intentionally left alone — it still uses'install'because it fans the install across every connected device on purpose.Test Plan
Verified on macOS with two emulators booted (
emulator-5554andemulator-5556).Before:
react-native run-android --deviceId emulator-5554 --port 8082runs Gradle's installDebug on the other AVD:After: same command, same two emulators connected:
installDebugand the other AVD no longer appear in the log; the install lands only on the requested device.Single-device runs (only
emulator-5554attached) produce the sameassembleDebug->tryInstallAppOnDevice->adb -s emulator-5554 installsequence, which is what already happened in--interactivemode.Why this is safe
installDebugisassembleDebugplus a postlude that calls adb install — so the APK is byte-identical and lives in the samebuild/outputs/apk/<variant>/location thattryInstallAppOnDevicealready walks. The dropped Gradle adb install is functionally identical to theadb -s <device> install -r -d <apk>that runs immediately after. The sameassemble*+ post-Gradle adb-install combination has been the active path for--interactiveruns for years.Edge cases route around the change:
--tasks <list>keeps the user's tasks viaargs.tasks ?? buildTaskand ignores the prefix entirely.--binary-path <apk>skips the Gradle build (if (!args.binaryPath)guard), unaffected.prodDebugetc.) getassembleProdDebug/ their matching APK path;getInstallApkNamehandles both.The realistic regression risk is custom Gradle plugins hooked into the
installDebugtask. Such users can opt back in by passing--tasks installDebug, and they were already in this same situation under--interactive.Checklist
react-nativecheckout (instructions).