Sagar Khurana
Sagar Khurana

Reputation: 334

Recomposition of all items on using List<Interface> with LazyColumn

On using a List<Interface> with the LazyColumn recomposes all the items, instead if I use List<Implementation> it skips the recompositions intelligently.

For instance, if I use example 1 MutableList in composable recompostition happens for all items on addition operation to list, but in example 2 it skips intelligently. WHY?

Example 1:

    val selectionState: MutableList<ViewItemScratch> = remember {
      mutableStateListOf()
    }

Example 2:

    val selectionState: MutableList<TitleViewItemScratch> = remember {
      mutableStateListOf()
    }

Interface:

interface ViewItemScratch {
  val identifier: Int
  val layoutId: Int

  @SuppressLint("NotConstructor")
  @Composable
  fun ViewItem(
    lazyItemScope: LazyItemScope? = null,
    rowScope: RowScope? = null,
    columnScope: ColumnScope? = null
  ) {
  }
}

Implementation Class:

data class TitleViewItemScratch(
  val index: Int,
  val text: StringResolver,
  val modifier: Modifier = Modifier,
  override val identifier: Int = text.hashCode()
) : ViewItemScratch {
  override val layoutId: Int
    get() = R.id.compose_view_title_view_item

  @Composable
  override fun ViewItem(
    lazyItemScope: LazyItemScope?, rowScope: RowScope?, columnScope: ColumnScope?
  ) {
    Text(
      modifier = modifier
        .fillMaxWidth()
        .wrapContentHeight()
        .padding(horizontal = 16.dp),
      text = text.resolve(),
    )
  }
}

Composable:

 @Composable
  private fun WithViewItemScratch() {
    val selectionState: MutableList<ViewItemScratch> = remember {
      mutableStateListOf()
    }
    val onButtonClick = remember {
      {
        selectionState.add(
          TitleViewItemScratch(
            index = selectionState.size + 1,
            text = UIString("Hello world #${selectionState.size + 1}"),
            identifier = selectionState.size + 1,
          )
        )
      }
    }
    Column {
      Button(onClick = {
        onButtonClick.invoke()
      }) {
        Text(text = "Add")
      }
      LazyColumn {
        items(
          items = selectionState,
          key = { viewItem: ViewItemScratch -> viewItem.identifier }
        ) { item ->
          item.ViewItem()
        }
      }
    }
  }

Compiler report(s):

restartable skippable fun ViewItem(
  unused stable lazyItemScope: LazyItemScope? = @static null
  unused stable rowScope: RowScope? = @static null
  unused stable columnScope: ColumnScope? = @static null
  unused <this>: ViewItemScratch
)
stable class TitleViewItemScratch {
  stable val index: Int
  stable val text: StringResolver
  stable val color: CompassColor
  stable val typography: CompassTypography
  stable val modifier: Modifier
  stable val identifier: Int
  <runtime stability> = Stable
}

Layout Inspector recompositions:

With List of Interface: enter image description here

With List of Implementation/Concrete class:

enter image description here

Upvotes: 1

Views: 672

Answers (1)

Shreyas Patil
Shreyas Patil

Reputation: 954

Extracting the Jetpack compose compiler report/metrics shows that the interface's stability is missing. Because the interface doesn't have logic but tells about the abstraction it brings. So if the interface doesn't explicitly promise the compose stability by applying @Stable or @Immutable, it's treated as unstable only.

The implementation is considered stable by default because it's a data class (which comes with equals() and hashCode() implementation) with all properties as val (immutable).

Upvotes: 3

Related Questions