Mobile Architecture Document
Mobile Architecture Document
Project: Drop — Fintech Payment App Version: 0.1.0 Date: 2026-02-23 Author: John (AI Director, ALAI) Status: In Review Reviewers: Alem Bašić (CEO)
Document History
| Version | Date | Author | Changes |
|---|---|---|---|
| 0.1 | 2026-02-23 | John | Initial draft — from ADR-011, MOBILE-APP.md, source code analysis |
1. Framework & Rationale
| Framework | Pros | Cons | Decision |
|---|---|---|---|
| React Native (Expo SDK 54) | JS/TS codebase reuse with Next.js web app, large ecosystem, OTA updates via EAS, managed workflow | Bridge overhead, larger binary (~25MB), Expo-compatible packages only | Selected |
| Flutter | High performance, consistent UI, strong typing | Dart language (no code sharing with React web app), no native OTA | Rejected |
| Swift (iOS native) | Best iOS performance, latest APIs | iOS only, separate codebase | Rejected |
| Kotlin (Android native) | Best Android performance, Material 3 | Android only, separate codebase | Rejected |
Selected: Expo SDK 54 (managed workflow)
Version: Expo SDK 54, React Native latest, Expo Router v4
ADR Reference: docs/architecture/adr/ADR-011-expo-mobile-framework.md
Rationale:
Drop's web app uses React 19 + Next.js. Expo enables sharing React components, hooks, types, and business logic between web and mobile — reducing duplication and bugs. BankID authentication requires opening a secure browser (expo-web-browser) and handling deep link callbacks (drop://auth/callback), which Expo provides natively. QR scanning is a core Drop feature requiring camera access — expo-camera provides built-in barcode scanning. OTA updates via EAS enable rapid hotfixes for financial apps without App Store review cycles. The AI-driven development team benefits from a single language (TypeScript) across all platforms.
Runtime environment:
- Min iOS: iOS 16
- Min Android: Android 10 (API 29)
- Target SDK: Android 34 / iOS 18
2. Project Structure
src/drop-mobile/
├── App.js # Expo entry (unused — Expo Router takes over)
├── app/
│ ├── _layout.js # Root Stack layout + font loading + splash screen
│ ├── index.js # Welcome screen (green bg, "drop." wordmark)
│ ├── login.js # Login screen (email + password)
│ ├── register.js # Registration (2-step: info, password)
│ ├── history.js # Transaction history (filter tabs, FlatList)
│ └── (tabs)/
│ ├── _layout.js # Tab navigator (4 tabs)
│ ├── index.js # Dashboard / Home (balance card, transactions)
│ ├── send.js # Send money (2-step: recipient + amount)
│ ├── scan.js # QR scanner (camera + payment flow)
│ └── profile.js # Profile & settings (recipients, logout)
├── lib/
│ ├── api.js # API client (Bearer token auth)
│ └── theme.js # Theme constants (colors, fonts, spacing, radius)
└── assets/ # Static assets (images, icons)
Key design decision: Expo Router is file-based routing — the directory structure maps directly to navigation routes.
3. Navigation Architecture
graph TD
Root["Root Stack Navigator\n(app/_layout.js)"] --> Welcome["Welcome Screen\napp/index.js"]
Root --> Login["Login Screen\napp/login.js"]
Root --> Register["Register Screen\napp/register.js"]
Root --> Tabs["Tab Navigator\napp/(tabs)/_layout.js"]
Root --> History["History Screen\napp/history.js (modal)"]
Tabs --> Home["Home Tab\napp/(tabs)/index.js\n(Dashboard)"]
Tabs --> Send["Send Tab\napp/(tabs)/send.js\n(Send Money)"]
Tabs --> Scan["Scan Tab\napp/(tabs)/scan.js\n(QR Scanner)"]
Tabs --> Profile["Profile Tab\napp/(tabs)/profile.js"]
Deep link handling:
- URL scheme:
drop:// - BankID callback:
drop://auth/callback?code=&state= - Library:
expo-linking - Config:
app.config.ts→scheme: "drop"
| Tab | Label | Icon | Screen |
|---|---|---|---|
| index | Hjem | Unicode house emoji | Dashboard |
| send | Send | Unicode arrow emoji | Send Money |
| scan | QR | Unicode QR emoji | QR Scanner |
| profile | Profil | Unicode person emoji | Profile |
Tab bar style: backgroundColor: #FFFFFF, borderTopColor: #E5E7EB, height: 60
Active tint: #0B6E35 (Forest Green), Inactive: #9CA3AF
Note: Mobile has 4 tabs. Web has 5 tabs (adds "Aktivitet/Kontoer"). Mobile shows bank balance on dashboard, not a separate screen.
Auth flow: On login success, token stored → navigate to /(tabs). On logout → clear token → navigate to /.
4. Platform-Specific Considerations
| Concern | iOS | Android | Solution |
|---|---|---|---|
| Back gesture | Swipe from edge | Back button + gesture | Expo Router handles both |
| Status bar | Overlaps content | Separate space | SafeAreaView from expo |
| Permissions model | Request at use time | Request at use time + Manifest | expo-camera permissions |
| Push notifications | APNs | FCM | expo-notifications (unified) |
| Keyboard behavior | Push up content | May or may not push | KeyboardAvoidingView |
| Font rendering | System fonts crisp | Sub-pixel differences | Custom font loading via expo-google-fonts |
| Haptics | UIFeedbackGenerator | Vibrator API | expo-haptics (future) |
| Secure storage | Keychain | Keystore | expo-secure-store |
| BankID browser | In-app secure browser | In-app secure browser | expo-web-browser |
| Camera / QR | AVFoundation | Camera2 API | expo-camera |
5. Build Variants & Flavors
| Variant | Bundle ID | API URL | Debug | Analytics | Push Env |
|---|---|---|---|---|---|
| Dev | no.getdrop.app.dev |
http://localhost:3000/api |
Yes | Off | Development |
| Staging | no.getdrop.app.staging |
https://drop-app-staging.vercel.app/api |
No | Off | Development |
| Production | no.getdrop.app |
https://drop-app.vercel.app/api |
No | Yes | Production |
Environment variable handling: expo-constants via app.config.ts
API URL Detection (current implementation):
// lib/api.js
const API_URL = __DEV__
? "http://localhost:3000/api"
: "https://drop-app.vercel.app/api";
6. Code Sharing Strategy
- TypeScript interfaces (
User,BankAccount,Transaction) - Business logic (age validation, XSS input sanitization)
- API endpoint paths
- Brand tokens (colors, spacing) — same values in
theme.jsandglobals.css
Mobile-specific:
- React Native StyleSheet (vs Tailwind CSS on web)
lib/api.js— Bearer token auth (vs httpOnly cookies on web)lib/theme.js— Native theme constants- Expo-specific modules (
expo-camera,expo-web-browser, etc.) - 4-tab navigation (vs 5-tab on web)
Web-only:
- Next.js App Router
- shadcn/ui components
- Server Components
- httpOnly cookie auth
- Cards feature (feature-flagged on web, not present on mobile)
- Merchant dashboard (on web, not on mobile)
7. Screens Detail
Welcome (app/index.js)
- Full-screen green background (
#0B6E35) - "drop." wordmark in white Fraunces 700 font
- Headline: "Enklere betalinger. Lavere gebyrer."
- Two buttons: "Logg inn" (outline) → /login, "Opprett konto" (solid white) → /register
Login (app/login.js)
- White background
- Email + password fields with React Native
TextInput - "Logg inn" green button →
api.login(email, password) - On success: stores token in AsyncStorage, navigates to
/(tabs) - "Opprett konto" link → /register
- Pre-filled demo credentials in
__DEV__mode
Register (app/register.js)
- 2-step flow with progress dots indicator (vs 4-step on web)
- Step 1: firstName, lastName, email, phone
- Step 2: password, confirmPassword
api.register(data)→ on success navigate to/(tabs)
Dashboard (app/(tabs)/index.js)
- Greeting: "Hei, {firstName}" with hand wave emoji
- Green balance card: "Total saldo" — formatted NOK amount
- Quick stats row: Sendt, Mottatt, Ventende (sent, received, pending counts)
- "Siste transaksjoner" section with
FlatList - Each transaction: direction icon (arrow up=sent, arrow down=received), name, date, amount with color coding
- "Se alle" link → /history
- Pull-to-refresh via
RefreshControl
Send Money (app/(tabs)/send.js)
- 2-step flow (vs 4-step on web — simpler mobile UX)
- Step 1: Recipient name input + currency picker (5 currencies with flags)
- Currencies: BAM (Bosnia), RSD (Serbia), PKR (Pakistan), TRY (Turkey), PLN (Poland)
- Step 2: Amount input (NOK) + conversion card showing exchange rate, 0.5% fee, "Mottaker får" calculated amount
- "Bekreft og send" →
api.sendRemittance(data)→ success screen
QR Scanner (app/(tabs)/scan.js)
- Camera placeholder (gray box with QR icon) — simulated in current version
- "Skann QR-kode" instruction text
- "Simuler skanning" button for demo
- Nearby merchants list (hardcoded: Ahmetov Kebab, Kafe Oslo, Narvesen)
- Payment flow: merchant info → amount input → confirm → success
api.payQR({ merchantId, amount })
Transaction History (app/history.js)
- Filter tabs: Alle, Sendinger (remittance), QR (qr_payment)
FlatListwith transaction items (direction icon, name, date, amount)- Pull-to-refresh
- Filter changes trigger re-fetch via
api.getTransactions({ type })
Profile (app/(tabs)/profile.js)
- User info section: initials avatar, name, email
- "Mine mottakere" section with recipients list (fetched from
api.getRecipients()) - Each recipient: name, country, account number
- Settings menu: Sprak, Varsler, Personvern, Vilkar
- Logout button → clears token, navigates to
/index
8. Native Module Integration
| Module | Purpose | Drop Feature |
|---|---|---|
expo-camera |
Camera access + barcode scanning | QR payment scanning |
expo-web-browser |
Secure in-app browser | BankID OIDC authentication |
expo-notifications |
Push notification handling | Transaction alerts, payment receipts |
expo-linking |
Deep link handling (drop://) |
BankID callback, notification deep links |
@react-native-async-storage/async-storage |
Persistent key-value store | Bearer token storage |
expo-secure-store |
Encrypted storage (Keychain/Keystore) | Sensitive data (future biometric) |
expo-local-authentication |
Biometric auth (Face ID/fingerprint) | App unlock — Phase 2 |
expo-haptics |
Haptic feedback | Payment confirmation — Phase 2 |
expo-google-fonts |
Font loading | Fraunces, DM Sans |
New native module process:
- Check if Expo SDK covers the need
- Check community modules (well-maintained, TypeScript types)
- Custom native module only as last resort
- Any native module must have TypeScript wrapper
9. Performance Optimization Strategy
| Strategy | Implementation | Status |
|---|---|---|
| JS thread optimization | Minimal computation — no heavy processing in current version | Done |
| Image caching | No images in current version — text-based UI | N/A |
| List performance | FlatList with keyExtractor |
Done |
| Bundle size | Hermes engine enabled (Expo default) | Done |
| Font loading | expo-google-fonts — loads async, SplashScreen prevents rendering until ready |
Done |
| Memory management | useEffect cleanup in API calls |
Partial |
| Render optimization | No React.memo yet — simple screens don't need it |
Planned |
Performance targets:
- App cold start to interactive: < 2 seconds
- Screen transition: < 300ms (React Navigation default)
- List scroll: 60fps
- Bundle size: < 25MB (Expo managed workflow)
10. CI/CD for Mobile
flowchart LR
PR["Pull Request"] --> UnitTests["Unit Tests\n(Jest)"]
UnitTests --> Build["Build\n(EAS Build)"]
Build --> Distribute["Distribute\n(TestFlight / Firebase App Distribution)"]
Distribute --> QA["QA Approval"]
QA --> Store["Store Submission\n(EAS Submit)"]
| Stage | Tool | Trigger |
|---|---|---|
| Build | EAS Build | Push to main or release/* |
| OTA hotfix | EAS Update | Any JS-only change to production |
| Test distribution | TestFlight (iOS) / Firebase App Distribution (Android) | Every staging build |
| App Store submit | EAS Submit | Manual trigger (release manager) |
| Code signing | Expo managed credentials | Automated |
EAS advantage: OTA updates allow hotfixes to JavaScript bundle without App Store review. Critical for financial apps.
11. Architecture Diagram
graph TB
subgraph "Mobile App (Expo SDK 54)"
Screens["Screens (7 screens)\nWelcome, Login, Register, Dashboard,\nSend, Scan, Profile, History"]
Nav["Expo Router v4\nStack + Tabs navigation"]
State["Local State (useState)\nNo global state library"]
APIClient["api.js — API Client\nBearer token auth"]
AsyncStorage["AsyncStorage\nToken persistence"]
SecureStore["expo-secure-store\nFuture: sensitive data"]
end
subgraph "Expo Native Modules"
Camera["expo-camera\nQR scanning"]
WebBrowser["expo-web-browser\nBankID auth"]
Notifications["expo-notifications\nPush alerts"]
Linking["expo-linking\ndrop:// deep links"]
Biometrics["expo-local-authentication\nPhase 2 — app unlock"]
end
subgraph "Backend"
HonoAPI["Hono v4 REST API\n/v1/* Bearer token auth"]
BankID["BankID OIDC\nAuthentication"]
OpenBanking["Open Banking (PSD2)\nAISP + PISP"]
end
Screens --> Nav
Screens --> State
State --> APIClient
APIClient --> AsyncStorage
APIClient --> HonoAPI
Screens --> Camera
Screens --> WebBrowser
WebBrowser --> BankID
BankID --> Linking
HonoAPI --> OpenBanking
Notifications --> Screens
Approval
| Role | Name | Date | Signature |
|---|---|---|---|
| Author | John (AI Director) | 2026-02-23 | |
| Mobile Lead | |||
| Tech Lead | |||
| Product Owner |
No comments to display
No comments to display