Mobile Development 16 min read

Managing Complexity in Android Search UI: From MVC to MVI and Kotlin DSL Implementation

This article explains how to reduce UI complexity in Android by layering architecture, tracing the evolution from MVC through MVP and MVVM to MVI, and demonstrates a searchable interface built with Jetpack Navigation and Kotlin DSL, highlighting pitfalls of monolithic initView methods and proposing more reusable abstractions.

Sohu Tech Products
Sohu Tech Products
Sohu Tech Products
Managing Complexity in Android Search UI: From MVC to MVI and Kotlin DSL Implementation

Software’s primary technical mission is to "manage complexity" (Code Complete). Layered architecture isolates concerns, as illustrated by the TCP/IP five‑layer model, which limits the impact of changes to a single layer.

Android UI architecture has evolved from MVC to MVP, then MVVM, and now MVI. MVVM remains the most common Android pattern, while MVP usage has declined sharply since 2018, as shown by Google Trends.

The article uses a real‑world search feature to showcase how to apply these concepts. The search page consists of an Activity that hosts a static search bar and a FragmentContainerView for three fragment states: history, suggestions, and results.

Navigation is handled by Jetpack’s Navigation component, defined in an XML graph with three fragments and actions for moving between them. Example navigation XML:

<navigation xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/search_navigation" app:startDestination="@id/SearchHistoryFragment">
    <fragment android:id="@+id/SearchHintFragment" android:name="com.bilibili.studio.search.template.fragment.SearchHintFragment" android:label="search_hint_fragment">
        <action android:id="@+id/action_to_result" app:destination="@id/SearchResultFragment" />
        <action android:id="@+id/action_to_history" app:popUpTo="@id/SearchHistoryFragment" />
    </fragment>
    ...
</navigation>

The UI layout is built with a Kotlin DSL that creates the view hierarchy programmatically, avoiding XML inflation and findViewById calls. A shortened excerpt of the DSL:

class TemplateSearchActivity : AppCompatActivity() {
    private val contentView by lazy {
        LinearLayout {
            orientation = vertical
            // Search bar
            ConstraintLayout {
                ivBack = ImageView { /* back button */ }
                vInputBg = View { /* background with rounded corners */ }
                etSearch = EditText { /* hint, input type, etc. */ }
                ivClear = ImageView { /* clear button */ }
                tvSearch = TextView { /* search button */ }
            }
            // Fragment container
            FragmentContainerView { layout_id = NAV_HOST_ID }
        }
    }
}

Interaction logic is implemented with listeners. When the activity starts, the search button is disabled and the clear button hidden; the keyboard is shown after a short delay. As the user types, a TextWatcher toggles the clear button visibility and enables the search button:

etSearch.addTextChangedListener(object : TextWatcher {
    override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
        val input = s?.toString() ?: ""
        if (input.isNotEmpty()) {
            ivClear.visibility = visible
            tvSearch.apply { textColor = "#F2F4FF"; isEnabled = true }
        } else {
            ivClear.visibility = gone
            tvSearch.apply { textColor = "#484951"; isEnabled = false }
        }
    }
    // other callbacks omitted
})

Search actions (keyboard IME or button click) invoke searchAndHideKeyboard() , which expands the search bar, navigates to the result fragment, hides the search button, and dismisses the keyboard. Returning from the result fragment restores the original UI state via backToHistory() .

The article points out that these methods mix multiple responsibilities—navigation, UI mutation, and keyboard handling—making them hard to reuse and prone to bugs. It argues for a single‑purpose abstraction, such as an MVI‑style state reducer, where UI changes are driven by a model rather than scattered imperative code.

In conclusion, the current implementation lacks a ViewModel or explicit model layer, leading to duplicated logic for the search button and high maintenance cost. Adopting a declarative UI framework (Compose) or an MVI architecture would centralize UI state, reduce coupling, and improve testability.

UIarchitectureandroidKotlinMVVMnavigationMVI
Sohu Tech Products
Written by

Sohu Tech Products

A knowledge-sharing platform for Sohu's technology products. As a leading Chinese internet brand with media, video, search, and gaming services and over 700 million users, Sohu continuously drives tech innovation and practice. We’ll share practical insights and tech news here.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.