1

I am facing a weird problem when trying to load an image from a URL, when I try the following :

    val painter1 = rememberAsyncImagePainter(
                       model = targetUrl,
                       onSuccess = {
                           Log.i("MYTAG", "BasicContentCard: Success!")
                       }
                   )

    Log.i("MYTAG", "BasicContentCard: ${painter1.state}")

    Image(
        painter = painter1,
        contentDescription = null,
        modifier = Modifier.fillMaxSize()
    )

The images load correctly and I even get the success logs for the painter state. The problem emerges when I try to put the same code behind some condition (So that I can show some placeholder composable until the image is fully loaded):

    if (painter1.state is AsyncImagePainter.State.Success){
        Image(
            painter = painter1,
            contentDescription = null,
            modifier = Modifier.fillMaxSize()
        )
    } else {
        PlaceHolderComposable()
    }

The image never loads and even the debug logs indicate that the state of the painter is Loading :

MYTAG  com.rahul.samplelauncher   BasicContentCard: Loading(painter=null)

How do I get around this problem, I want to show a different composable while the image loads.

4 Answers 4

4

Loading an image with Coil is straightforward. Use SubcomposeAsyncImage from Coil, which handles everything for you. It provides callbacks like loading, success, and error for better control.

@Composable
fun LoadImage(
    model: String,
    modifier: Modifier = Modifier
) {
    SubcomposeAsyncImage(
        modifier = modifier,
        model = model,
        loading = {
            Box(modifier = Modifier.shimmerLoading())
        },
        contentDescription = null,
    )
}

To use this function, simply call it and specify your desired size.

LoadImage(
    model = uiState.image,
    modifier = Modifier.size(400.dp, 200.dp)
)

enter image description here

By default, the loading block matches the image size, which works well for shimmer effects. However, if you prefer to display a CircularProgressIndicator during loading, you can use requiredSize to customize the size to your preference.

@Composable
fun LoadImage(
    model: String,
    modifier: Modifier = Modifier
) {
    SubcomposeAsyncImage(
        modifier = modifier,
        model = model,
        loading = {
            CircularProgressIndicator(modifier = Modifier.requiredSize(40.dp))
        },
        contentDescription = null,
    )
}

enter image description here

See coil documentation for more.

Sign up to request clarification or add additional context in comments.

Comments

2

You can use the AsyncImage composable function:

@Composable
@NonRestartableComposable
fun AsyncImage(
    model: Any?,
    contentDescription: String?,
    modifier: Modifier = Modifier,
    placeholder: Painter? = null,
    error: Painter? = null,
    fallback: Painter? = error,
    onLoading: ((State.Loading) -> Unit)? = null,
    onSuccess: ((State.Success) -> Unit)? = null,
    onError: ((State.Error) -> Unit)? = null,
    alignment: Alignment = Alignment.Center,
    contentScale: ContentScale = ContentScale.Fit,
    alpha: Float = DefaultAlpha,
    colorFilter: ColorFilter? = null,
    filterQuality: FilterQuality = DefaultFilterQuality,
)

You can specify your URL assign it to the model parameter, and provide different painters for placeholder and error or fallback.

Another way is to use a state that you can update in the callback onSuccess.

var imageLoaded by rememberSaveable { mutableStateOf(false) }
val painter1 = rememberAsyncImagePainter(
   model = targetUrl,
   onSuccess = {
      imageLoaded = true
   }
)
if (imageLoaded){
   Image(
      painter = painter1,
      contentDescription = null,
      modifier = Modifier.fillMaxSize()
   )
} else {
   PlaceHolderComposable()
}

2 Comments

Hi Luca, can I place a composable in the placeholder block of the AsyncImage() composable?
No, it accepts a painter, which can be either one of: AsyncImagePainter, BitmapPainter, BrushPainter, ColorPainter, CrossfadePainter or VectorPainter. If you need a custom composable use the second approach, with the state to check whether if the image is loaded or not.
1

The issue you're facing seems to be related to the recomposition of your Composable when the state of the AsyncImagePainter changes. When you put the image loading code behind a condition, it might not trigger a recomposition correctly.

You should use AsyncImage.

AsyncImage(
    model = ImageRequest.Builder(LocalContext.current)
        .data("https://example.com/image.jpg")
        .build(),
    placeholder = painterResource(R.drawable.placeholder),
    contentDescription = stringResource(R.string.description),
    contentScale = ContentScale.Crop,
    modifier = Modifier.clip(CircleShape)
)

It has nice support for setting placeholder and error painters.

Solution 2: If you want to show a custom placeholder composable

To show a placeholder while the image is loading using Jetpack Compose, you can use the produceState. It can convert a noncomposable state to a composable state.

@Composable
fun BasicContentCard(targetUrl: String) {
    val painter = rememberAsyncImagePainter(
        model = targetUrl,
        onSuccess = {
            Log.i("MYTAG", "BasicContentCard: Success!")
        },
        onError = {
            Log.i("MYTAG", "BasicContentCard: Error!")
        }
    )

    // Use produceState to monitor the painter's state
    val painterState by produceState<AsyncImagePainter.State>(initialValue = painter.state) {
        painter.state // Trigger recomposition whenever painter's state changes
    }

    Log.i("MYTAG", "BasicContentCard: ${painterState}")

    if (painterState is AsyncImagePainter.State.Success) {
        Image(
            painter = painter,
            contentDescription = null,
            modifier = Modifier.fillMaxSize()
        )
    } else {
        PlaceHolderComposable()
    }
}

@Composable
fun PlaceHolderComposable() {
    // Your placeholder implementation
}

2 Comments

Hi Waseem, the main requirement Ii have is that I want to show a separate composable PlaceHolderComposable() while the image loads. Do you have any suggestions as to how to accomplish this?
Hi Rahul, I have updated the answer. Please check the Solution 2. I hope it helps you
-1

This is how I'm doing it.

    SubcomposeAsyncImage(
        model = url,
        contentDescription = "Deity Image",
        contentScale = ContentScale.Crop,
        modifier = Modifier.fillMaxSize(),
        loading = {
            CircularProgressIndicator(
                color = Color.Gray,
                modifier = Modifier.size(48.dp)
            )
        }
    )

1 Comment

As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.