Я протестировал это, используя Kotlin 2.0.0, 2.0.10-RC и 1.9.20, просто чтобы убедиться, что это не какая-то странная проблема с компилятором Kotlin. Однако в настоящее время я пытаюсь понять, как лямбды влияют на возможность пропустить рекомпозицию компонента с использованием указанной лямбды. Я также сгенерировал метрики компилятора Compose для своего проекта для всех, кто заинтересован в воспроизведении этой проблемы, но все компоненты и классы были помечены как пропускаемые и стабильные. (даже лямбды)
Мой экран сейчас выглядит так:
@Composable
fun CounterScreen(presenter: CounterPresenter) {
val state = presenter.present()
CounterContent(state)
}
@Composable
private fun CounterContent(state: CounterState) {
// remember lambdas to ensure recomposition of buttons is skipped
val increment = remember {
{ state.eventSink(CounterEvent.Increment) }
}
val decrement = remember {
{ state.eventSink(CounterEvent.Decrement) }
}
Scaffold(content = { paddingValues ->
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Column(
modifier = Modifier.padding(paddingValues),
horizontalAlignment = Alignment.CenterHorizontally,
) {
TextButton(onClick = increment) {
Text("Increment")
}
Text("Count: ${state.count}")
Text(state.message)
TextButton(onClick = decrement) {
Text("Decrement")
}
}
}
})
}
Остальная часть моей настройки Presenter, State, Event выглядит следующим образом:
sealed interface CounterEvent : Event {
data object Increment : CounterEvent
data object Decrement : CounterEvent
}
class CounterPresenter : Presenter<CounterState> {
@Composable
override fun present(): CounterState {
var count by rememberSaveable { mutableIntStateOf(0) }
val message by remember {
derivedStateOf {
when {
count < 0 -> "Counter is less than 0"
count > 0 -> "Counter is greater than 0"
else -> "Counter is 0"
}
}
}
return CounterState(
count = count,
message = message,
eventSink = { event ->
when (event) {
Increment -> count += 1
Decrement -> count -= 1
}
}
)
}
}
data class CounterState(
val count: Int,
val message: String,
override val eventSink: (CounterEvent) -> Unit,
) : State<CounterEvent>
Нажатие кнопки увеличения 10 раз приводит к следующим отсчетам рекомпозиции:
Как видите, кнопки пропускаются правильно. Однако включение сильного пропуска теоретически должно позволить мне удалить вызовы запоминания и передать эти лямбды напрямую, а также избежать рекомпозиции. Однако это не так. Удаление лямбда-выражений с включенным режимом сильного пропуска приводит к следующим результатам рекомпозиции:
Кнопки теперь перестраиваются каждый раз при изменении счетчика. Это код экрана, который я использовал, напрямую передавая лямбды кнопкам:
@Composable
private fun CounterContent(state: CounterState) {
Scaffold(content = { paddingValues ->
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Column(
modifier = Modifier.padding(paddingValues),
horizontalAlignment = Alignment.CenterHorizontally,
) {
TextButton(onClick = { state.eventSink(CounterEvent.Increment) }) {
Text("Increment")
}
Text("Count: ${state.count}")
Text(state.message)
TextButton(onClick = { state.eventSink(CounterEvent.Decrement) }) {
Text("Decrement")
}
}
}
})
}
Ссылка на проект: https://github.com/itsandreramon/mvp-compose
Судя по всему, это известная проблема с шаблоном Circuit, то есть объединением событий с состоянием. При включении режима сильного пропуска мне удалось избежать вызовов запоминания, при этом пропуская перестановку кнопок, используя этот простой трюк благодаря Саурабу Арора:
@Composable
private fun CounterContent(state: CounterState) {
val eventSink = state.eventSink // this works for some reason
TextButton(onClick = { eventSink(CounterEvent.Increment) }) {
Text("Increment")
}
}