Skip to main content

Command Palette

Search for a command to run...

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

Updated
Install and Auto Launch Expo Release APK with Dynamic Package Name Checking
W

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:

  1. System theme not detected correctly in debug mode

  2. 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 Appearance doesn’t propagate system theme info to Chrome.

  • As a result, you always get "light" in debug builds.

✅ The fix

To see the real theme:

  1. Turn off Remote Debugging (shake menu → “Stop Debugging”).

  2. Or build a release APK (gradlew assembleRelease). OR npx expo run:android

  3. Or 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.xml often doesn’t include a package attribute at all.

  • build.gradle may not show your applicationId directly.

  • Instead, Expo injects it dynamically from app.json during the prebuild step.


✅ 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:

  1. Read the package name dynamically from app.json.

  2. Detect whether you have a release or debug APK.

  3. Install and launch the app without any guesswork.


🎉 Why this matters

  • No more case sensitivity mistakes (subTrack vs subtrack).

  • 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.jsonexpo.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 🚀