ivoronline
ivoronline

Reputation: 1079

Jetpack Compose actually works with Classes and not Functions?

When you declare @Composable Function you are actually declaring a Class

This is why variable value is remembered between onClicks. Every click executes Class Method which increases Class Property. This can be seen in Console since we are not updating UI at this point (there is no recomposition taking place).

So the way that Jetpack Compose works and is being explained is totally misleading. It is explained that you work with Functions but in background they behave like Classes in order to make them stateful. So instead of using well known constructs like Classes to implement UI Views they are using Functions with a lot of magic behind the scene to make them behave stateful like Classes. Am I missing something?

//======================================================================
// MAIN ACTIVITY
//======================================================================
class MainActivity : ComponentActivity() {
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContent {
      Column {
        MyComposable("Object1") //Create Object of Class MyComposable
        MyComposable("Object2") //Create Object of Class MyComposable
      }
    }
  }
}

//======================================================================
// MY COMPOSABLE
//======================================================================
// Instead of Function you are actually creating Object behind the scene
// So this Function actually works as Constructor for MyComposable Class    
@Composable
fun MyComposable(name: String) { //CLASS

  //CLASS PROPERTY
  var myProperty = name

  //CLASS METHOD: onClick
  Text(text = myProperty, modifier = Modifier.clickable(onClick = {
    myProperty += " clicked"
    println(myProperty)
  }))

}

Console

Object1 clicked
Object1 clicked clicked

Object2 clicked

Upvotes: 5

Views: 4305

Answers (1)

Logain
Logain

Reputation: 4364


From the book Jetpack Compose Internals

Any function that is annotated as @Composable gets translated by the Jetpack Compose compiler to a function that implicitly gets passed an instance of a Composer context as a parameter, and that also forwards that instance to its Composable children. We could think of it as an injected implicit parameter that the runtime and the developer can remain agnostic of. It looks something like this. Let’s say we have the following composable to represent a nameplate:

NamePlate.kt

@Composable
fun NamePlate(name:String,lastname:String){ 
    Column(modifier = Modifier.padding(16.dp)) {
        Text(text = name)
        Text(text = lastname, style = MaterialTheme.typography.subtitle1)
    }
}

The compiler will add an implicit Composable parameter to each Composable call on the tree, plus some markers to the start and end of each composable. Note that the following code is simplified, but the result will be something like this:

fun NamePlate(name:String,lastname:String, $composer:Composer<*>){
    $composer.start(123)
    Column(modifier = Modifier.padding(16.dp), $composer) {
        Text(text = name, $composer            
        Text(text = lastname, style = MaterialTheme.typography.subtitle1, $composer)
        $composer.end()
    }
}

In the example you are presenting you are basically using a closure that keeps local state by referencing its outer context.

You don't need Compose to achieve this, vanilla Kotlin (or Java for that matter), behaves like this:

fun main() {
    val createCounter = {
        var x = 0
        {
            x += 1
            "x=$x"
        }
    }
    val nextValue = createCounter()

    print(nextValue()) // x=1
    print(nextValue()) // x=2
    print(nextValue()) // x=3
}

While the language allows you to do that, you are actually breaking one of the prerequisites for Compose which is that your functions should be Pure, or at least not have uncontrolled side effects.

What the Compose functions do is similar to a visitor pattern, where the composer is passed down the tree and each function emits to that composer. By being free of sides effects, the compose compiler can assume that if the value of the parameters do not change, then the what the function emits won't change, and so a recomposition (rerun of the function) is not required.

Upvotes: 3

Related Questions