State hoisting in Jetpack Compose

504 Views Asked by At

This post has been edited in order to provide more context to my question. The "final" version, which I can develop with my current knowledge and the solution that I can understand better, is this. I would appreciate it if you could analyze it and give me the OK. I am aware that there may be some better solutions, but this one is mine ;)

            var myTextQuantityState by remember { mutableStateOf(1) }

            Text(myTextQuantityState)

            Button(
                Modifier.constrainAs(buttonAdd) {
                    end.linkTo(parent.end)
                    bottom.linkTo(buttonOrder.top)
                    top.linkTo(imageIceCream.bottom)
                },
                myTextQuantityState
            )
            { myTextQuantityState = it }
        }
    }
}

@Composable
fun Text(text: Int) {
    Text(
        text = text.toString(),
    )
}

@Composable
fun Button(
    modifier: Modifier,
    currentTextQuantityState: Int,
    increaseIceCreamQuantity: (Int) -> Unit
) {
    Button(
        onClick = {
            increaseIceCreamQuantity(currentTextQuantityState + 1)
        },
        modifier = modifier
    ) {
        
    }
}
2

There are 2 best solutions below

4
nguch On

I'm 2 months into Android so I can't tell you the "best" way. But here is how I would do it.

var myTextQuantityState by remember { mutableStateOf(1) }

Text( text = myTextQuantityState.toString() )

Button( onClick = { myTextQuantityState += 1 } ) {}

The reason I would do it this way is because I would prefer to have the lambda in a position to directly access the quantity without having to pass it as a variable like you are doing with the increaseIceCreamQuantity function

Here is how it would work if the state and button were in different composables

@Composable
fun screen1() {
   var myTextQuantityState by remember { mutableStateOf(1) }

   Text( text = myTextQuantityState.toString() )
   screen2(increment = { myTextQuantityState += 1 })
}

@Composable
fun screen2(
   increment: () -> Unit
) {  
   Button( onClick = { increment() } ) {}
}

Based on your current code. The solution is still similar to the function I have above. The currentTextQuantityState value you have as a parameter in your Button composable is actually a reference to the original one so you can still pass a lambda that doesn't need an input. Here is an edit of your code to clarify what I mean. I have put ... to skip code I didn't change

           ...
            Button(
                Modifier..., 
// A CHANGE HERE. NO NEED TO PASS IN myTextQuantityState. It can be modified from here by the button. The button gets the lambda and the lambda has access to myTextQuantityState. The button in your scenario does not need direct access to myTextQuantityState
                { myTextQuantityState += 1 }, // HERE IS A CHANGE
            )
            ...
}

...

@Composable
fun Button(
    modifier: Modifier, 
    increaseIceCreamQuantity: () -> Unit // HERE IS A CHANGE
) {
    Button(
        onClick = {
            increaseIceCreamQuantity() // HERE IS A CHANGE
        },
        modifier = modifier
    ) {
        
    }
}
2
Allan W On

As you mentioned, the second implementation currently isn't hoisted, as you have direct access to the stateful value. In terms of what you can do, you can (as in it will work) pass the MutableState<T> directly, where you have both a getter and setter. This is effectively what the first option does through the use of delegates:

https://developer.android.com/jetpack/compose/state#state-in-composables

val (value, setValue) = remember { mutableStateOf(default) }

Passing in the value + a setter lambda directly however is compose agnostic, as how you get the value and what you provide in the lambda can be whatever you want.


If your goal is to use data down several composables, with the option to modify data, and you'd like to avoid passing a getter and a setter to each composable, you can take a look at CompositionLocal:

https://developer.android.com/jetpack/compose/compositionlocal#deciding

It allows you to get values within tree hierarchy, at the cost of making it harder to reason about the data you have available. Some parent is expected to provide the CompositionLocal.