kotlinandroid-jetpack-compose

Understanding the subscription mechanism of Jetpack Compose


I'm looking at this working code:

class MainActivity : AppCompatActivity() {
    
    var progress by mutableStateOf(0f)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val composeView = findViewById<ComposeView>(R.id.composeView)


        composeView.setContent {
            ComposeProgressThingy(progress = progress)
        }
        thread {
            while (progress < 1f) {
                Thread.sleep(500) // Simulate work
                progress += 0.1f // Update progress
            }
        }

    }
}

What I don't understand is how ComposeProgressThingy is aware of changes to progress. I never explicitly registered ComposeProgressThingy as a listener or observer of progress.

It feels like some kind of "black magic" is happening here. Does the lambda passed to setContent{} somehow analyze its references—at compile time or runtime—and automatically subscribe to any mutableStateOf() variables it uses?

Can someone explain what mechanism is at work here?


Solution

  • progress is backed by mutableStateOf(0f), which creates a Compose State object. That is an observable container that holds a value (which you unwrap using Kotlin's by delegate). This takes care of tracking changes happening to progress.

    Now, reacting to these changes is more complicated. That is handled by Compose's Snapshot system. The link explains a lot of the technical details, but it basically boils down to the Compose compiler generating code to re-execute functions annotated with @Composable and the Compose runtime to determine when that is necessary (i.e. observing which State variables changed).

    So, the "black magic" you talk about is actually just some elaborate code, hidden in the Compose compiler plugin and the Compose libraries you use.

    In conclusion, when you use a State you sign up to observe it for changes, and by annotating a function with @Composable you kind of register an observer to listen to changes of any State that it uses to re-execute the function. You just don't need to do any of the dirty work yourself.