Coil rememberAsyncImagePainter states are not updated
Asked Answered
A

3

9

I use Coil along with Compose.

And I'm trying to make a shimmer animation while the image is loading.
All examples use ImagePainter with ImagePainter.State and it works correctly, but this is now marked as "deprecated".
That's why I chose AsyncImagePainter. Without the state check it works perfect, but with the check I get an infinite shimmer animation.
I also tried to change the load state var with mutableState in the onSuccess method in AsyncImagePainter, but the animation is still infinite

@Composable
fun FoodItem(food: Fun) {
    Column (
        modifier = Modifier.fillMaxWidth(),
        horizontalAlignment = Alignment.Start,
        verticalArrangement = Arrangement.Top
    ) {
        val painter = rememberAsyncImagePainter(food.image.sm)
        if (painter.state is AsyncImagePainter.State.Loading) {
            AnimatedShimmer { ShimmerFoodItem(brush = it) }
        } else {
            Image(
                modifier = Modifier
                    .fillMaxWidth()
                    .height(104.dp)
                    .clip(RoundedCornerShape(size = 8.dp)),
                painter = painter,
                contentScale = ContentScale.Crop,
                contentDescription = "Food photo"
            )
            Text(
                modifier = Modifier.padding(top = 4.dp),
                text = food.title,
                fontSize = MaterialTheme.typography.body1.fontSize,
                overflow = TextOverflow.Ellipsis,
                maxLines = 1,
                fontWeight = FontWeight.Bold
            )
            Text(
                text = food.subtitle,
                fontSize = MaterialTheme.typography.subtitle2.fontSize,
                overflow = TextOverflow.Ellipsis,
                maxLines = 2,
                fontWeight = FontWeight.Normal
            )
        }
    }
}
Anfractuous answered 10/10, 2022 at 0:6 Comment(0)
L
12

You need to provide size using

model = ImageRequest.Builder(LocalContext.current)
    .data(overlayImage)
    .size(coil.size.Size.ORIGINAL) // Set the target size to load the image at.
    .build()

If you comment the line size is you will see that it gets stuck at loading state. Also you can provide any dimensions you want image to be loaded for instance 1280x720 with .size(coil.size.Size(1280, 720))

@Composable
private fun CoilSample() {
    val overlayImage =
        "https://images6.alphacoders.com/488/thumb-1920-488158.jpg"

    val painter = rememberAsyncImagePainter(
        model = ImageRequest.Builder(LocalContext.current)
            .data(overlayImage)
            .size(coil.size.Size.ORIGINAL) // Set the target size to load the image at.
            .build()
    )

    Column(modifier = Modifier.fillMaxSize()) {

        Text("state:${painter.state}")

        Box(
            modifier = Modifier.fillMaxSize(),
            contentAlignment = Alignment.Center
        ) {
            if (painter.state is AsyncImagePainter.State.Loading) {
                CircularProgressIndicator()
            } else {
                Image(
                    modifier = Modifier
                        .fillMaxWidth()
                        .height(104.dp)
                        .clip(RoundedCornerShape(size = 8.dp)),
                    painter = painter,
                    contentScale = ContentScale.Crop,
                    contentDescription = "photo"
                )
            }
        }
    }
}

You can also check out this answer as well if you need to get Bitmap or ImageBitmap from painter of Coil

Get bitmap before using in a Composable

Lucent answered 6/12, 2022 at 16:1 Comment(0)
L
0

Here is how I did it, including showing a holder icon when the state has an error, for example, if the image URL is invalid. The painter is supplied into the Composable immediately, but I added another state showImage that is initially true and can change whenever the ImageLoadingState Changes. You can wrap this whole code inside a function that accepts the URL parameter and will only show one Composable depending on the State of ShowImage/Loading/Error:

val modifier = Modifier
            .clip(RoundedCornerShape(CornerSize(10.dp)))
            .padding(4.dp)

val url = "your_image_url_or_not"

val imagePainter = rememberAsyncImagePainter(
    model = ImageRequest.Builder(LocalContext.current)
      .data(url)
      .crossfade(true)
      .build(),
  )

val showImage = remember {
    mutableStateOf(true)
  }
 if (showImage.value) {
    Image(
      painter = imagePainter,
      contentDescription = description,
      contentScale = ContentScale.Fit,
      modifier = modifier.padding(3.dp),
    )
  }

  if (imagePainter.state is AsyncImagePainter.State.Loading) {
    showImage.value = false
    CircularProgressIndicator(
      modifier = modifier,
      strokeWidth = 2.dp,
      color = MaterialTheme.colors.primaryVariant
    )
  } else if (imagePainter.state is AsyncImagePainter.State.Error) {
    showImage.value = false
    Icon(
      imageVector = Icons.Default.Person,
      contentDescription = description,
      modifier = modifier,
      tint = MaterialTheme.colors.primary
    )
  } else {
    showImage.value = true
  }
Limp answered 6/12, 2022 at 11:6 Comment(0)
H
-2

Looks like the painter needs to be supplied into an Image composable right away so its state could change, I tried implementing your use-case, and logged the painter.state it wouldn't get updated unless its supplied immediately on an Image, so I ended up with this

// random public image
val painter = rememberAsyncImagePainter(
    model = ImageRequest.Builder(LocalContext.current)
        .data("https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQUJ0jVyT2UJwn_Ln5s4yd17MtwLlgzm8-WTGKExqZg&s")
        .build()
)

Box {

    Image(
        modifier = Modifier.size(150.dp),
        painter = painter,
        contentDescription = ""
    )

    runBlocking {
        delay(500)
    }

    if (painter.state is AsyncImagePainter.State.Loading) {
        Box(
            modifier = Modifier
                .size(150.dp)
                .background(Color.Red)
        )
    }
}

Based on the Coil Samples

Please don't mind the runbBlocking and the red Box, I just put them there to mimic something on top of the Image (shimmering) and to delay the showing of the actual Image(fetching). Hope this helps.

Hispaniola answered 10/10, 2022 at 3:57 Comment(1)
Thanks, this helped solve my problem. But now the images are loading so fast that the shimmer of the item doesn't even have time to draw :)Anfractuous

© 2022 - 2024 — McMap. All rights reserved.