Applying Domain‑Driven Design to Large List Architecture in Android
This article describes how the Lofter team applied Domain‑Driven Design to refactor and govern complex, multi‑type RecyclerView lists on Android, progressing from a monolithic adapter through a split‑stage implementation to a full DDD‑based framework with reusable bars and context‑aware layout managers.
Domain‑Driven Design (DDD) was introduced in 2004 and has recently been adopted for backend and mobile architectures. The article uses the Lofter interest‑community app as a case study to illustrate the challenges of rendering massive RecyclerView lists that contain dozens of item types (text, photo, video, music, etc.) and many shared UI elements such as likes, comments, and menus.
1. Single‑Item Stage – Initially all item types were handled in a single LofterAdapter with a large switch‑case inside convert() . The adapter grew to over 8,000 lines, making maintenance and iteration costly.
public class LofterAdapter
extends BaseMultiItemQuickAdapter
{
@Override
public void convert(helper: K, item: T) {
switch (item.type) {
TEXT: ...; break;
PHOTO: ...; break;
VIDEO: ...; break;
MUSIC: ...; break;
}
}
}2. Divide‑and‑Conquer Stage – The team built a ComMultiItemAdapter that registers separate item‑holder controllers for each type, moving layout creation and data binding into dedicated render classes such as TextItemRender and PhotoItemRender . This reduced the monolithic switch but still suffered from a bloated base render class.
addItemHolderController(TEXT, R.layout.text, new TextItemRender(adapterController));
addItemHolderController(PHOTO, R.layout.photo, new PhotoItemRender(adapterController));3. DDD Stage – By treating each UI fragment (called a “bar”) as a bounded context, the architecture separates concerns more cleanly. Bars encapsulate view lookup, click handling, data binding, and lifecycle management. Example of a simple bar implementation:
class DebugInfoBar : FrameLayout, ItemPartView {
private var hintTv: TextView? = null
init { View.inflate(context, R.layout.item_part_debug_info_bar, this) }
override fun onCreate() { hintTv = findViewById(R.id.hint_tv) }
override fun onUpdate(itemModel: Any?) { /* update UI */ }
}Bars can communicate via a shared ItemPartLayoutMan that receives lifecycle events, broadcasts, and scroll callbacks. For example, PostOwnerItemPartLayoutMan listens for follow‑user broadcasts and triggers partial UI updates.
override fun onContextAttached() {
adapterContext.registerLocalBroadcastReceiver(FOLLOW_FILTER, followUserReceiver)
adapterContext.registerLocalBroadcastReceiver(COLLECT_FILTER, updateCollectionInfoReceiver)
}The final DDD architecture is visualized as a hierarchy where each item’s XML only declares the list of bars it composes, dramatically simplifying layout files and enabling reuse across pages.
To adopt the framework, developers register the required ItemLayoutMan for each item type (text, photo, video, etc.) in the activity/fragment, and the framework assembles the appropriate bars at runtime.
fun createAdapter(): UnityAdapter {
adapter = UnityAdapter(this, ArrayList
()).apply {
registerItemLayoutMan(POST_CARD_TEXT, R.layout.post_card_item_text, PostTextItemLayoutMan::class.java)
registerItemLayoutMan(POST_CARD_PHOTO, R.layout.post_card_item_photo, PostPhotoItemLayoutMan::class.java)
// ... other types
}
}The article concludes that DDD‑driven large‑list governance improves maintainability, reduces merge conflicts, and enables multiple teams to collaborate efficiently on complex UI lists.
LOFTER Tech Team
Technical sharing and discussion from NetEase LOFTER Tech Team
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.