c3f0f3c35c
Build & Push Docker Images / test-backend (push) Successful in 53s
Build & Push Docker Images / test-frontend (push) Successful in 58s
Build & Push Docker Images / build-backend (push) Successful in 1m4s
Build & Push Docker Images / build-frontend (push) Failing after 56s
197 lines
8.4 KiB
Markdown
197 lines
8.4 KiB
Markdown
# Native Mobile App Build Guide
|
||
|
||
FarmaFinder ships as a PWA and is wrapped for the App Store / Play Store via [Capacitor 6](https://capacitorjs.com). The web frontend in `frontend/` is bundled into each native shell — there's no separate codebase for Android or iOS.
|
||
|
||
```
|
||
FarmaFinder/
|
||
├── frontend/ # React + Vite PWA (the actual UI)
|
||
├── backend/ # Node API
|
||
├── android/ # Capacitor-generated Android Studio project
|
||
├── ios/ # Capacitor-generated Xcode project
|
||
└── capacitor.config.json
|
||
```
|
||
|
||
The `android/` and `ios/` folders are real native projects — open them in their IDEs, change Swift / Kotlin / Gradle / xib as needed.
|
||
|
||
---
|
||
|
||
## One-time setup
|
||
|
||
### Common (any OS)
|
||
```bash
|
||
npm run install:all # root + frontend + backend deps
|
||
```
|
||
|
||
### Android prerequisites
|
||
- **JDK 21** (or 17). Install via your package manager or [Adoptium](https://adoptium.net/).
|
||
- **Android Studio** (Hedgehog or later) with the Android SDK Platform 34 installed.
|
||
- Set `ANDROID_HOME` and add `platform-tools` to `PATH`, e.g.:
|
||
```bash
|
||
export ANDROID_HOME="$HOME/Android/Sdk"
|
||
export PATH="$ANDROID_HOME/platform-tools:$ANDROID_HOME/cmdline-tools/latest/bin:$PATH"
|
||
```
|
||
- Open `android/` once in Android Studio so Gradle resolves the dependencies.
|
||
|
||
### iOS prerequisites (macOS only)
|
||
- **Xcode 15+** from the App Store. Run it once and accept the licence.
|
||
- **Command Line Tools**: `xcode-select --install`
|
||
- **CocoaPods**: `sudo gem install cocoapods` (or `brew install cocoapods`)
|
||
- After cloning, run pod install inside `ios/App`:
|
||
```bash
|
||
cd ios/App && pod install && cd -
|
||
```
|
||
|
||
---
|
||
|
||
## Day-to-day workflow
|
||
|
||
1. **Build the web bundle**
|
||
```bash
|
||
npm run build:web
|
||
```
|
||
Produces `frontend/dist/`, which Capacitor copies into the native projects.
|
||
|
||
2. **Sync into the native shells**
|
||
```bash
|
||
npm run cap:sync
|
||
```
|
||
- Copies `frontend/dist/` → `android/app/src/main/assets/public/` and `ios/App/App/public/`
|
||
- Re-runs `pod install` if new plugins were added
|
||
- Updates `capacitor.config.json` inside each platform
|
||
|
||
3. **Open the native project in its IDE**
|
||
```bash
|
||
npm run cap:open:android # Android Studio
|
||
npm run cap:open:ios # Xcode (macOS only)
|
||
```
|
||
|
||
4. **Run on a device or emulator**
|
||
```bash
|
||
npm run cap:run:android
|
||
npm run cap:run:ios
|
||
```
|
||
These build the web bundle, sync, and launch on the first connected device/emulator.
|
||
|
||
After editing anything under `frontend/`, repeat steps 1 + 2 (or use `cap:sync`). Editing native files (Gradle, Swift, Kotlin, plist) is done directly inside `android/` and `ios/` — those edits survive `cap sync`.
|
||
|
||
---
|
||
|
||
## Pointing the app at the backend
|
||
|
||
The native bundle loads `index.html` from inside the app (`capacitor://localhost/` on iOS, `https://localhost/` on Android by default), so **relative `/api/...` paths do not reach the backend**. Pick one of:
|
||
|
||
### Option A — wrap the deployed PWA (simplest)
|
||
Add a `server.url` to `capacitor.config.json` so the WebView loads the production site:
|
||
```json
|
||
{
|
||
"server": {
|
||
"url": "https://farmacias.hacecalor.net",
|
||
"cleartext": false,
|
||
"androidScheme": "https"
|
||
}
|
||
}
|
||
```
|
||
Run `npm run cap:sync` after the change. The native app is now a thin shell around the live PWA — fastest to ship, but useless without a network.
|
||
|
||
### Option B — bundled web + absolute API URLs (recommended)
|
||
Keep the web assets bundled (no `server.url`) and rewrite frontend `fetch()` calls to use an absolute backend URL. The path of least resistance:
|
||
|
||
1. Add a Vite env var to `frontend/.env.production`:
|
||
```
|
||
VITE_API_BASE=https://farmacias.hacecalor.net
|
||
```
|
||
2. Centralise `fetch` calls through a helper (e.g. `frontend/src/utils/api.js`):
|
||
```js
|
||
const BASE = import.meta.env.VITE_API_BASE || '';
|
||
export const apiUrl = (path) => `${BASE}${path.startsWith('/') ? path : '/' + path}`;
|
||
```
|
||
3. Replace `fetch('/api/...')` with `fetch(apiUrl('/api/...'))` across the components.
|
||
4. On the backend, set `CORS_ORIGIN=https://farmacias.hacecalor.net,capacitor://localhost,https://localhost` so the native WebView origin is allowed.
|
||
|
||
This lets the app work offline for cached UI and only hits the network for live data.
|
||
|
||
---
|
||
|
||
## Permissions already declared
|
||
|
||
The two manifests are pre-wired for the features the PWA already uses:
|
||
|
||
**`android/app/src/main/AndroidManifest.xml`**
|
||
- `INTERNET` — network calls
|
||
- `ACCESS_FINE_LOCATION` / `ACCESS_COARSE_LOCATION` — "Sort by distance"
|
||
- `POST_NOTIFICATIONS` — Android 13+ push opt-in
|
||
|
||
**`ios/App/App/Info.plist`**
|
||
- `NSLocationWhenInUseUsageDescription` — shown by the OS when geolocation is requested
|
||
- `NSLocationAlwaysAndWhenInUseUsageDescription` — same for background-style requests
|
||
- `UIBackgroundModes: remote-notification` — required for APNs push delivery
|
||
|
||
If you add features that need new permissions (camera, contacts, etc.), edit those files directly.
|
||
|
||
---
|
||
|
||
## App icons & splash screens
|
||
|
||
Capacitor generated placeholder assets:
|
||
- Android: `android/app/src/main/res/mipmap-*/ic_launcher*.png`
|
||
- iOS: `ios/App/App/Assets.xcassets/AppIcon.appiconset/`
|
||
- Splash: `ios/App/App/Assets.xcassets/Splash.imageset/` and the Android theme
|
||
|
||
Replace these with real artwork before shipping. The easiest way is the official asset generator — drop a single 1024×1024 PNG (and a foreground/background for adaptive icons) into a `resources/` folder at the project root and run:
|
||
```bash
|
||
npx @capacitor/assets generate --iconBackgroundColor '#0f766e' --splashBackgroundColor '#0f766e'
|
||
```
|
||
(install once: `npm install --save-dev @capacitor/assets`)
|
||
|
||
Splash colour and behaviour are configured in `capacitor.config.json` (`plugins.SplashScreen`). The runtime hook in `frontend/src/utils/native.js` hides the splash after React mounts.
|
||
|
||
---
|
||
|
||
## Push notifications on native
|
||
|
||
The web PWA uses VAPID + the browser PushManager. **Native iOS and Android cannot use Web Push.** To deliver push from the existing backend to native shells:
|
||
|
||
- **Android** — set up Firebase Cloud Messaging, add `android/app/google-services.json`, install `@capacitor/push-notifications`. The backend then sends to FCM tokens instead of (or in addition to) the existing Web Push subscriptions.
|
||
- **iOS** — create an APNs key in the Apple Developer portal, enable the Push capability in Xcode, configure the `aps-environment` entitlement, install `@capacitor/push-notifications`.
|
||
|
||
Until those are wired up, the bell button in the native app is a no-op — `pushSupported()` in `frontend/src/utils/notifications.js` returns false because there's no service worker in the native WebView.
|
||
|
||
---
|
||
|
||
## Releasing
|
||
|
||
### Android
|
||
1. In Android Studio: **Build → Generate Signed Bundle / APK → Android App Bundle**.
|
||
2. Create or reuse a keystore; the password unlocks the release signing config.
|
||
3. Bump `versionCode` and `versionName` in `android/app/build.gradle` for each release.
|
||
4. Upload the produced `.aab` to Google Play Console.
|
||
|
||
### iOS
|
||
1. In Xcode: select an **Any iOS Device (arm64)** destination → **Product → Archive**.
|
||
2. From the Organizer, **Distribute App → App Store Connect**.
|
||
3. Bump `CURRENT_PROJECT_VERSION` (build) and `MARKETING_VERSION` (display) under the App target's *General* tab.
|
||
4. Submit via App Store Connect.
|
||
|
||
Both stores require: privacy policy URL, screenshots (multiple device sizes), an app description, and an age rating questionnaire. The pharmacy domain typically triggers an extra medical-content review on iOS — keep marketing copy factual.
|
||
|
||
---
|
||
|
||
## Updating Capacitor / plugins
|
||
|
||
```bash
|
||
npm install @capacitor/core@latest @capacitor/cli@latest \
|
||
@capacitor/android@latest @capacitor/ios@latest \
|
||
@capacitor/app@latest @capacitor/splash-screen@latest @capacitor/status-bar@latest
|
||
npm run cap:sync
|
||
```
|
||
After major upgrades follow the migration guide at https://capacitorjs.com/docs/updating.
|
||
|
||
---
|
||
|
||
## Troubleshooting
|
||
|
||
- **`cap sync` says `Could not find platform 'android'`** — you removed the folder. Re-create with `npx cap add android`.
|
||
- **iOS build fails with `Sandbox: rsync ... deny`** — clean build folder (`Cmd-Shift-K`), then `pod install` inside `ios/App`.
|
||
- **WebView shows a white screen on Android 14** — verify `android:usesCleartextTraffic` isn't blocking your dev backend (HTTPS only by default in `capacitor.config.json`).
|
||
- **Geolocation never returns** — Android 14 requires both `ACCESS_FINE_LOCATION` *and* a runtime grant via the system dialog; iOS requires the `Info.plist` keys above plus enabling Location on the simulator (`Features → Location`).
|