6.3 KiB
To AI Agent: Implement Server-Hosted Android APK Download Button and Automated Build Deployment Pipeline
1. Context & Feature Objective
We are adding a native Android app distribution workflow directly from our self-hosted server backend. Instead of relying purely on app stores, users visiting the web version from an Android device must be able to download the official compiled .apk file directly.
Objective: 1. Backend Asset Exposure: Configure a secure, static file directory on the Node.js/Express server to host the production .apk binary.
2. Build Pipeline Link Automation: Create a post-build deployment shell script. Every time a new production Android APK is generated (release), the script must automatically rename and copy it to the backend's public distribution folder under a persistent file pointer name (yotrip-latest.apk).
3. Frontend Action Button: Add an interactive "Tải ứng dụng Android" action row with a download icon inside both the Member and Guest profile menu sheets.
2. Technical Architecture & Implementation Steps
[Android Build Output] ➔ [deploy-apk.sh Script] ➔ [Backend public/downloads/yotrip-latest.apk] ▲ [Frontend UI Button] ➔ ➔ ➔ [Triggers HTTP GET Request] ➔ ➔ ➔ ➔ ➔ ┛
Step 1: Configure Backend Static Asset Folder
Locate the core server setup file (e.g., server.ts, app.ts, or index.js). Ensure a dedicated folder path named public/downloads is created and mapped to express static file serving handlers:
import express from 'express';
import path from 'path';
const app = express();
// Ensure the directory exists: public/downloads/
const downloadsDir = path.join(__dirname, '../public/downloads');
/* ✅ BACKEND STATIC MIDDLEWARE REGISTRATION
This exposes the file at: [https://yourdomain.com/downloads/yotrip-latest.apk](https://yourdomain.com/downloads/yotrip-latest.apk)
*/
app.use('/downloads', express.static(downloadsDir, {
setHeaders: (res) => {
// Force browser engines to download the file directly instead of trying to parse it
res.set('Content-Type', 'application/vnd.android.package-archive');
res.set('Content-Disposition', 'attachment; filename="yotrip-latest.apk"');
}
}));
### Step 2: Automate APK Release Mapping Link (Post-Build Script)
Create an automation script file named scripts/deploy-apk.sh in the root environment. This script runs instantly after your Android compiler output is generated (e.g., via Gradle ./gradlew assembleRelease or Capacitor/Cordova build actions):
#!/bin/bash
# Define relative path coordinates
ANDROID_OUTPUT_PATH="./android/app/build/outputs/apk/release/app-release.apk"
BACKEND_TARGET_DIR="./backend/public/downloads"
TARGET_FILE_NAME="yotrip-latest.apk"
echo "🚀 Starting automated post-build Android deployment pipeline..."
# 1. Verify compiler target exists
if [ -f "$ANDROID_OUTPUT_PATH" ]; then
# 2. Ensure target storage folder structure is active
mkdir -p "$BACKEND_TARGET_DIR"
# 3. Copy and force overwrite the old production bundle with the updated version
cp -f "$ANDROID_OUTPUT_PATH" "$BACKEND_TARGET_DIR/$TARGET_FILE_NAME"
echo "✅ Success! New build copied safely to $BACKEND_TARGET_DIR/$TARGET_FILE_NAME"
echo "🔗 Direct Download Link Active: /downloads/$TARGET_FILE_NAME"
else
echo "❌ Critical Error: Android build output artifact not found at $ANDROID_OUTPUT_PATH"
exit 1
fi
Add "build:android": "cd android && ./gradlew assembleRelease && cd .. && sh scripts/deploy-apk.sh" inside package.json scripts matrix for unified execution hooks.
### Step 3: Add Download Trigger into Frontend UI (MapProfileDropdown.tsx)
Locate the unified dropdown menu component created in the previous layout consolidation phase. Inject the direct-download operational action rows:
// Define the static destination asset link helper
const APK_DOWNLOAD_URL = `${process.env.REACT_APP_API_BASE_URL || ''}/downloads/yotrip-latest.apk`;
/* --- INSIDE AUTHENTICATED MEMBER STACK SECTION --- */
<div className="flex flex-col space-y-1">
{/* Existing Dashboard, Profile, Create Tour rows... */}
{/* NEW: ANDROID APK DIRECT DOWNLOAD BUTTON */}
<a
href={APK_DOWNLOAD_URL}
download="yotrip.apk"
className="flex items-center gap-3 w-full px-4 py-3 hover:bg-slate-800 rounded-lg text-left text-xs text-slate-200 transition-colors"
>
<svg xmlns="[http://www.w3.org/2000/svg](http://www.w3.org/2000/svg)" className="w-4 h-4 text-green-400 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v1m-4-4l-4 4m0 0l-4-4m4 4V4" />
</svg>
<span>Tải ứng dụng Android (APK)</span>
</a>
</div>
/* --- INSIDE ANONYMOUS GUEST STACK SECTION --- */
<div className="flex flex-col space-y-1">
{/* Existing Guest language/theme configurations... */}
<div className="h-[1px] bg-slate-800 my-1 mx-2" />
{/* NEW: GUEST STATE ANDROID APK DOWNLOAD BUTTON */}
<a
href={APK_DOWNLOAD_URL}
download="yotrip.apk"
className="flex items-center gap-3 w-full px-4 py-3 hover:bg-slate-800 rounded-lg text-left text-xs text-slate-200 transition-colors"
>
<svg xmlns="[http://www.w3.org/2000/svg](http://www.w3.org/2000/svg)" className="w-4 h-4 text-green-400 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M12 18h.01M8 21h8a2 2 0 002-2V5a2 2 0 00-2-2H8a2 2 0 00-2 2v14a2 2 0 002 2z" />
</svg>
<span>Cài đặt bản Android (.APK)</span>
</a>
{/* Existing Register/Login Button row below... */}
</div>
## 3. Verification & Acceptance Criteria for AI Agent
[ ] Deployment Script Validation: Run the build sequence script. Confirm that backend/public/downloads/yotrip-latest.apk updates its file modification timestamp matching the compiler execution timing logs.
[ ] Direct Download Header Safety: Trigger a request to GET /downloads/yotrip-latest.apk. The network tab response must show content-type: application/vnd.android.package-archive to guarantee mobile devices instantly trigger package installation workflows.
[ ] UI Integrity Test: Open the drop menu layout panel on a mobile simulator frame. Confirm that clicking the text icon row acts as a standard link target that downloads the binary file smoothly without breaking route navigation states.