Install and Auto Launch Expo Release APK with Dynamic Package Name Checking
Why Dark Mode is not Triggered by theme Change and how to fix it

Building Enterprise Softwares🚀 | Sharing insights on Linkedin/in/Waleed-javed
Working with Expo in the bare workflow (or after running expo prebuild) opens up the full power of native Android development — but it also introduces some common pain points.
Two of the biggest issues developers run into are:
System theme not detected correctly in debug mode
APK installation/launch errors due to package name mismatches or Windows path quirks
This post will walk you through both problems and provide clean, repeatable solutions.
🌗 The “Always Light Theme” Problem in Debug Mode
If you’ve ever tried using Appearance.getColorScheme() or the useColorScheme() hook on Android, you might have noticed:
👉 In Chrome remote debug mode/EXPO GO, the theme always comes back as "light", even if your system is in dark mode.
Why this happens
When debugging remotely, React Native runs JS inside Chrome instead of the device’s JS engine.
The native bridge for
Appearancedoesn’t propagate system theme info to Chrome.As a result, you always get
"light"in debug builds.
✅ The fix
To see the real theme:
Turn off Remote Debugging (shake menu → “Stop Debugging”).
Or build a release APK (
gradlew assembleRelease). ORnpx expo run:androidOr use Flipper/React Native DevTools instead of Chrome debugger.
In other words: you can’t get the correct theme in Chrome debug mode — you need to run on-device JS.
Installing and Launching Expo APKs with the Correct Package Name Dynamically
Another common problem is launching APKs manually with adb especially for release apk build or standalone builds.
When you’re working with an Expo project in the bare workflow or after running expo prebuild, you’ll often want to install your APK directly on a device or emulator using adb. Sounds simple enough, right?
adb install app-release.apk
adb shell am start -n com.example.myapp/.MainActivity
But then you get an error like:
Error: Activity class {com.example.myapp/com.example.myapp.MainActivity} does not exist.
Or worse:
No activities found to run, monkey aborted.
Why does this happen? And how do you avoid hard-coding your package name incorrectly every time?
🔍 Where the package name actually comes from
In a standard EXPO Android project, you can find your app’s ID (applicationId) inside android/app/build.gradle:
defaultConfig {
applicationId "com.example.myapp"
}
This applicationId becomes the runtime package name used inside the APK.
But in Expo managed projects, things look different:
AndroidManifest.xmloften doesn’t include apackageattribute at all.build.gradlemay not show yourapplicationIddirectly.Instead, Expo injects it dynamically from
app.jsonduring theprebuildstep.
✅ The real source of truth: app.json
Open your app.json and look for this section:
{
"expo": {
"android": {
"package": "com.waleedjavied01.subtrack" // "domain.username.appId"
}
}
}
That value (com.waleedjavied01.subtrack) is the actual package ID used inside your APK.
So instead of guessing, we can read it directly from app.json and use it when running adb.
⚡ Automating with a Node script
Expo app when built using Default Template comes with a dir structure that has the scripts directory.
Here’s a simple script (scripts/installRunApk.js) that installs and launches your APK using the correct package ID:
// scripts/installRunApk.js
import fs from "fs";
import path from "path";
import { execSync } from "child_process";
const appJsonPath = path.resolve("app.json"); // GET PATH
const appJson = JSON.parse(fs.readFileSync(appJsonPath, "utf8")); // PARSE THE INFORMATION
const appId = appJson.expo?.android?.package; // GET PACKAGE NAME
if (!appId) {
throw new Error("Could not find expo.android.package in app.json");
}
console.log("Detected App ID:", appId);
// Pick APK (release preferred, fallback to debug)
const releaseApk = "android/app/build/outputs/apk/release/app-release.apk";
const debugApk = "android/app/build/outputs/apk/debug/app-debug.apk";
const apkPath = fs.existsSync(releaseApk) ? releaseApk : debugApk;
console.log(`Installing APK: ${apkPath}`);
execSync(`adb install -r ${apkPath}`, { stdio: "inherit" });
console.log("Launching app...");
execSync(`adb shell am start -n ${appId}/.MainActivity`, { stdio: "inherit" }); //DYNAMICALLY INJECTED appId
Then add an npm script in your package.json:
{
"scripts": {
"launch-apk": "node ./scripts/installRunApk.js"
}
}
Now you can simply run:
npm run launch-apk
✅ This will:
Read the package name dynamically from
app.json.Detect whether you have a release or debug APK.
Install and launch the app without any guesswork.
🎉 Why this matters
No more case sensitivity mistakes (
subTrackvssubtrack).Works consistently across debug and release builds.
One command handles both install and launch.
Uses the Expo single source of truth (
app.json) instead of relying on fragile manual edits.
🔮 Final thoughts
If you’re moving between Expo managed and bare workflows, or you often test APKs outside of Expo Go, this little script can save you tons of time.
Instead of memorizing your package ID or re-typing long adb commands, you can just run:
npm run launch-apk
…and your app will be up and running on the emulator or device instantly 🚀
🎉 Final Takeaways
Theme detection: works only in on-device JS (turn off Chrome debugger).
Package name: always read from
app.json→expo.android.package.Automation: use a Node script to install & launch APKs without guesswork.
With these fixes, you can avoid hours of head-scratching and streamline your Expo Android workflow 🚀





