Компонуемая рекомпозиция даже при включенном режиме сильного пропуска

Я протестировал это, используя 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

0
0
106
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Судя по всему, это известная проблема с шаблоном Circuit, то есть объединением событий с состоянием. При включении режима сильного пропуска мне удалось избежать вызовов запоминания, при этом пропуская перестановку кнопок, используя этот простой трюк благодаря Саурабу Арора:

@Composable
private fun CounterContent(state: CounterState) {
  val eventSink = state.eventSink // this works for some reason
  TextButton(onClick = { eventSink(CounterEvent.Increment) }) {
    Text("Increment")
  }
}

Другие вопросы по теме