I'm building a calculator app in Jetpack Compose and facing an issue with recomposition. My layout has three main components: CalculatorDisplay
, CalculatorAdvanced
, and CalculatorPad
. The layout uses weights for responsive design. However, when I toggle theisExpanded
state using CalculatorExpandButton
, the whole screen recomposes instead of only the relevant components (CalculatorAdvanced and CalculatorPad)
.
CalculatorDisplay
from recomposing unnecessarily.CalculatorAdvanced
and CalculatorPad
recompose when isExpanded
changes.remember
and derivedStateOf
: I applied these to advancedWeight
and padWeight
to minimize state updates, but the entire UI still recomposes. (Maybe I did it incorrectly)CalculatorExpandButton
causes the whole PhonePortraitLayout
to recompose.Here's the code for the layout:
@Composable
fun CalculatorScreen(viewModel: CalculatorViewModel = viewModel()) {
val context = LocalContext.current
val activity = context as Activity
val window = activity.window
val windowSizeClass = rememberWindowSizeClass()
val color = MaterialTheme.colorScheme.surfaceColorAtElevation(24.dp).toArgb()
SideEffect {
window.statusBarColor = color
}
val expandedStateHandler = remember {
ExpandedStateHandler()
}
Box(modifier = Modifier.fillMaxSize()) {
when {
/** PHONE */
windowSizeClass.isPhonePortrait -> PhonePortraitLayout(
viewModel = viewModel,
windowSizeClass = windowSizeClass,
expandedStateHandler = expandedStateHandler
)
}
}
}
class ExpandedStateHandler {
var isExpanded by mutableStateOf(false)
private set
fun toggle() {
isExpanded = !isExpanded
}
}
@Composable
fun PhonePortraitLayout(
viewModel: CalculatorViewModel,
windowSizeClass: MyWindowSizeClass,
expandedStateHandler: ExpandedStateHandler
) {
val displayWeight = 0.3337f // Fixed weight for CalculatorDisplay
val advancedWeight = if (expandedStateHandler.isExpanded) 0.1451f else 0.045f
val padWeight = if (expandedStateHandler.isExpanded) 0.4f else 0.5f
Column(
verticalArrangement = Arrangement.spacedBy(8.dp),
modifier = Modifier.padding(bottom = 8.dp)
) {
// Stabilized CalculatorDisplay
Box(
modifier = Modifier
.fillMaxWidth()
.weight(displayWeight)
) {
CalculatorDisplay(
viewModel = viewModel,
modifier = Modifier.fillMaxSize()
)
}
Row(
modifier = Modifier
.fillMaxWidth()
.weight(advancedWeight)
) {
Box(
modifier = Modifier
.weight(3.1f) // Keeps internal proportions
.padding(start = 16.dp, end = 4.dp)
) {
CalculatorAdvanced(
viewModel = viewModel,
windowSizeClass = windowSizeClass,
isExpanded = expandedStateHandler.isExpanded,
modifier = Modifier.fillMaxSize()
)
}
Box(
contentAlignment = Alignment.TopEnd,
modifier = Modifier
.padding(start = 4.dp, end = 16.dp)
.weight(0.56f)
) {
CalculatorExpandButton(
text = if (expandedStateHandler.isExpanded) "Adv" else "Basic",
onClick = { expandedStateHandler.toggle() }
)
}
}
Box(
modifier = Modifier
.fillMaxWidth()
.weight(padWeight)
.padding(horizontal = 16.dp)
) {
CalculatorPad(
viewModel = viewModel,
windowSizeClass = windowSizeClass,
modifier = Modifier.fillMaxSize()
)
}
}
}
I think you need to make two changes:
First, move the following code into the "PhonePortraitLayout" Composable:
val expandedStateHandler = remember {
ExpandedStateHandler()
}
If you want to prevent "CalculatorDisplay" from recomposing, move it out of the "PhonePortraitLayout" and place it in the "CalculatorScreen."