app-store/UPLOAD_APP.md

3.6 KiB

Upload Android App to Local App Store

Follow these steps to add a new Android app to the Local App Store.

Prerequisites

  • Java 17: export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64
  • Android SDK: export ANDROID_HOME=/home/jarian/android-sdk
  • Gradle wrapper in your app project (run gradle wrapper if missing)
  • Signing keystore: /home/jarian/playground/app-store/signing/localstore.keystore (alias localstore, password localstore123)
  • Server running on port 9800

Step 1: Build the APK

export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64
export ANDROID_HOME=/home/jarian/android-sdk
cd /path/to/your/app/project
./gradlew :app:assembleRelease

The signed release APK will be at app/build/outputs/apk/release/app-release.apk.

Step 2: Copy APK to repos

cp app/build/outputs/apk/release/app-release.apk /home/jarian/playground/app-store/server/repos/<app_id>.apk

Use a lowercase, hyphen-separated ID (e.g., myapp, hello-world).

Step 3: Create metadata JSON

Create <app_id>.json in the repos directory alongside the APK:

{
  "name": "App Display Name",
  "description": "One or two sentences describing the app.",
  "icon": null,
  "screenshots": [],
  "package_name": "com.your.package",
  "version_name": "1.0",
  "version_code": "1",
  "min_sdk": "26",
  "target_sdk": "34",
  "permissions": ["android.permission.INTERNET"]
}

Field notes:

  • name: Human-readable display name
  • description: Short description shown in the store
  • icon: null if no PNG icon in the APK, otherwise the resource path (e.g., res/drawable-mdpi/ic_launcher.png)
  • screenshots: Array of screenshot filenames, or [] if none
  • package_name: From AndroidManifest.xml or build.gradle
  • version_name / version_code: From build.gradle defaultConfig
  • min_sdk / target_sdk: From build.gradle defaultConfig
  • permissions: Array of <uses-permission> entries from AndroidManifest.xml

Step 4: Trigger server scan

curl -X POST http://localhost:9800/api/scan

Or restart the server if preferred:

screen -S appstore -X quit 2>/dev/null
sleep 1
screen -dmS appstore bash -c "cd /home/jarian/playground/app-store/server && source venv/bin/activate && python -c 'import uvicorn, sys; sys.path.insert(0, \".\"); from main import app; uvicorn.run(app, host=\"0.0.0.0\", port=9800)'"

Step 5: Verify

curl -s http://localhost:9800/api/apps | python3 -c "
import json, sys
data = json.load(sys.stdin)
print(f'Total: {data[\"total\"]} apps')
for app in data['apps']:
    print(f'  - {app[\"name\"]}  (v{app[\"version_name\"]}, pkg={app[\"package_name\"]})')
"

Optional: Add to downloads directory

For easy browser download via /dl/ endpoint:

cp app/build/outputs/apk/release/app-release.apk /home/jarian/playground/app-store/server/downloads/<app_id>.apk

Common Issues

  • Binary XML manifest parsing: The scanner reads APKs directly without aapt. Fields like version_name may show unknown if the string parser misses them — always set them in the JSON override.
  • No PNG icon: If the APK only has XML vector drawables, set "icon": null in the JSON.
  • KSP plugin: Required for Room. Keep only in app/build.gradle, not root build.gradle.
  • Navigation: Use <fragment> tag (not FrameLayout) with android:name="androidx.navigation.fragment.NavHostFragment", or add NavHostFragment programmatically in MainActivity.
  • Data binding: Cannot create typed properties for <fragment> tags. Use programmatic NavHostFragment instead.
  • Lint: Set checkReleaseBuilds false to avoid blocking builds.