The Situation
A financial services team had an iOS-only transaction management app built in Objective-C. The app had been in production for several years, but the codebase had grown brittle — no tests, no CI/CD pipeline, and a deployment process that required a developer to be physically at a Mac.
The immediate problem: their user base had shifted, and nearly half of new sign-ups were on Android. Every Android user was getting a web fallback that converted at a fraction of the native rate.
The request was to add Android support without doubling the maintenance burden.
The Audit
Before a line of new code was written, a codebase audit mapped the existing app against three dimensions: architecture, dependencies, and deployment.
The audit surfaced three blockers:
- Authentication — The existing app used a custom session token scheme with no biometric support. The new app needed Face ID and fingerprint authentication on both platforms.
- Offline state — The Objective-C app assumed a persistent connection. Transactions fetched from the API were never cached locally. On a weak connection, the app was unusable.
- Manual releases — Every App Store submission was a manual process. There was no staging environment and no automated build pipeline. A botched release meant an emergency hotfix cycle.
The audit report defined the architecture for the React Native build before the project kicked off.
The Architecture
Framework: React Native with the New Architecture (Fabric + JSI) enabled from day one. Migrating to the new architecture mid-project is painful — starting with it is not.
State management: Redux Toolkit with RTK Query for server-side data fetching. RTK Query's caching and invalidation model aligned directly with the transaction list's read-heavy, infrequent-write pattern.
Offline persistence: react-native-mmkv for fast key-value storage and a SQLite layer for queryable transaction history. Transactions written offline are queued and synced when the connection is restored. Conflict resolution uses a last-write-wins strategy keyed by transaction timestamp.
Biometric auth: A custom native module wrapping iOS LocalAuthentication and Android BiometricPrompt. The module surfaces a single authenticate() promise that returns the platform-appropriate UI — no platform-specific code required in the React Native layer.
Navigation: React Navigation v7 with a typed navigation prop pattern. Every screen's params are typed at the navigator level, which eliminates an entire class of runtime navigation errors.
The Build
The 14-week project ran in seven 2-week sprints. A working demo was delivered at the end of every sprint.
Sprint 1–2: Project scaffolding, authentication flow, biometric native module, navigation skeleton.
Sprint 3–4: Transaction list, transaction detail, offline queue and sync engine.
Sprint 5–6: Search, filters, export to PDF, push notification handling.
Sprint 7: End-to-end QA on real devices, App Store and Google Play submission, CI/CD pipeline handoff.
The CI/CD pipeline — GitHub Actions with Fastlane lanes — produces a signed iOS build and a signed Android APK on every merge to main. App Store Connect and Google Play Internal Testing receive the builds automatically. A developer no longer needs to be at a Mac to cut a release.
The Outcome
The app shipped to both platforms within the 14-week timeline. At handoff, the client team received:
- A single TypeScript codebase targeting iOS 15+ and Android 9+
- A Fastlane + GitHub Actions pipeline that deploys to both stores on every tagged release
- A test suite covering auth, offline sync, and transaction CRUD operations
- Architecture documentation and a recorded walkthrough of the codebase
- 30 days of post-launch support
The offline sync engine handled real-world connectivity edge cases from day one — no post-launch patches required for sync failures.
What Made This Work
Three things determined the outcome:
Audit first. Discovering the offline and auth gaps before the project started meant the architecture addressed them directly. Discovering them at week 10 would have required a rebuild.
New Architecture from the start. Enabling Fabric and JSI from the initial scaffold meant no migration penalty. The biometric native module runs over JSI — no bridge serialization, no async queue.
Written scope before every sprint. No feature crept in unannounced. Every scope change came with an impact estimate before anything changed. The client always knew exactly where the project stood.
Build Your App
If you are evaluating a React Native build or migration, the process starts the same way it did here — with a 30-minute scoping call and a written codebase audit.