How Alipay Rebuilt Its ‘My Tab’ Using KMP for Seamless Three‑Platform Code
Alipay’s terminal team refactored the high‑traffic ‘My Tab’ feature into a single Kotlin Multiplatform codebase, tackling challenges like MVC‑to‑MVVM migration, frame‑rate drops, and cross‑platform stability, and sharing detailed design patterns, performance optimizations, and solutions for Android, iOS, and HarmonyOS.
Background
HarmonyOS introduced a three‑platform mobile ecosystem. To support a unified codebase, Alipay’s terminal team selected Kotlin Multiplatform (KMP) and targeted the high‑traffic "My Tab" (MYTab) for the first migration.
Refactor Overview
The KMP framework uses Jetpack Compose as the UI model. Compose follows a state‑driven, strong MVVM paradigm, which is familiar to developers of Vue, Flutter, or SwiftUI. The main difficulty was converting the existing MVC business logic—accumulated over many product versions—into the new state‑driven MVVM structure.
Design‑Pattern Refactor
MYTab originally used classic MVC, but the boundaries between Model, View, and Controller became blurred, harming maintainability. Switching to Compose provides two key benefits:
Strict separation of data and UI: UI elements can only be rendered from immutable State, preventing accidental use of Model state.
State‑driven UI: The UI is a deterministic visual mapping of the current State, guaranteeing consistent rendering regardless of navigation paths.
Compose Business‑Model Abstraction
MYTab contains many view components with subtle variations for different user groups and UI versions. By introducing a ViewModel layer and moving version‑specific adaptation logic to dedicated Model and View layers, most core business logic is centralized in ViewModel, dramatically improving maintainability.
Challenges and Solutions
During a gray‑release three issues emerged:
Frame‑rate drop on iPhone devices.
Stability problems on all three platforms.
1. Frame‑Rate Drop Investigation and Fix
Enable iOS ProMotion High‑Refresh‑Rate
Alipay’s app defaults to 60 Hz rendering, while iPhone 13 Pro and later support up to 120 Hz. Explicitly enabling 120 Hz reduced the perceived smoothness gap.
Mitigate Over‑Recomposition
Strategy 1: Split Large @Composable Functions
Compose recomposes any @Composable that observes changed State. Even if child functions are skipped, the parent recomposition still incurs cost, especially when a ScrollableState is observed. Extracting non‑scroll‑related UI into separate @Composable functions reduces the recomposition overhead during scrolling.
Strategy 2: Watch for Hidden Over‑Recomposition
Unstable lambda captures can cause unexpected recompositions. The example below shows a button click that triggers recomposition of both SubItem1 (expected) and SubItem (unexpected) because the lambda captures an unstable variable.
class MyModel() {
var field1: Int = 0
}
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
Text(text = "Hello $name!", modifier = modifier)
val model = remember { MyModel() }
val count = remember { mutableStateOf(0) }
Column(modifier = Modifier.padding(top = 100.dp)) {
Button(onClick = { count.value++ }) {
Text("Button")
}
SubItem(lambdaParam = { print("$model") })
SubItem1(count.value)
}
}Conclusion: SubItem1 recomposes because it depends on the changing count state. SubItem recomposes even though it does not use count; the lambda captures an unstable variable, causing hidden recomposition.
2. Stability Issues Fixes
Typical Problem 1: SnapshotException
During the gray‑release, all platforms reported SnapshotException. The root cause was modifying Compose State from a background thread while a recomposition was in progress. The team rolled back the experiment that pushed State changes directly from background threads and audited the codebase to ensure all State writes occur on the main thread.
Typical Problem 2: Platform API Deadlock on HarmonyOS
HarmonyOS reported a large number of ANR (Application Not Responding) incidents. The deadlock originated from the Kotlin‑Native runtime acquiring a global lock when accessing a Companion object, while the same thread simultaneously waited for a JS‑runtime call that also required the lock.
Resolution steps:
Avoid platform‑layer API calls during Companion object initialization.
Wrap all platform calls in suspend functions and avoid runBlocking on background threads.
Refactor Benefits
After addressing the performance and stability issues, MYTab migrated to the KMP version on all three platforms, retiring legacy code and achieving the "one‑code‑for‑three‑platforms" goal. The unified codebase increased development efficiency, eliminated logic inconsistencies, accelerated HarmonyOS adoption, and improved overall code quality and developer experience.
Future Outlook
Even with massive traffic, KMP still encounters edge cases such as occasional rendering failures on specific ROMs and rare Metal pipeline blockages. Ongoing work will broaden KMP adoption, share lessons with the industry, and continue refining the framework toward a "super app" development platform.
References
Android Compose stability documentation: https://developer.android.com/develop/ui/compose/performance/stability
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
