Android app for printing price labels to a USB-connected Zebra ZD410 printer.
- Enter a price on a numeric keypad
- Choose label size (25×13 mm, 46.8×81 mm, 50.8×25.4 mm)
- Print ZPL labels with centered price and black pill styling (small label)
- Fine-tune print position per label size (saved permanently)
- USB Zebra printer discovery and printing
- Node.js ≥ 22.11
- Android SDK and a connected device or emulator
- Zebra ZD410 (or compatible ZPL USB printer)
npm install
npm start
npm run androidIn one terminal, keep Metro running (npm start). In another, build and launch the app (npm run android).
- Open Android Studio → Device Manager and start a virtual device (or run
emulator -list-avdsthenemulator -avd <name>). - With Metro running, install and launch the app:
npm run androidThe emulator can exercise the UI, but it has no USB host access — printer discovery and printing require a physical device.
Useful when the tablet/phone stays on the desk and you do not want a USB cable for Metro/adb. USB printing still needs the OTG cable to the Zebra printer.
One-time setup (device on the same Wi‑Fi as your PC):
- Enable Developer options and USB debugging on the device.
- Connect the device with USB and confirm it appears in
adb devices. - Switch adb to TCP/IP mode:
adb tcpip 5555- Find the device IP (Wi‑Fi settings, or
adb shell ip route | awk '{print $9}'). - Connect over the network:
adb connect <device-ip>:5555
adb devicesYou should see <device-ip>:5555 listed. You can unplug USB.
Daily dev:
npm start
npm run androidIf the wireless adb connection drops (sleep, reboot, network change), repeat from adb connect <device-ip>:5555. On Android 11+, you can also pair from Developer options → Wireless debugging if adb tcpip is unavailable.
npm run build:apkOutput: dist/ZebraLabel-<version>-release.apk
Copy android/keystore.properties.example to android/keystore.properties and fill in your passwords. The keystore file lives at the repo root (zebra-label-release.keystore, gitignored). Without keystore.properties, release builds fall back to the debug keystore.
Add these repository secrets (Settings → Secrets and variables → Actions):
| Secret | Value |
|---|---|
ANDROID_KEYSTORE_BASE64 |
Base64 of zebra-label-release.keystore (see below) |
ANDROID_KEYSTORE_PASSWORD |
Keystore password |
ANDROID_KEY_ALIAS |
zebra-label |
ANDROID_KEY_PASSWORD |
Key password |
Encode the keystore (Git Bash / Linux / macOS):
base64 -w 0 zebra-label-release.keystorePowerShell:
[Convert]::ToBase64String([IO.File]::ReadAllBytes("zebra-label-release.keystore"))Paste the single-line output into ANDROID_KEYSTORE_BASE64. Release tags and pushes to main build a Play Store–compatible signed APK; pull requests still use the debug keystore so forks do not need secrets.
Set up wireless adb once as in Physical device over Wi‑Fi, then install or update the release APK without USB:
adb connect <device-ip>:5555
adb install -r dist/ZebraLabel-<version>-release.apk-r replaces an existing install. If adb lists multiple devices, target the tablet explicitly:
adb -s <device-ip>:5555 install -r dist/ZebraLabel-<version>-release.apkReplace <version> with the version from package.json (e.g. 0.0.1). If the connection dropped, run adb connect <device-ip>:5555 again before installing.
Run the GitHub Actions workflow locally with act. The --artifact-server-path flag is required so upload-artifact can store the built APK under .artifacts/:
act workflow_dispatch -W .github/workflows/build-apk.yml -j build-apk --artifact-server-path .artifactsOn the first run, Cache not found for input keys: Linux-android-ndk-… is normal — nothing has been saved yet. The NDK is downloaded by sdkmanager, then cached at the end of the job.
Important: actions/cache only saves when the job succeeds (post-if: success()). If a previous local run failed (for example at upload-artifact without --artifact-server-path), the NDK cache was never written and the next run will miss again. Gradle caches may still restore because setup-gradle saves independently. Once a run completes successfully, subsequent runs should restore the NDK cache.
Update the Owl baseline (used in the README image above):
npm run owl:test:android:updateThis writes .owl/baseline/android/home-screen.png.
| Path | Purpose |
|---|---|
App.tsx |
App entry (safe area wrapper) |
src/components/AppContent.tsx |
Main UI |
src/buildZpl.ts |
ZPL label templates |
src/labelOffsetStorage.ts |
Persistent position offsets |
android/.../ZebraPrinterModule.kt |
USB print native module |
scripts/build-apk.js |
Release APK build script |
keystore creation:
keytool -genkeypair -v -storetype PKCS12 -keystore zebra-label-release.keystore -alias zebra-label -keyalg RSA -keysize 2048 -validity 10000 -dname "CN=Zebra Label, OU=Development, O=Seb Green, L=Dresden, ST=Saxony, C=DE, EMAILADDRESS=sebgreenbus@gmail.com"
