iOS Platform Architecture Case StudyOctober 20269 min readProduction Verified

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

AP
AS PamungkasAuthorGitHubLinkedIn
iOS Developer at Erajaya • Contributor to DebugSwift
With contributions from Erajaya Mobile Team
Swift 6 ReadySwiftUI 5.0+

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:

🏬 1. Store Pickup (BOPIS)Click & Collect with dynamic real-time inventory and operating hours across 1,000+ retail stores nationwide.
⚡ 2. Instant Courier (Eraxpress)Hyper-local 3-hour instant dispatch routed by GPS proximity from the nearest participating store outlet.
📦 3. Standard LogisticsMulti-carrier 3PL parcel matrix with volumetric tariff calculations and insurance coverage options.
🔄 4. Trade-In (Tukar Tambah)Dynamic IMEI hardware diagnostic valuation integrated directly into the cart breakdown for instant price offsets.

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.

Executive Telemetry • 4 Core Enterprise Mobile Bottlenecks
~600ms
Network I/O Latency Cut
1,000ms waterfall ➔ 400ms fan-out (-60%)
100%
Zero UI Freeze (60 FPS)
33 skeleton bindings + Loadable<T>
+148%
Zero-Regression Safety
84 ➔ 209 tests (10,082 LoC test suite)
-68.5%
Max File Blast Radius
1,161 LoC ➔ 366 LoC (Root view 100 LoC)
Visual Telemetry • iOS Simulator Capture

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.

Legacy Monolith (UIKit)
Blocking Modal
Legacy Checkout Screen Recording
⚠️ Hard UI Freezing:Every store or courier mutation triggers a fullscreen gray modal spinner that disables touch events, prevents scrolling, and flashes upon completion.
One Page Checkout (SwiftUI)
Granular Shimmer
One Page Checkout Modern SwiftUI Screen Recording
✓ Continuous Interaction:Store & courier dropdowns stay fully interactive. Shimmer pulses gently only on the calculating product items and rate labels with 0 layout jump.
Architectural Matrix

Architectural Matrix: Legacy Monolith vs. Modern OPC

Comparison across 13 core dimensions—highlighting structural failure points in Legacy and their modern architectural resolutions:

Evaluation DimensionLegacy UIKit MonolithModern 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
Architectural Pillars

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:

Pillar 01

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.

Pillar 02

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.

Pillar 03

Factory Pattern & Coordinator Hub

Replaced rigid singleton instantiations and God ViewModels with Factory DI (@InjectedObject) and isolated section assembly via OPCSectionViewFactory.

Pillar 04

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.

Pillar 05

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.

Pillar 06

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.

Granular Component Topology

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:

OrderListSection.swift (84 LoC Assembler)Organism

Rather than nesting TableViews within cells, the section acts purely as an immutable stack orchestrator composing:

OrderStoreHeaderRow
24 LoC
OrderItemRow
32 LoC
BundlingBadgeRow
20 LoC
PriceBreakdownItem
18 LoC
QuantityStepperView
28 LoC
ShippingAddressSection.swift (124 LoC Assembler)Organism

Completely isolates geographical and courier state from cart totals, orchestrating dedicated atomic units:

AddressCardView
42 LoC
EmptyAddressCardView
25 LoC
CourierSelectorDropdown
38 LoC
StoreLocationPickerRow
34 LoC
ShippingRateLabel
22 LoC
Production Code Footprint: Modular Sections Inventory
Max Blast Radius: 366 LoC (-68.5%)
OrderListSection.swift
Cart Items & Stepper Assembler
84 LoC
OnePageCheckoutView.swift
Root Screen Assembly
100 LoC
ShippingAddressSection.swift
Address & Courier Selector
124 LoC
RewardsSection.swift
Voucher, Points & Promo Sheet
193 LoC
PaymentMethodSection.swift
Gateway & Channel Bindings
194 LoC
OrderSummarySection.swift
Financial Breakdown & Fees
295 LoC
PersonalDataSection.swift
Customer Profile & Consent (Max File)
366 LoC
The Architectural Dividend: Surgical Invalidation & 60 FPS

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.

0.001sMutation Latency per 1k Iterations
Resilient Error Architecture

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:

📝 1. Inline Field ErrorField Target

NIK validation, missing phone digits, or incomplete address. Renders directly underneath the targeted input box without disturbing surrounding sections.

🍞 2. Toast NotificationNon-Blocking Banner

Promo voucher expiration, slight inventory threshold change, or transient network hiccup. Floating banner informs user without disrupting scroll.

🚨 3. Blocking Modal PopupAction Required

Payment gateway rejection, fraud flag, or store inventory timeout. Demands explicit user action (e.g., Switch Payment Method or Retry Order).

🤫 4. Silent Error / TelemetryBackground Log

Optional profile consent fetch failure or analytics ping drop. Safely logged to telemetry with automatic fallback—zero interruption to user purchase.

Architectural Topology: Unidirectional Task Dispatch
Compile-Time Exhaustive Mapping

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.

Sanitized Architecture Pseudocode

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.

UI ArchitectureOrderItemsSection.swift (SwiftUI Micro-Assembler)

Pure declarative assembler (<100 LoC) composing atomic subviews. Touching quantity steppers only re-renders the targeted leaf node, eliminating parent layout storms.

OrderItemsSection.swift (SwiftUI Micro-Assembler)
swift
123456789101112131415161718192021222324252627282930313233343536373839
import SwiftUI
 
// MARK: - Pattern 1: Section View as a Pure Micro-Assembler (<100 LoC)
// Completely decoupled from ViewControllers and global mutable singletons
public struct OrderItemsSection: View {
let state: CartSectionState
let onQuantityChanged: (String, Int) -> Void
let onPromotionTapped: (String) -> Void
 
public var body: some View {
VStack(alignment: .leading, spacing: 16) {
// 1. Isolated Fulfillment Badge Molecule
FulfillmentHeaderRow(badge: state.fulfillmentBadge)
 
// 2. Composable Product Rows with Granular Mutation Boundaries
ForEach(state.items) { item in
VStack(spacing: 8) {
ProductItemSummaryRow(item: item)
 
if let promo = item.promotionHighlight {
PromotionTagBadge(promo: promo) { onPromotionTapped(item.id) }
}
 
HStack {
UnitPriceBreakdownView(price: item.price, subtotal: item.subtotal)
Spacer()
// Surgical Mutation: Touching stepper only invalidates this subview
QuantityStepperView(quantity: item.quantity) { newQty in
onQuantityChanged(item.id, newQty)
}
}
}
}
}
.padding(16)
.background(Color(.secondarySystemBackground))
.cornerRadius(16)
}
}
Verified modular pattern conforms to Swift 6 strict concurrency isolation guidelines.
Architecture Decision Records (ADR)

Architecture Decision Records: Trade-Offs & Alternatives Considered

Why we picked specific architectural patterns over established industry alternatives:

ADR-01: Coordinator Hub + Domain Slices vs. VIPER / TCAAccepted • Production Standard
Context & Need:

Checkout requires dynamic conditional branching across 4 distinct fulfillment options, promo vouchers, and payment channels without cross-domain regression hazards.

Alternatives Evaluated:

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.

Final Decision & Dividend:

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.

ADR-02: Pure Value-Type Actions Struct vs. 20+ Method Delegate ProtocolsAccepted • Zero Retain Cycle
Context & Need:

Subviews need to signal actions (address change, coupon apply, quantity step) back to domain services without retaining view controllers or parent hierarchies.

Alternatives Evaluated:

UIKit Delegate Protocol: Required massive 20-method protocols with weak references, prone to memory leaks and boilerplate stubs in testing.

Final Decision & Dividend:

Immutable value-type CheckoutActionHandlers struct containing first-class Swift closures. Easily mockable in unit tests with zero memory overhead.

Engineering Field Notes

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:

Gotcha 01

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.

Fix: Equatable Subviews
Gotcha 02

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.

Fix: Task Cancellation
Gotcha 03

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.

Fix: Dual-Slot Loadable
AI-Assisted Engineering

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).

Architectural Principles

Key Engineering Takeaways

Principle 01

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.

Principle 02

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.

Principle 03

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.

Principle 04

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.

AP

AS Pamungkas

iOS Developer at ErajayaGitHub (@ajinumoto)LinkedIn Profile

AS Pamungkas is an iOS Developer at Erajaya, focusing on mobile architecture evolution, modern SwiftUI paradigms, and high-performance checkout systems across Erajaya's flagship retail mobile commerce applications. He is also an active open-source contributor developing in-app network debugging capabilities for DebugSwift .

Organization: Erafone / Erajaya Digital•Co-authored with Erajaya Mobile Team
We're Hiring • Mobile Engineering

Build the Future of Retail Mobile Tech with Erafone

Interested in solving high-concurrency retail challenges, designing modular SwiftUI systems, and engineering software that reaches millions of users nationwide? Join our mobile platform team.

Explore Engineering Roles
Internal Engineering Staff Only

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.