Mobile Architecture
Mobile Architecture Document
Project:
{{PROJECT_NAME}}Drop — Fintech Payment App Version:{{VERSION}}0.1.0 Date:{{DATE}}2026-02-23 Author:{{AUTHOR}}John (AI Director, ALAI) Status:Draft |In Review| ApprovedReviewers:{{REVIEWERS}}Alem Bašić (CEO)
Document History
| Version | Date | Author | Changes |
|---|---|---|---|
| 0.1 | Initial draft — from ADR-011, MOBILE-APP.md, source code analysis |
1. Framework & Rationale
| Framework | Pros | Cons | Decision |
|---|---|---|---|
| React Native ( |
Bridge overhead, |
|
|
| Flutter | High performance, consistent UI, strong typing | Dart |
Rejected |
| Swift (iOS native) | Best iOS performance, latest APIs | iOS only, |
Rejected |
| Kotlin (Android native) | Best Android performance, Material 3 | Android |
Rejected |
Selected:
Version: Expo SDK 54, React Native latest, Expo Router v4
ADR Reference: {{FRAMEWORK}}Expo SDK 54 (managed workflow){{VERSION}}docs/architecture/adr/ADR-011-expo-mobile-framework.md
Rationale:
Drop's
TODO:web3-5appsentencesusesexplainingReactthe19final+decisionNext.js.includingExpo 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-cameraprovides built-in barcode scanning. OTA updates via EAS enable rapid hotfixes for financial apps without App Store review cycles. The AI-driven development teamskillsbenefitsandfromprojectarequirements.single language (TypeScript) across all platforms.
Runtime environment:
- Min iOS:
16{{iOS16}} - Min Android:
{{Android 10 (API 29)}} - Target SDK:
18{{Android 34|/ iOS17}}
2. Project Structure
{{PROJECT_NAME}}/src/drop-mobile/
├── src/App.js # Expo entry (unused — Expo Router takes over)
├── app/
│ ├── app/_layout.js # ExpoRoot RouterStack routeslayout (file-based)+ orfont Reactloading Navigation+ configsplash screen
│ ├── screens/index.js # Full-Welcome screen components(green │bg, "drop." wordmark)
│ ├── auth/login.js │# Login screen (email + password)
│ ├── home/register.js # Registration (2-step: info, password)
│ ├── history.js # Transaction history (filter tabs, FlatList)
│ └── settings/(tabs)/
│ ├── components/_layout.js │# Tab navigator (4 tabs)
│ ├── ui/index.js # DesignDashboard system/ primitivesHome (balance card, transactions)
│ ├── send.js # Send money (2-step: recipient + amount)
│ ├── scan.js # QR scanner (camera + payment flow)
│ └── features/profile.js # Feature-specificProfile components& settings (recipients, logout)
├── lib/
│ ├── navigation/ # Navigator definitions, types
│ ├── hooks/ # Shared custom hooks
│ ├── services/api.js # API client,client native(Bearer integrationstoken │ ├── stores/ # State management slices
│ ├── utils/ # Pure helpers
│ ├── constants/ # App-wide constantsauth)
│ └── types/theme.js # TypeScriptTheme interfaces
├── ios/ # iOS native codeconstants (Xcodecolors, project)fonts, ├──spacing, android/radius)
# Android native code (Gradle project)
├└── assets/ # Images,Static fonts,assets icons(images, ├── app.config.ts # Expo config or equivalent
└── package.jsonicons)
TODO:Key design decision: UpdateExpo Router is file-based routing — the directory structure maps directly to matchnavigation actual project structure.routes.
3. Navigation Architecture
graph TD
Root["Root Navigator"Stack Navigator\n(app/_layout.js)"] --> Auth[Welcome["AuthWelcome Stack\n(Unauthenticated)"Screen\napp/index.js"]
Root --> App["App Navigator\n(Authenticated)"]
Auth --> Login["Login Screen"Screen\napp/login.js"]
AuthRoot --> Register["Register Screen"Screen\napp/register.js"]
AuthRoot --> ForgotPassword[Tabs["ForgotTab Password"Navigator\napp/(tabs)/_layout.js"]
AppRoot --> BottomTabs[History["BottomHistory TabScreen\napp/history.js Navigator"(modal)"]
App --> Modal["Modal Stack"]
BottomTabsTabs --> Home["Home Tab\napp/(tabs)/index.js\n(Stack)Dashboard)"]
BottomTabsTabs --> Explore[Send["ExploreSend Tab\napp/(tabs)/send.js\n(Stack)Send Money)"]
BottomTabsTabs --> Scan["Scan Tab\napp/(tabs)/scan.js\n(QR Scanner)"]
Tabs --> Profile["Profile Tab\n(Stack)"]
BottomTabs --> Settings["Settings Tab\n(Stack)"]
Home --> HomeScreen["Home Screen"]
Home --> DetailScreen["Detail Screen"]
Modal --> ImageViewer["Image Viewer"]
Modal --> ShareSheet["Share Sheet"napp/(tabs)/profile.js"]
Deep link handling:
- URL scheme:
{{appname:drop://}} UniversalBankIDlinks:callback:{{https:drop://app.domain.com}}auth/callback?code=&state=SeeLibrary:push-notification-design.mdexpo-linking- Config:
app.config.tsfor→notificationscheme:deep links"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: RootOn navigatorlogin listenssuccess, token stored → navigate to auth/(tabs). stateOn —logout no→ manualclear navigationtoken required→ onnavigate login/logout.to /.
4. Platform-Specific Considerations
| Concern | iOS | Android | Solution |
|---|---|---|---|
| Back gesture | Swipe from edge | Back button + gesture | Expo |
| Status bar | Overlaps content | Separate space | SafeAreaView expo |
| Permissions model | Request at use time | Request at use time + Manifest | permissions |
| Push notifications | APNs | FCM | expo-notifications |
| 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 |
TODO: Add platform differences discovered during development.
5. Build Variants & Flavors
| Variant | Bundle ID | API URL | Debug | Analytics | Push Env |
|---|---|---|---|---|---|
| Dev | |
http://localhost: |
Yes | Off | Development |
| Staging | |
https:// |
No | Off | Development |
| Production | |
https:// |
No | Yes | Production |
Environment variable handling: {{expo-constants|via react-native-config}}app.config.ts
TODO:API URL Detection (current implementation):
// tolib/api.js
.env.exampleconst API_URL = __DEV__
? "http://localhost:3000/api"
: "https://drop-app.vercel.app/api";
for each variant.
6. Code Sharing Strategy
- TypeScript
ofinterfacescodebase(User,BankAccount,Transaction) - Business logic (age validation, XSS input sanitization)
- API endpoint paths
- Brand tokens (colors, spacing) —
platform-agnosticsamelogicvalues├──inhooks/theme.js#andCustomglobals.css
PlatformMobile-specific:
- React
BundlerNativeautomaticallyStyleSheetresolves(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,,.ios.tsxexpo-web-browser/etc.) - 4-tab
beforenavigation.tsx.(vs 5-tab on web)
.android.tsxWeb-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 | ||
|---|---|---|---|
expo-camera |
|||
expo- |
|||
expo-notifications |
Push |
||
expo-linking |
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/ |
||
expo- |
Biometric auth (Face |
||
|
Haptic feedback | Payment confirmation — Phase 2 | |
|
Fraunces, DM Sans |
New native module process:
EvaluateCheck if Expo SDK covers the need- Check community modules (well-maintained,
typed)TypeScript types) WriteCustomcustomnative module only as last resortNativeAny native module must have TypeScript wrapperwith full types
8.9. Performance Optimization Strategy
| Strategy | Implementation | Status | |
|---|---|---|---|
| JS thread optimization | Done |
||
| Image caching | No |
N/A |
|
| List performance | |
Done |
|
| Bundle size | Hermes engine |
Done |
|
— loads async, SplashScreen prevents rendering until ready |
Done | ||
| Memory management | useEffect cleanup in API calls |
Partial |
|
| Render optimization | No React.memoyet |
Planned |
Performance targets:
- App cold start to interactive: < 2 seconds
- Screen transition: < 300ms (React Navigation default)
- List scroll: 60fps
(120fps on ProMotion) - Bundle size: <
20MB25MB (iOS),Expo<managed15MB (Android)workflow)
9. Crash Reporting & Analytics Integration
| | |
| | |
| |
Privacy rules:
No PII in analytics eventsUser ID: hashed, not rawComply with GDPR — analytics requires consentCrash reports: scrub sensitive data from breadcrumbs
Event naming convention: {screen}_{action} — e.g., checkout_payment_started
10. CI/CD for Mobile
flowchart LR
PR["Pull Request"] --> UnitTests["Unit Tests\n(Jest)"]
UnitTests --> E2ETests["E2E Tests\n(Detox / Maestro)"]
E2ETests --> Build["Build\n(EAS Build / Fastlane)Build)"]
Build --> Distribute["Distribute\n(TestFlight / Firebase App Distrib)Distribution)"]
Distribute --> QA["QA Approval"]
QA --> Store["Store Submission\n(EAS Submit / Fastlane deliver)Submit)"]
| Stage | Tool | Trigger |
|---|---|---|
| Build | |
Push to main or release/* |
| OTA hotfix | EAS Update | Any JS-only change to production |
| Test distribution | Distribution (Android) |
Every staging build |
| ||
| App Store submit | |
Manual trigger (release manager) |
| Code signing | credentials |
Automated |
TODO:EAS advantage: LinkOTA updates allow hotfixes to CIJavaScript configurationbundle fileswithout inApp Store .github/workflows/orreview. Fastfile.Critical for financial apps.
11. Architecture Diagram
graph TB
subgraph "Mobile App"App UI[(Expo SDK 54)"
Screens["Screens &(7 Components"screens)\nWelcome, Login, Register, Dashboard,\nSend, Scan, Profile, History"]
Nav["NavigationExpo Layer"Router v4\nStack + Tabs navigation"]
State["Local State Management"(useState)\nNo global state library"]
Services[APIClient["Serviceapi.js Layer"— API Client\nBearer token auth"]
Native[AsyncStorage["NativeAsyncStorage\nToken Modules"persistence"]
SecureStore["expo-secure-store\nFuture: sensitive data"]
end
subgraph "External"Expo API[Native Modules"
Camera["RESTexpo-camera\nQR API"scanning"]
Auth[WebBrowser["Authexpo-web-browser\nBankID Service"auth"]
Push[Notifications["Pushexpo-notifications\nPush Service\n(APNsalerts"]
Linking["expo-linking\ndrop:// FCM)"deep links"]
Analytics[Biometrics["Analytics\n(Firebaseexpo-local-authentication\nPhase /2 Sentry)"— app unlock"]
end
UIsubgraph "Backend"
HonoAPI["Hono v4 REST API\n/v1/* Bearer token auth"]
BankID["BankID OIDC\nAuthentication"]
OpenBanking["Open Banking (PSD2)\nAISP + PISP"]
end
Screens --> Nav
UIScreens --> State
State --> ServicesAPIClient
ServicesAPIClient --> APIAsyncStorage
ServicesAPIClient --> AuthHonoAPI
NativeScreens --> PushCamera
ServicesScreens --> AnalyticsWebBrowser
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 |