Applying Divide‑and‑Conquer and Jetpack Architecture to Decouple Complex Payment Flows in Android
The article describes how a Ctrip senior Android engineer uses divide‑and‑conquer, MVP with Clean Architecture, and Jetpack components such as LiveData and ViewModel to break down a highly coupled payment module into reusable view components, improve data flow, and simplify testing and maintenance.
Author : Ryann Liu, senior software engineer at Ctrip, responsible for Android payment development for both Chinese and international versions.
Business background : In e‑commerce, payment pages involve many methods—multiple bank cards, points, Trip Coins, gift cards, coupons, service fees, and mixed‑payment scenarios—creating a highly coupled system that must handle dynamic configuration changes and view updates.
Divide and Conquer : To reduce coupling, the team isolates business logic from Activity/Fragment using MVP + Clean Architecture, treating each view as a base case . This componentization follows the classic divide‑and‑conquer principle: split a complex problem into smaller, similar sub‑problems until they can be solved directly.
Partitioning : Guided by the SOLID single‑responsibility principle, each UI element on the payment page becomes a base case. A screenshot (see image) shows the partitioning of a Hong Kong‑currency payment scenario.
After partitioning, each base case can be combined flexibly, lowering integration cost and enabling rapid verification.
2.1.3 Summary : The resulting view componentization yields highly reusable modules, improves maintainability and extensibility, and when combined with MVP + Clean Architecture, produces concise, testable code.
Data Flow : Although componentization decouples UI, data still needs to flow between base cases (e.g., mixed‑payment, coupons, service‑fee recalculations). Jetpack's LiveData and ViewModel are introduced to manage this flow.
LiveData analysis : LiveData is an observable data holder that is lifecycle‑aware, updating only active observers and preventing crashes during configuration changes.
@MainThread
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
...
LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
...
owner.getLifecycle().addObserver(wrapper);
}Advantages of LiveData include eliminating boiler‑plate lifecycle code, delivering the latest data after a pause, avoiding crashes when the UI is stopped, and automatically clearing observers to prevent memory leaks.
ViewModel introduction : ViewModel stores UI‑related data across configuration changes, ensuring data survives screen rotations.
public final Object onRetainNonConfigurationInstance() {
...
ViewModelStore viewModelStore = mViewModelStore;
...
NonConfigurationInstances nci = new NonConfigurationInstances();
nci.viewModelStore = viewModelStore;
return nci;
} public ViewModelStore getViewModelStore() {
...
if (mViewModelStore == null) {
NonConfigurationInstances nc = (NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
mViewModelStore = nc.viewModelStore;
}
if (mViewModelStore == null) {
mViewModelStore = new ViewModelStore();
}
}
return mViewModelStore;
} public ComponentActivity() {
...
getLifecycle().addObserver(new LifecycleEventObserver() {
@Override
public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event) {
if (event == Lifecycle.Event.ON_DESTROY) {
if (!isChangingConfigurations()) {
getViewModelStore().clear();
}
}
}
});
...
}In the project, each base case defines its own LiveData and exposes proxy methods for external updates. All LiveData are aggregated in a shared ViewModel, while a ViewHelper acts as an intermediate layer to route data between base cases and the Activity/Fragment, avoiding the latter becoming a controller.
Limitations of LiveData include its sticky‑event nature (only the latest value is delivered after a pause) and the need to prevent duplicate handling across multiple observers. Solutions involve event‑wrapper classes or reflective tweaks to the last version, and custom wrappers that add flags to control propagation.
Testing : After partitioning, each view can run independently, enabling fast verification and simple self‑testing during development.
Conclusion : By understanding the business context and code structure, the team decomposes a complex payment flow into isolated components, applies MVP + Clean Architecture, and leverages Jetpack’s lifecycle‑aware components to achieve clearer code, easier maintenance, and quicker validation.
Ctrip Technology
Official Ctrip Technology account, sharing and discussing growth.
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.