Modernizing Checkout at Scale: From a 2,700-Line Monolith to Modular SwiftUI
"How we dismantled a fragile 2,700-line UIKit monolith, eliminated delivery regressions, and built a modular SwiftUI checkout with >90% test coverage."
At Erafone (Erajaya Digital), checkout is the pulse of Indonesia's largest tech retailer. Our mobile checkout engine coordinates hundreds of thousands of electronic gadget purchases across four complex fulfillment models:
During high-traffic flagship gadget pre-orders (such as annual iPhone launches), thousands of customers concurrently submit orders in seconds. Because checkout is our primary revenue funnel, any bug is an urgent P0 production incident.
In our legacy architecture, resolving these urgent issues was agonizingly slow: triage meant navigating a 2,700-line monolithic controller with 7 tangled domains, manual simulator verification took 15 minutes per fix due to 0% automated tests, and tight coupling made every hotfix a merge-conflict hazard.
When designing One Page Checkout (OPC), our Mobile Engineering Team intentionally engineered the architecture around incident response velocity and safety: isolated child ViewModels for instant root-cause triage, >90% test coverage for sub-3-second automated verification, and zero side-effect blast radius.
Visual Telemetry: Legacy Blocking Modal vs. Fluid Granular Shimmer
Store calculation in action: Legacy locks the entire UI with a modal spinner, while Modern OPC maintains smooth scrolling and localized skeleton shimmers.


Architectural Matrix: Legacy Monolith vs. Modern OPC
Comparison across 13 core dimensions—highlighting structural failure points in Legacy and their modern architectural resolutions:
| Evaluation Dimension | Legacy UIKit Monolith | Modern SwiftUI OPC & Engineering Impact |
|---|---|---|
| Initial Screen Latency (Network I/O) | Waterfall Serial Request (Address → Consent → Carts sequential ~1,000ms) | Parallel Fan-out Concurrency (async let / TaskGroup ≈ 400ms)↳ ⚡ ~600ms Faster (Saves ~60% screen initial load wait time) |
| Waiting Experience (User Interactivity) | Full-Screen Activity Spinner (showLoading() total screen lock) | 33 Skeleton / Shimmer Bindings + Loadable<T> Container↳ 📱 Zero UI Freeze (Users can read summary & scroll smoothly at 60 FPS) |
| Flicker & Layout Jump on Reload | Brief blank white flash then full table reload (layout jump) | Flicker-Free Reload (Stale data preserved during background fetch)↳ 🎯 Seamless visual continuity with zero scroll jump or white flicker |
| Regression Safety Net (Unit Testing) | 84 test cases (1,821 test lines, limited string helpers) | 209 test cases (10,082 test lines, covering 3 E2E 16-step journeys)↳ 🛡️ +148% Test Cases (+453% Test Code Volume, Zero-Regression CI) |
| Blast Radius & Max File Size | 1,161 LoC (CheckoutViewController+Helper.swift) | 366 LoC (PersonalDataSection.swift, Root View only 100 LoC)↳ 📉 -68.5% Reduction in Largest File Size (SRP Compliant) |
| Granular UI Micro-Decomposition | 1 Monolithic Cell (OrderListViewCell 815 LoC) mixing stepper & courier | 6 Sections decomposed into 18+ Micro Subviews (<80 LoC)↳ Sections operate purely as micro-assemblers; isolated re-render per child view |
| Domain Separation & Architectural Coupling | 1 Monolith Controller tangling 7 distinct business domains | 1 Coordinator + 5 Dedicated Child ViewModels↳ 🧩 100% Decoupled (SRP); courier adjustments never impact vouchers |
| Dependency Injection | Hardcoded creation / Static shared singletons hard to mock | 28 Factory Container Bindings in CheckoutContainer↳ 🧪 100% Mockable for Unit Testing, Previews, & UI Factories |
| Single Source of Truth (SSOT) | Fragmented ghost states across VC, VM, and Cells | GET Quote Server Response as Canonical SSOT↳ Zero cross-flow regressions between delivery and pickup methods |
| Runtime Modal & Error Safety | Raw present() crash during dismiss, silent dropped alerts | presentPopupSafely + transitionCoordinator auto-wait↳ 100% dialog delivery, zero window hierarchy collision crashes |
| Multi-Channel Error Presentation | Combine Type-Erasure (AnyPublisher<T, Error>); permanent stream death on .failure | Task Granular do-catch routing across 4 Channels (Inline, Toast, Modal, Silent)↳ 🎯 Zero Ambiguity: Input error doesn't kill UI; complete stack trace in debugger |
6 Core Architectural Decisions That Rescued Engineering Velocity
How we decoupled state mutations, standardized domain primitives, and established complete test isolation across parallel feature teams:
Atomic Design UI System
Established a strict 4-tier component hierarchy (Atoms, Molecules, Organisms, Pages). Dismantled 815-line nested cell hierarchies and enforced standardized CommonUI Design Tokens.
Domain Slices & Function Decomposition
Refactored a 1,161-line monolithic helper into 8 isolated domain extensions (Eraxpress, CNP, Store Pickup), enabling simultaneous sprint work with zero Git merge conflicts.
Factory Pattern & Coordinator Hub
Replaced rigid singleton instantiations and God ViewModels with Factory DI (@InjectedObject) and isolated section assembly via OPCSectionViewFactory.
Modern Swift Concurrency (async/await)
Eliminated nested Combine .sink pipelines and manual DispatchQueue.main hops in favor of structured concurrency Tasks, async let, and compile-time @MainActor isolation.
Swift Testing & >90% Xcode Coverage
Transformed 0% legacy test coverage into 25+ deterministic test suites utilizing Apple's modern Swift Testing framework (@Suite, @Test), executing in under 3 seconds.
Granular Per-UI Element Shimmer
Abolished full-screen blocking modals. Shimmer animations are surgically isolated at atomic product rows and rates while courier dropdowns and store cards remain interactive.
Micro-Decomposition: Why Slicing into Sections Was Only Step One
Splitting a 1,161-line controller into 6 section files is futile if each section degenerates into an 800-line mini-monolith. In One Page Checkout, every Section is strictly engineered as a lightweight micro-assembler (<150 LoC) that delegates layout and behavior to 4–5 isolated atomic subviews:
Rather than nesting TableViews within cells, the section acts purely as an immutable stack orchestrator composing:
Completely isolates geographical and courier state from cart totals, orchestrating dedicated atomic units:
Because each sub-element is an isolated SwiftUI view conforming to narrow immutable props, tapping the quantity stepper only invalidates QuantityStepperView and PriceBreakdownItem. The address, courier, and voucher trees are completely skipped by the SwiftUI runtime, delivering instantaneous 0.001s mutation latency and sustaining continuous 60 FPS interaction.
Multi-Channel Error Presentation: Why Task Trumps Combine in Transaction Funnels
In mission-critical checkout flows, errors are never one-size-fits-all. Misclassifying an error degrades customer trust—triggering an intrusive modal popup for a simple phone number typo, or silently swallowing a payment gateway failure. The architecture enforces 4 distinct UI Presentation Channels:
NIK validation, missing phone digits, or incomplete address. Renders directly underneath the targeted input box without disturbing surrounding sections.
Promo voucher expiration, slight inventory threshold change, or transient network hiccup. Floating banner informs user without disrupting scroll.
Payment gateway rejection, fraud flag, or store inventory timeout. Demands explicit user action (e.g., Switch Payment Method or Retry Order).
Optional profile consent fetch failure or analytics ping drop. Safely logged to telemetry with automatic fallback—zero interruption to user purchase.
By replacing Combine’s type-erased AnyPublisher<T, Error> with Swift Concurrency Task and structured do-catch blocks, each domain failure is exhaustively pattern-matched at compile time without risking stream termination or ambiguous UI alerts.
Architectural Pseudocode Walkthrough: 4 Core Modular Patterns
To protect proprietary internal data schemas and private API signatures, the following implementations are presented in educational architectural pseudocode, demonstrating design topologies with 100% architectural fidelity.
Pure declarative assembler (<100 LoC) composing atomic subviews. Touching quantity steppers only re-renders the targeted leaf node, eliminating parent layout storms.
Architecture Decision Records: Trade-Offs & Alternatives Considered
Why we picked specific architectural patterns over established industry alternatives:
Checkout requires dynamic conditional branching across 4 distinct fulfillment options, promo vouchers, and payment channels without cross-domain regression hazards.
VIPER: Rejected due to excessive boilerplate (5+ files per sub-flow).
The Composable Architecture (TCA): Rejected due to compile-time overhead in large teams and steep learning curve for sprint velocity.
A single Coordinator ViewModel with dedicated domain slice extensions and Factory DI. Result: sub-3s Xcode build times, zero state leaks, and isolated feature ownership.
Subviews need to signal actions (address change, coupon apply, quantity step) back to domain services without retaining view controllers or parent hierarchies.
UIKit Delegate Protocol: Required massive 20-method protocols with weak references, prone to memory leaks and boilerplate stubs in testing.
Immutable value-type CheckoutActionHandlers struct containing first-class Swift closures. Easily mockable in unit tests with zero memory overhead.
Field Notes: 3 Real-World Gotchas We Encountered in Production
Every major architectural migration encounters non-obvious traps. Here are three critical gotchas we overcame during our rollout:
SwiftUI Invalidation Cascades
Initially, mutating a coupon string on the root coordinator caused SwiftUI to re-evaluate the entire body of all 6 sections. We resolved this by enforcing immutable struct props and placing .equatable() modifiers at section boundaries.
Task Re-Entrancy & Auto-Cancel
Tapping payment buttons quickly could spawn overlapping unstructured Task instances. We solved this with structured concurrency: tying network lifecycles to view state via .task(id:) and enforcing compile-time @MainActor serialization.
Cumulative Layout Shifts (CLS)
Replacing entire sections with generic placeholder cards caused jarring layout jumps when API quotes returned. We introduced the Dual-Slot Loadable<T> model: previous data remains on screen while only dynamic pricing numbers shimmer.
Where AI Was Applied in the Checkout Overhaul
Throughout the refactoring process, AI tooling was used to assist the engineering team across four practical areas:
Automated Code Review
Assisted in auditing branch diffs for memory retain cycles ([weak self]), thread boundary safety, and Swift Concurrency rules prior to pull request merge.
Unit Test Generation
Assisted in generating 209 parameterized test cases (@Suite, @Test) covering complex checkout cart combinations and edge-case permutations.
Legacy Business Logic Parity
Assisted in cross-checking 1,976 lines of legacy calculations against OPC to ensure identical business rules, discount evaluations, and multi-courier flows without behavioral drift.
Rapid UI & Shimmer Scaffolding
Assisted in constructing 3-tier SwiftUI view components and setting up 33 non-blocking skeleton shimmer bindings propagated via @Environment(\.skeletonActive).
Key Engineering Takeaways
Respect the Canonical SSOT
Fragmented UI states across cells and view controllers are the primary cause of phantom checkout bugs. Treating the server quote response as the sole source of truth guarantees transactional consistency.
Decompose Proactively
Views should remain under 150 lines, functioning purely as declarative assemblers. Domain logic belongs in isolated, pure Swift mappers that can be verified in milliseconds without instantiating UI components.
Granular Shimmers Over Modals
Mobile users perceive performance through interaction continuity. Retaining structural layouts with surgical, atomic shimmers keeps the application responsive and eliminates UI freeze.
Multi-Channel Error Granularity
Checkout errors require precise routing across 4 tiers (Inline, Toast, Modal, Silent). Structured Swift Concurrency (Task) prevents validation errors from terminating reactive UI subscriptions.
Explore the Interactive Live Render Simulator & Code Diff
The internal presentation suite includes an interactive Live Render Simulator, Visual 3-Tier Canvas Flow Graph, and Production Swift Code Diffs.