I am getting extremely frustrated with recompositi...
# compose
j
I am getting extremely frustrated with recomposition and was hoping I could get some assistance here. I have
Copy code
var rowHolder by remember { mutableStateOf(StateWrapper(initialState)) }
State wrapper is defined as
Copy code
data class StateWrapper<T>(
    val state: T
)
and in this case the initialState is
Copy code
initialState: List<TableRow>
I have a button that looks like this
Copy code
Button( onClick =  {
                active = false
                //state.value.add(TableRow(Song("sup", 0),false))
                rowHolder = rowHolder.copy(state = listOf())
                val rows: MutableList<TableRow> = mutableListOf()
                for (i in 0 until 50) {

                    rows.add(
                        TableRow(
                            Song(
                                "Song $i",
                                i.toString(),
                                Random.nextInt(3).let {
                                    STATUS.values()[it]
                                }
                            )
                        )
                    )

                }
                rowHolder = StateWrapper(rows)

            }) {
                Text("POPUL8")
            }
The code looks something like this
Copy code
@Composable fun render() {
          // define rowHolder
         // button code 
         renderComponent(rowHolder)

}

@Composable fun renderComponent(rowHolder: StateWrapper) {
        var ret by remember {mutableStateOf(rowHolder)}
       // make a bunch of cards. 
}
clicking the button does nothing...
u
So you have 2 mutableState instance of StateWrapper, by clicking the button you update the outside one, then passing it to
renderComponent
, creating and remembering an inside mutableState instance of StateWrapper?
j
yes
u
That is the problem. The inner mutableState is remembered unconditionally, which means it will only access the param passed in only in the first time, and ignore the updated param passing later. Try removing the inner mutableState in
renderComponent
and use the parameter rowHolder directly. And check https://developer.android.com/jetpack/compose/state#retrigger-remember for more infomations.
j
but I am not able to change the actual rowholder as it is a function parameter?
a
rowHolder is already a mutable state in
render
, so why are you re-remembering it in
renderComponent
? StateWrapper is just a data class, so you don't need to remember or mutate it again. Use it directly.
When you mutate rowHolder in
render
,
renderComponent
will recompose with the correct mutated value, but then as explained above, you're re-remembering only the initial value (
var ret by remember {mutableStateOf(rowHolder)}
). The link above explains why, and how you can re-trigger by passing in keys (if key changes, init will run again). In any case, I'm not sure why you're creating another mutable state of the same thing in a child composable.
If you're not mutating it again in the child, then why not use it directly? I think you should go through the docs again — even the comments in code — to understand what remember & mutableState do. If child is re-mutating, then this is a bug. You should have only 1 source of truth for any "state", and that should be the parent composable in this case. Do something like this instead:
Copy code
@Composable fun render() {
    var rowHolder by remember { mutableStateOf(StateWrapper(initialState)) }
    // button code 
    renderComponent(rowHolder, { rowHolder = it })
}

@Composable fun renderComponent(rowHolder: StateWrapper, onRowHolderChange: (StateWrapper) -> Unit) {
    // use rowHolder directly
    // if changed, for example in a button or in response to another event, call `onRowHolderChange` with the mutated value
}
j
It turned out the bug was not with recomposition but with a reset data call that happens during recomposition, thanks for the help