跪拜 Guibai
← Back to the summary

Compose Replaces Manual UI Sync with a Single Principle: UI = f(state)

Positioning: Introduction / Concept Enlightenment Suitable for: Android developers who have never touched Compose, or students transitioning from the View system


I. First, a story: Two ways to renovate a room

Imagine you want to renovate a room. There are two ways to do it.

Method A: Imperative You hold a checklist and direct the workers step by step:

  1. Nail a hook into the wall.
  2. If 3 pieces of clothing are hung on the rack, replace the hook with a larger one.
  3. If there are more than 10 pieces of clothing, install another wardrobe.

For every new state, you must manually write "if... then..." logic. The interface and the state are two parallel lines, and you must manually keep them synchronized. This is traditional Android View + XML development: findViewById, setText, setVisibility... whenever the state changes, you have to remember to update the corresponding control.

Method B: Declarative You write just one sentence: "Number of racks = number of clothes ÷ 5, number of wardrobes = number of clothes ÷ 10". The worker (the framework) calculates it itself; you never manually move bricks.

This is Jetpack Compose. You only describe "what the interface looks like," and the framework is responsible for "how to update it after the state changes." This is a fundamental shift in mindset.


II. What pain points does Compose actually solve?

Pain Point View + XML Era Compose
State Synchronization Manual setText/setVisibility, easy to miss updates Interface automatically recomposes when state changes
Code Dispersion XML manages layout, Kotlin manages logic, Adapter manages lists One Kotlin function handles everything
Custom Controls Inherit View, write onMeasure/onDraw, lots of boilerplate code One @Composable function
List Reuse RecyclerView + Adapter + ViewHolder LazyColumn one function

For beginners, the biggest advantage of Compose is: a short learning path and a low mental burden for writing interfaces. For senior developers, the biggest advantage is: it eliminates the entire layer of error-prone glue code between View and data.


III. The first Compose program: Hello Compose

First, get a feel for what it looks like. Below is a complete, runnable minimal example:

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import com.example.hellocompose.ui.theme.HelloComposeTheme

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            HelloComposeTheme {
                Surface {
                    Greeting("Compose")
                }
            }
        }
    }
}

@Composable
fun Greeting(name: String) {
    Text(text = "Hello, $name!")
}

@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
    HelloComposeTheme {
        Greeting("Compose")
    }
}

There are two most critical things in this code:

  1. @Composable: Marks a function as a function that "describes the interface." It has no return value because it's not "returning an interface," but rather "drawing the interface."
  2. setContent { }: The entry point for the Activity, switching from the traditional setContentView to here. Everything that follows runs within this declarative world.

IV. Declarative vs. Imperative: The same counter, two ways to write it

This is the most important case study in this article; please compare carefully.

Imperative approach (traditional View) — you must manually do three steps

class CounterActivity : AppCompatActivity() {
    private var count = 0
    private lateinit var textView: TextView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_counter)
        textView = findViewById(R.id.tv_count)
        findViewById<Button>(R.id.btn_add).setOnClickListener {
            count++
            textView.text = "$count"   // ① Must manually synchronize
        }
    }
}

Declarative approach (Compose) — only describe the relationship

@Composable
fun Counter() {
    var count by remember { mutableStateOf(0) }  // State
    Column {
        Text("Current count: $count")                 // Interface
        Button(onClick = { count++ }) {         // Event only changes state
            Text("+1")
        }
    }
}

Notice: In the Compose version, there is not a single line of "update text" code. You just say "the text displays count," and then the button increments count by one. The framework detects that count has changed, automatically re-executes Counter, and the text updates itself.

This is the mental model of Compose, summarized in one sentence:

UI = f(state). The interface is a function of state; when the state changes, the interface is automatically recalculated.


V. 3 terms beginners must know (just get familiar for now)


VI. The "aha!" moment for senior developers

If you are coming from the View system, focus on experiencing these three points:

  1. No findViewById, and no notifyDataSetChanged. UI updates are no longer a series of commands, but a declaration of "the interface derived from the state." This eliminates the most common type of bug: the state changed but the interface forgot to refresh.
  2. Composable functions will be called repeatedly, and out of order. Therefore, they must be "pure functions" — the same input produces the same output, and you cannot write side effects inside them (like directly making a network request). Many people don't understand this at first; the next article will go deeper.
  3. Compose is not "View with a different syntax sugar." It is a new system where the compiler, runtime, and UI toolchain work together. Once you understand "declarative" and "recomposition," all subsequent knowledge will fall into place logically.

VII. Common pitfalls


VIII. Hands-on exercise

Modify the counter demo above: add a "-1" button, and display whether "count is even or odd." Run it yourself after the changes to experience the feeling of "only changing the state, not touching the interface."


IX. Learning resources

(Next article preview: Composable functions and the "recomposition" mechanism, understanding the engine of Compose.)