How does compose update scroll state internally?

63 Views Asked by At

As the Compose documents suggest, To make a composable stateless, We use state hoisting by replacing the state variable with two parameters: value and onValueChananged.

Something like this:

MyText(text: String, onTextChanged: (String) -> Unit

However, in some cases, the composable function gets a value without state hoisting, for example when we are interested in reading ScrollableState or LazyListState, we pass a remembered state to the composable function:

val lazyListState = rememberLazyListState()
LazyRow(state = lazyListState) {...}

With the given code above, let's assume that lazyListState is indirectly used in MyItem composable:

items(items = myItems,
    key = { item -> item.id }) { item ->
    val someDerivedState by remember {
        derivedStateOf {
           someComputationsWithLazyListState(lazyListState)
        }
    }
    MyItem(item = item, deferredDerivedState = { someDerivedState })
}

Here is my question, How does compose keep lazyListState updated internally without passing it by something like oScrollstateChange?

Note that I'm not asking how to use LazyListState, The question is about how it works.

1

There are 1 best solutions below

0
Farshad On BEST ANSWER

Well, It was easier than I thought, The short answer is MutableState!

Instead of LazyListState, I start with a simpler example. Let's assume there is an interface named Person with this contract:

interface Person {
    val name: String
    val age: Int
}

We want to update the age internally and let the Compose know, Here is how it is implemented:

internal class PersonImpl(override val name: String, age: Int) : Person {

    override var age: Int by mutableStateOf(age)
        private set

    override fun oneYearLater() {
        age += 1
    }
}

Also, we can provide an instance of PersonImpl as Person by a composable function, something like rememberLazyListState (But LazyListState is not an internal class):

@Composable
fun rememberPerson(name: String, age: Int): Person = remember {
    PersonImpl(name, age)
}

Note that oneYearLater is added to simulate internal updates of the class. Every time that we invoke this function, Compose knows the state is changed and If any composable accepts it as a state, There will be a recomposition:

val person = rememberPerson(name = "Farshad", age = 21)
Column(
    modifier = Modifier.padding(8.dp),
    horizontalAlignment = Alignment.CenterHorizontally,
    verticalArrangement = Arrangement.Center
) {
    val message by remember {
        derivedStateOf {
            "${person.name} is ${person.age} years old!"
        }
    }
    Text(
        text = message,
        style = MaterialTheme.typography.headlineMedium
    )
    Button(modifier = Modifier.padding(8.dp), onClick = {
        person.oneYearLater()
    }) {
        Text(text = "Increase age by 1")
    }
}

So, every button click increases age by one and the Text will be updated.