skydoves / landscapist

🌻 A pluggable, highly optimized Jetpack Compose and Kotlin Multiplatform image loading library that fetches and displays network images with Glide, Coil, and Fresco.
https://skydoves.github.io/landscapist/
Apache License 2.0
2.01k stars 112 forks source link

Asking for intrinsic measurements of SubcomposeLayout layouts is not supported. #482

Closed KwonDae closed 2 months ago

KwonDae commented 2 months ago

Please complete the following information:

Describe the Bug:

Scaffold {
   Column {
      TabRow {
         Tab {
            LazyColumn {
                GlideImage
            ...

I'm using as above structure. When I use GlideImage inside TabRow, the following error occurs,

java.lang.IllegalStateException: Asking for intrinsic measurements of SubcomposeLayout layouts is not supported. 
This includes components that are built on top of SubcomposeLayout, such as lazy lists, BoxWithConstraints, TabRow, etc. To mitigate this:

- if intrinsic measurements are used to achieve 'match parent' sizing, 
consider replacing the parent of the component with a custom layout which controls the order in which children are measured, 
making intrinsic measurement not needed

- adding a size modifier to the component, in order to fast return the queried intrinsic measurement.

It is said that it does not support intrinsic measurements behavior inside components such as TabRow using SubcomposeLayout.

@Composable
fun TabRow(
    selectedTabIndex: Int,
    modifier: Modifier = Modifier,
    containerColor: Color = TabRowDefaults.primaryContainerColor,
    contentColor: Color = TabRowDefaults.primaryContentColor,
    indicator: @Composable (tabPositions: List<TabPosition>) -> Unit = @Composable { tabPositions ->
        if (selectedTabIndex < tabPositions.size) {
            TabRowDefaults.SecondaryIndicator(
                Modifier.tabIndicatorOffset(tabPositions[selectedTabIndex])
            )
        }
    },
    divider: @Composable () -> Unit = @Composable {
        HorizontalDivider()
    },
    tabs: @Composable () -> Unit
) {
    TabRowWithSubcomposeImpl(modifier, containerColor, contentColor, indicator, divider, tabs)
}

Is it possible that the contents of the ImageLoad are using BoxWithConstraintsScope internally in the process of calling the ImageLoad function in private fun GlideImage?

@Composable
public fun <T : Any> ImageLoad(
  recomposeKey: T?,
  executeImageRequest: suspend () -> Flow<ImageLoadState>,
  modifier: Modifier = Modifier,
  imageOptions: ImageOptions,
  constrainable: Constrainable? = null,
  content: @Composable BoxWithConstraintsScope.(imageState: ImageLoadState) -> Unit,
) {
image
skydoves commented 2 months ago

Hey @KwonDae, I believe you'll be able to resolve this issue by wrapping your GlideImage with a Box Composable, as demonstrated in the code below:

Box(modifier = Modifier.size(56.dp)) {
          GlideImage(..)
}
KwonDae commented 2 months ago

Thanks @skydoves Should I use fixed size in parent composable of GlideImage?

    FlowRow(
        modifier = modifier.fillMaxWidth(),
        maxItemsInEachRow = 2,
    ) {
        items.forEach {
            ImageItem(
                item = it,
                modifier = Modifier.weight(0.5f)
            )
        }
    }

I use GlideImage within Modifier.weight

skydoves commented 2 months ago

Hi @KwonDae, you actually don't need to use FlowRow if you want to maintain the same width for each item as half the size. Instead, you can utilize LazyVerticalGrid.

LazyVerticalGrid(
      modifier = Modifier.fillMaxWidth(),
      columns = GridCells.Fixed(2)
) {
KwonDae commented 2 months ago

Hi @skydoves, Thanks to you, I was able to solve the Issue. I'm more curious to see how coil render image without that issue in FlowRow. I find AsyncImage using SizeResolver with ConstraintSizeResolver. Has it something to do with above issue?

GlideImage render content within BoxWithConstraintsScope, AsyncImage use Layout with ConstraintsSizeResolver?

skydoves commented 2 months ago

Hey @KwonDae, Landscapist uses SizeResolver to request pre-computed image sizes before rendering. The reason for adopting BoxWithConstraints is to limit content sizes and support match-parent functionality. This is achieved by leveraging the properties of SubComposeLayout, which delegates incoming parent constraints, making it easier to implement match-parent features.

On the other hand, Landscapist offers other image libraries, such as Glide and Fresco, but they don't yet provide features similar to SizeResolver. Therefore, I need to find a better way to support all of them. This is important because BoxWithConstraints incurs slightly higher overhead compared to a regular Box.

KwonDae commented 2 months ago

Thank you for the answers. It was very helpful, I got a lot out of it :)