I have a `LazyColumn` with `AndroidView` items and...
# compose
s
I have a
LazyColumn
with
AndroidView
items and I’m just noticing that
AndroidView#update
is getting called on every scroll/recomposition. Is this expected?
1
t
Make sure you use
item(key) { ... }
or
item { key(...) { ... } }
to avoid unnecessary recompositions.
a
no, it is not. can you show your code?
s
@tad thanks, I’m already using a unique key for each row @Andrey Kulikov I think I know why it’s happening. We have a background that is scrolled with the list. I am doing this by calculating the scroll offset from my
LazyListState
. Because the background is synced with scroll, it’s causing recompositions on every scroll. What I don’t understand is why would my LazyColumn items also get invalidated if they aren’t reading this state. Here’s a gist of what my code looks like:
Copy code
@Composable
fun Content() {
  val listState = rememberLazyListState()
  val scrollOffsetPx = listState.rememberScrollOffsetPx()

  GradientBackground(scrollOffsetPx)

  LazyColumn(state = listState) {
    Row(...)
    Row(...)
    Row(...)
  }
}

fun <V : View, M> LazyListScope.Row(
  key: InvestmentEntityViewType, // Some enum
  modifier: Modifier = Modifier,
  model: M?,
  create: () -> V,
  render: V.(M) -> Unit = {}
) {
  item(key) {
    if (model != null) {
      AndroidView(
        modifier = modifier,
        factory = { create() },
        update = { view -> view.render(model) }
      )
    }
  }
}
a
when your Content recomposed you also provide a new lambda as a content for LazyColumn which means that you have new lambda for each item so it has to recompose. you need to try to better localize the recomposition so it only affects GradientBackground, not the whole Content. or even better is to avoid recompositions at all and only do redrawing on each scroll if possible
s
sorry I don’t follow. Can you share some code to show how this
Content
can be changed to localize the recomposition?
a
for example you can make your GradientBackground to accept LazyListState, not resolved px and do offset calculation in there. this will make it to only recompose GradientBackground when the offset changes
s
I see. I’m able to understand that in my specific example, but how would I do it if I can’t extract the state into my background composable? here’s the same example but with nested scrolling:
Copy code
@Composable
fun Content() {
  val scrollConnection = remember {
    object : NestedScrollConnection {
      var scrollOffset by mutableStateOf(0f)
      override fun onPostScroll(consumed: Offset, available: Offset, source: NestedScrollSource): Offset {
        scrollOffset += consumed.y
        return super.onPostScroll(consumed, available, source)
      }
    }
  }

  GradientBackground(yOffsetPx = scrollConnection.scrollOffset.roundToInt())

  LazyColumn(modifier = Modifier.nestedScroll(scrollConnection)) {
    ...
  }
}
I think I may have figured this out: 1. Hoist an “observable” NestedScrollConnection 2. Pass the connection to my
GradientDrawable
so that it can track scrolled pixels
That said, as someone who’s new this doesn’t feel natural yet. “Why is reading the scroll state of my list causing all my items to recompose?”
a
it is not really about trying to never recompose your items. that is fine on its own. the main idea of this is to try to do less work if possible. if you have to recompose often try to minimize the scope which will be recomposed. you can extract that logic which needs recomposition into a separate function so only it restarts. even more efficient is to try to avoid recomposition at all and only do relayout or remeasure when something changes. because of that we have Modifier.offset which accepts lambda if you need to do an offset, or Modifier.graphicsLayer which accepts lambda if you need a visual transformations. lambdas there make sure that we can only rerun this small lambda on the change