Skip to main content

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 | Approved Reviewers: {{REVIEWERS}}Alem Bašić (CEO)

Document History

Version Date Author Changes
0.1 {{DATE}}2026-02-23 {{AUTHOR}}John Initial draft — from ADR-011, MOBILE-APP.md, source code analysis

1. Framework & Rationale

Framework Pros Cons Decision
React Native (Expo)Expo SDK 54) JSJS/TS codebase reuse,reuse with Next.js web app, large ecosystem, OTA updates via EAS, managed workflow Bridge overhead, somelarger nativebinary gaps(~25MB), Expo-compatible packages only {{Selected / Rejected}}
Flutter High performance, consistent UI, strong typing Dart language,language binary(no sizecode sharing with React web app), no native OTA {{Selected / Rejected}}Rejected
Swift (iOS native) Best iOS performance, latest APIs iOS only, Swift/ObjCseparate requiredcodebase {{Selected / Rejected}}Rejected
Kotlin (Android native) Best Android performance, Material 3 Android onlyonly, separate codebase {{Selected / Rejected}}Rejected

Selected: {{FRAMEWORK}}Expo SDK 54 (managed workflow) Version: Expo SDK 54, React Native latest, Expo Router v4 ADR Reference: {{VERSION}}docs/architecture/adr/ADR-011-expo-mobile-framework.md

Rationale:

Drop's

TODO:web 3-5app sentencesuses explainingReact the19 final+ 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-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 skillsbenefits andfrom projecta requirements.single language (TypeScript) across all platforms.

Runtime environment:

  • Min iOS: {{iOS 16}}16
  • Min Android: {{Android 10 (API 29)}}
  • Target SDK: {{Android 34 |/ iOS 17}}18

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"]

Navigation library: {{React Navigation v7 | Expo Router v4}}v4 (file-based, built on React Navigation)

  • URL scheme: {{appname:drop://}}
  • UniversalBankID links:callback: {{https:drop://app.domain.com}}auth/callback?code=&state=
  • SeeLibrary: push-notification-design.mdexpo-linking
  • Config: app.config.ts for notificationscheme: deep links"drop"

Tab Navigator Configuration (4 tabs):

TabLabelIconScreen
indexHjemUnicode house emojiDashboard
sendSendUnicode arrow emojiSend Money
scanQRUnicode QR emojiQR Scanner
profileProfilUnicode person emojiProfile

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 hardwareBackPressExpo handlerRouter handles both
Status bar Overlaps content Separate space SafeAreaView +from StatusBarexpo
Permissions model Request at use time Request at use time + Manifest react-native-expo-camera permissions
Push notifications APNs FCM Abstractionexpo-notifications layer(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 browserIn-app secure browserIn-app secure browserexpo-web-browser
Camera / QRAVFoundationCamera2 APIexpo-camera

TODO: Add platform differences discovered during development.


5. Build Variants & Flavors

Variant Bundle ID API URL Debug Analytics Push Env
Dev {{com.company.no.getdrop.app.dev}}dev http://localhost:40003000/api Yes Off Development
Staging {{com.company.no.getdrop.app.staging}}staging https://api-drop-app-staging.domain.comvercel.app/api No Off Development
Production {{com.company.app}}no.getdrop.app https://api.domain.comdrop-app.vercel.app/api No Yes Production

Environment variable handling: {{expo-constants |via react-native-config}}app.config.ts

TODO:API URL Detection (current implementation):

Link
// 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

Shared

with web app (shared/src/drop-app/):

#
    80%
  • TypeScript ofinterfaces codebase(User, BankAccount, Transaction)
  • Business logic (age validation, XSS input sanitization)
  • API endpoint paths
  • Brand tokens (colors, spacing)platform-agnosticsame logicvalues ├──in hooks/theme.js #and Customglobals.css
  • hooks ├── services/ # API, storage abstractions ├── stores/ # State management └── utils/ # Pure utilities platform/ ├── ios/ # iOS-specific implementations └── android/ # Android-specific implementations components/ ├── Button.tsx # Shared component ├── Button.ios.tsx # iOS-specific override (if needed) └── Button.android.tsx # Android-specific override (if needed)

PlatformMobile-specific:

file
    resolution:
  • React BundlerNative automaticallyStyleSheet resolves(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.)
  • .android.tsx
  • 4-tab beforenavigation .tsx.(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)
  • FlatList with 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

DroppaymentOIDC dataunlock
Module Purpose Source PlatformFeature
expo-camera QRCamera scanning,access photo+ capturebarcode scanning ExpoQR SDK Bothscanning
expo-locationweb-browser GeolocationSecure in-app browser ExpoBankID SDK Bothauthentication
expo-notifications Push notificationsnotification handling ExpoTransaction SDKalerts, payment receipts
expo-linking BothDeep link handling (drop://)BankID callback, notification deep links
@react-native-async-storage/async-storagePersistent key-value storeBearer token storage
expo-secure-store Encrypted storage (Keychain/KeystoreKeystore) ExpoSensitive SDK Both(future biometric)
expo-biometricslocal-authentication Biometric auth (Face ID / fingerprintID/fingerprint) ExpoApp SDK Both— Phase 2
{{custom-native-module}}expo-haptics Haptic feedbackPayment confirmation — Phase 2
{{PURPOSE}}expo-google-fonts CustomFont loading {{iOS/Android/Both}}Fraunces, DM Sans

New native module process:

  1. EvaluateCheck if Expo SDK covers the need
  2. Check community modules (well-maintained, typed)TypeScript types)
  3. WriteCustom customnative module only as last resort
  4. NativeAny native module must have TypeScript wrapper with full types

8.9. Performance Optimization Strategy

Strategy Implementation Status
JS thread optimization MoveMinimal computation — no heavy computationprocessing toin workletscurrent (Reanimated)version {{Done/Planned}}Done
Image caching expo-imageNo withimages diskin cachecurrent version — text-based UI {{Done/Planned}}N/A
List performance FlashListFlatList instead ofwith FlatListkeyExtractor {{Done/Planned}}Done
Bundle size Hermes engine enabled,enabled tree(Expo shakingdefault) {{Done/Planned}}Done
JS startup timeLazyFont loading of non-critical screens {{Done/Planned}}expo-google-fonts — loads async, SplashScreen prevents rendering until readyDone
Memory management SubscriptionuseEffect cleanup in useEffectAPI calls {{Done/Planned}}Partial
Render optimization No React.memo, useCallbackyet on listsimple itemsscreens don't need it {{Done/Planned}}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 <managed 15MB (Android)workflow)

9. Crash Reporting & Analytics Integration

ServicePurposeLibrary
{{Sentry}}Crash reporting, error tracking@sentry/react-native
{{Firebase Analytics}}User analytics, funnel tracking@react-native-firebase/analytics
{{Datadog}}APM, performance monitoring@datadog/mobile-react-native

Privacy rules:

  • No PII in analytics events
  • User ID: hashed, not raw
  • Comply with GDPR — analytics requires consent
  • Crash 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 {{EAS Build / Fastlane}} Push to main or release/*
OTA hotfixEAS UpdateAny JS-only change to production
Test distribution {{TestFlight (iOS) / Firebase App Distribution}}Distribution (Android) Every staging build
E2E tests{{Detox / Maestro}}PR checks
App Store submit {{EAS Submit / Fastlane deliver}} Manual trigger (release manager)
Code signing {{Expo managed / Fastlane match}}credentials Automated

TODO:EAS advantage: LinkOTA updates allow hotfixes to CIJavaScript configurationbundle fileswithout inApp .github/workflows/Store 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