Developer Docs

Android Development Setup

Repository: ngwenya-android
Submodule: ngwenya-front/mobile/android
Requirements: Android Studio Ladybug 2024.2+, JDK 17, Apple Silicon Mac
Tested on: M1 Max, 64 GB RAM, macOS 15


Quick Start

# 1. Clone (or init submodule)
cd /path/to/ngwenya-front
git submodule update --init mobile/android
cd mobile/android

# 2. Open in Android Studio
open -a "Android Studio" .

# 3. Wait for Gradle sync โ†’ Select emulator โ†’ โ–ถ Run

Prerequisites

Tool Minimum Download
Android Studio Ladybug 2024.2+ developer.android.com/studio
JDK 17+ Bundled with Android Studio
Git 2.30+ git --version

Install Android Studio

  1. Download the Mac (Apple Silicon) .dmg from developer.android.com/studio
  2. Drag to /Applications โ†’ Launch
  3. Complete the Setup Wizard:
    • Install Type: Standard
    • Accept all SDK licenses
    • Skip Intel HAXM (Apple Silicon uses Hypervisor.framework natively)

Configure PATH

Add to ~/.zshrc:

export ANDROID_HOME=$HOME/Library/Android/sdk
export PATH=$ANDROID_HOME/emulator:$ANDROID_HOME/platform-tools:$PATH

Then source ~/.zshrc and verify:

adb --version   # Android Debug Bridge version 1.0.41+
emulator -version  # Android emulator version 35.x

Install SDK Components

Settings (โŒ˜,) โ†’ Languages & Frameworks โ†’ Android SDK:

Tab Component Required
SDK Platforms Android 15.0 (API 35) โœ… Yes
SDK Platforms Android 14.0 (API 34) Recommended
SDK Tools Build-Tools 35 โœ… Yes
SDK Tools Android Emulator โœ… Yes
SDK Tools Platform-Tools โœ… Yes
SDK Tools Google Play services โœ… Yes (for FCM)

Open the Project

Step 1: Clone

# Via submodule
cd /path/to/ngwenya-front
git submodule update --init mobile/android

# Or standalone
git clone https://github.com/mall-dev/ngwenya-android.git

Step 2: Open in Android Studio

open -a "Android Studio" /path/to/mobile/android

Click Trust Project when prompted.

Step 3: Gradle Sync

Android Studio triggers Gradle sync automatically. Look for:

  • โœ… "Gradle sync finished" in the status bar
  • ~200 MB of dependencies downloaded on first run

If it doesn't auto-trigger: File โ†’ Sync Project with Gradle Files (โŒ˜โ‡งO)

Step 4: Verify Project Structure

The Project pane (Android view) should show:

app/
โ”œโ”€โ”€ manifests/
โ”‚   โ””โ”€โ”€ AndroidManifest.xml
โ”œโ”€โ”€ java/com.mallnline.ngwenya/
โ”‚   โ”œโ”€โ”€ MainActivity.kt
โ”‚   โ”œโ”€โ”€ NgwenyaApp.kt
โ”‚   โ”œโ”€โ”€ bridge/WebViewBridge.kt
โ”‚   โ”œโ”€โ”€ services/NgwenyaFirebaseService.kt
โ”‚   โ””โ”€โ”€ ui/Screens.kt
โ”œโ”€โ”€ res/values/
โ”‚   โ””โ”€โ”€ themes.xml
โ””โ”€โ”€ build.gradle.kts

Build Configuration

The project uses Gradle Kotlin DSL with these key settings:

Property Value Notes
compileSdk 35 Android 15
minSdk 26 Android 8.0 (Oreo)
targetSdk 35 Android 15
jvmTarget 17 JDK 17
Compose BOM 2024.12.01 Material3
Firebase BOM 33.7.0 FCM

Key Dependencies

Library Purpose
androidx.compose.material3 Material Design 3 UI
androidx.navigation:navigation-compose Tab navigation
androidx.webkit:webkit Modern WebView API
com.google.firebase:firebase-messaging-ktx Push notifications
com.squareup.okhttp3:okhttp HTTP client for GraphQL

Gradle Performance (M1 Max)

The gradle.properties is tuned for your hardware:

org.gradle.jvmargs=-Xmx4096m -Dfile.encoding=UTF-8
org.gradle.parallel=true
org.gradle.caching=true

Expected build times on M1 Max / 64 GB:

  • First build: ~90 seconds
  • Incremental builds: ~5-10 seconds
  • Clean rebuild: ~45 seconds

Configure Firebase (FCM)

Firebase Cloud Messaging requires a google-services.json file.

Step 1: Create Firebase Project

  1. Go to Firebase Console
  2. Add Project โ†’ Name: Ngwenya โ†’ Create

Step 2: Register Android App

  1. Click Add App โ†’ Android
  2. Package name: com.mallnline.ngwenya
  3. App nickname: Ngwenya Android
  4. Click Register App

Step 3: Download Config

Download google-services.json โ†’ place in app/ directory:

cp ~/Downloads/google-services.json app/

Step 4: Add Plugin

Update build.gradle.kts (root):

plugins {
    // ...existing plugins...
    id("com.google.gms.google-services") version "4.4.2" apply false
}

Update app/build.gradle.kts:

plugins {
    // ...existing plugins...
    id("com.google.gms.google-services")
}

Step 5: Re-sync Gradle

File โ†’ Sync Project with Gradle Files

Running Without Firebase

To build without push notifications (e.g., for pure UI development), comment out the Firebase dependencies in app/build.gradle.kts:

// implementation(platform("com.google.firebase:firebase-bom:33.7.0"))
// implementation("com.google.firebase:firebase-messaging-ktx")

The app compiles and runs โ€” push features are non-functional.

Backend Configuration

Once FCM is configured, set these environment variables in the alerts subgraph:

FCM_PROJECT_ID=your-firebase-project-id
FCM_CLIENT_EMAIL=firebase-adminsdk-xxx@your-project.iam.gserviceaccount.com
FCM_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----"

See Mobile Push Infrastructure for the full backend dispatch architecture.


Create an Emulator

Important: Use ARM64 Images on Apple Silicon

Critical: Select arm64-v8a system images. x86_64 images use Rosetta 2 translation and run 5-10x slower.

Step-by-Step

  1. Tools โ†’ Device Manager โ†’ Create Virtual Device
  2. Select hardware:
Recommended Screen Density
Pixel 8 Pro 6.7" 560 dpi
Pixel 8 6.2" 420 dpi
  1. Select system image: VanillaIceCream (API 35) โ€” ABI must show arm64-v8a โœ…
  2. Configure:
Setting Value
RAM 4096 MB
VM Heap 512 MB
Internal Storage 4096 MB
  1. Click Finish โ†’ โ–ถ to launch

With 64 GB RAM, you can run 2-3 emulators simultaneously. Each instance uses ~2-3 GB.


Run the App

On Emulator

  1. Select emulator from device dropdown
  2. Press โ–ถ Run (โŒƒR) or ./gradlew :app:installDebug

On Physical Device

  1. Enable Developer Options: Settings โ†’ About phone โ†’ tap Build number 7 times
  2. Enable USB Debugging in Developer Options
  3. Connect via USB-C โ†’ Accept debugging prompt โ†’ select device โ†’ โ–ถ Run

Wireless Debugging

adb tcpip 5555
adb connect 192.168.1.XXX:5555
# Disconnect USB โ€” you're wireless

Verify

You should see:

  • โœ… Material3 bottom navigation with 6 tabs
  • โœ… Notification permission dialog (Android 13+)
  • โœ… Logcat: [NgwenyaApp] Notification channel created: ngwenya_default

Local Backend Development

Emulator โ†’ Host Machine

The Android Emulator maps 10.0.2.2 to your Mac's localhost:

// In NgwenyaApp.kt or a config file:
const val API_URL = "http://10.0.2.2:30000/graphql"  // Gateway
const val WEB_URL = "http://10.0.2.2:5173"           // Vite dev server

Physical Device โ†’ Mac

Both must be on the same Wi-Fi. Use your Mac's IP:

const val API_URL = "http://192.168.1.XXX:30000/graphql"
const val WEB_URL = "http://192.168.1.XXX:5173"

Cleartext Traffic (HTTP)

For local development with HTTP, create app/src/main/res/xml/network_security_config.xml:

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <domain-config cleartextTrafficPermitted="true">
        <domain includeSubdomains="false">10.0.2.2</domain>
        <domain includeSubdomains="false">192.168.1.0</domain>
    </domain-config>
</network-security-config>

Reference in AndroidManifest.xml:

<application android:networkSecurityConfig="@xml/network_security_config" ... >

WebView Bridge Testing

Enable WebView Debugging

Already enabled in debug builds via:

WebView.setWebContentsDebuggingEnabled(BuildConfig.DEBUG)

Chrome DevTools

  1. Open Chrome on Mac: chrome://inspect/#devices
  2. Your emulator/device WebView appears under Remote Target
  3. Click inspect to open DevTools

Test Injected Globals

In Chrome DevTools console:

window.__NGWENYA_TOKEN__    // โ†’ JWT string
window.__NGWENYA_PLATFORM__ // โ†’ "android"

Test Bridge Interface

NgwenyaBridge.navigate(JSON.stringify({ route: "/cart" }));
// Check Logcat for: [WebViewBridge] Navigate: /cart

The frontend detects this context via mobileUtils.ts โ€” isMobileWebView(), getMobilePlatform(), and sendBridgeMessage().


Build Variants

Variant applicationId Debuggable Minified
debug com.mallnline.ngwenya.debug โœ… โŒ
release com.mallnline.ngwenya โŒ โœ… (R8)
# Debug APK
./gradlew :app:assembleDebug
# โ†’ app/build/outputs/apk/debug/app-debug.apk

# Release AAB (Play Store)
./gradlew :app:bundleRelease

Useful Commands

# Clean + rebuild
./gradlew clean assembleDebug

# Run unit tests
./gradlew test

# Run instrumented tests (requires emulator)
./gradlew connectedAndroidTest

# Lint report
./gradlew lint
# โ†’ app/build/reports/lint-results-debug.html

# View logs (filtered to our app)
adb logcat --pid=$(adb shell pidof com.mallnline.ngwenya.debug)

# Direct install
adb install app/build/outputs/apk/debug/app-debug.apk

Troubleshooting

Problem Solution
Gradle sync: "Could not resolve gradle" Invalidate caches (File โ†’ Invalidate Caches), delete ~/.gradle/caches/, re-sync
"Failed to find target android-35" Settings โ†’ Android SDK โ†’ check "Android 15.0" โ†’ Apply
Emulator extremely slow Delete AVD โ†’ recreate with arm64-v8a image (not x86_64)
google-services.json not found Download from Firebase Console โ†’ place in app/ directory, or comment out plugin
FCM token is null Use emulator with "Google APIs" system image
net::ERR_CLEARTEXT_NOT_PERMITTED Add network_security_config.xml (see Local Backend section)
"Unsupported class file major version 65" Settings โ†’ Build โ†’ Gradle โ†’ Gradle JDK โ†’ select 17