In Compose how to access drawable once Coil loads image from URL

Viewed 2318

I am using accompanist-coil:0.12.0. I want to load image from a url and then pass the drawable to a method. I am using this:

val painter = rememberCoilPainter(
        request = ImageRequest.Builder(LocalContext.current)
            .data(imageUrl)
            .target {
                viewModel.calcDominantColor(it) { color ->
                    dominantColor = color
                }
            }
            .build(),
        fadeIn = true
    )

and then passing the painter to Image like this:

Image(
   painter = painter,
   contentDescription = "Some Image",
)

The image loads without any problem but the method calcDominantColor is never called.
Am I doing it the wrong way?

UPDATE:
I was able to call the method using Transformation in requestBuilder but I am not sure, if this is how it is supposed to be done because I am not actually transforming the Bitmap itself:

val painter = rememberCoilPainter(
        request = entry.imageUrl,
        requestBuilder = {
            transformations(
                object: Transformation{
                    override fun key(): String {
                        return entry.imageUrl
                    }
                    override suspend fun transform(
                        pool: BitmapPool,
                        input: Bitmap,
                        size: Size
                    ): Bitmap {
                        viewModel.calcDominantColor(input) { color ->
                            dominantColor = color
                        }
                        return input
                    }
                }
            )
        }
    )

This works fine for first time but when the composable recomposes, transformation is returned from cache and my method doesn't run.

3 Answers

I think you want to use LaunchedEffect along with an ImageLoader to access the bitmap from the loader result.

    val context = LocalContext.current

    val imageLoader = ImageLoader(context)

    val request = ImageRequest.Builder(context)
        .transformations(RoundedCornersTransformation(12.dp.value))
        .data(imageUrl)
        .build()

    val imagePainter = rememberCoilPainter(
        request = request,
        imageLoader = imageLoader
    )

    LaunchedEffect(key1 = imagePainter) {
        launch {
            val result = (imageLoader.execute(request) as SuccessResult).drawable
            val bitmap = (result as BitmapDrawable).bitmap
            val vibrant = Palette.from(bitmap)
                .generate()
                .getVibrantColor(defaultColor)
            // do something with vibrant color
        }
    }

I would suggest using the new coil-compose library. Just copy the following and add it to the app build.gradle file:

implementation "io.coil-kt:coil-compose:1.4.0"

I was also following the tutorial and got stuck at this point. I would suggest copying and pasting the following code:

Column {
            val painter = rememberImagePainter(
                data = entry.imageUrl
            )
            val painterState = painter.state
            Image(
                painter = painter,
                contentDescription = entry.pokemonName,
                modifier = Modifier
                    .size(120.dp)
                    .align(CenterHorizontally),
            )
            if (painterState is ImagePainter.State.Loading) {
                CircularProgressIndicator(
                    color = MaterialTheme.colors.primary,
                    modifier = Modifier
                        .scale(0.5f)
                        .align(CenterHorizontally)
                )
            }
            else if (painterState is ImagePainter.State.Success) {
                LaunchedEffect(key1 = painter) {
                    launch {
                        val image = painter.imageLoader.execute(painter.request).drawable
                        viewModel.calcDominantColor(image!!) {
                            dominantColor = it
                        }
                    }
                }
            }
            Text(
                text = entry.pokemonName,
                fontFamily = RobotoCondensed,
                textAlign = TextAlign.Center,
                modifier = Modifier.fillMaxWidth()
            )
        }

Replace your "AsyncImage" with "AsyncImageWithDrawable" :

@Composable
fun AsyncImageWithDrawable(
    model: Any?,
    contentDescription: String?,
    modifier: Modifier = Modifier,
    placeholderResId: Int? = null,
    errorResId: Int? = null,
    fallbackResId: Int? = errorResId,
    contentScale: ContentScale,
    onDrawableLoad: (Drawable?) -> Unit) {

    val painter = rememberAsyncImagePainter(
        ImageRequest.Builder(LocalContext.current).data(data = model)
            .apply(block = fun ImageRequest.Builder.() {
                crossfade(true)
                placeholderResId?.let { placeholder(it) }
                errorResId?.let { error(it) }
                fallbackResId?.let { fallback(it) }
                allowHardware(false)
            }).build()
    )

    val state = painter.state

    Image(
        painter = painter,
        contentDescription = contentDescription,
        modifier = modifier,
        contentScale = contentScale
 )

    when (state) {
        is AsyncImagePainter.State.Success -> {
            LaunchedEffect(key1 = painter) {
                launch {
                    val drawable: Drawable? = 
            painter.imageLoader.execute(painter.request).drawable
            onDrawableLoad(drawable)
                }
            }
        }
        else -> {}
    }
}

I've created this Composable inspired by @Aknk answer and Coil source code.

Hint: You can use this Composable to Load and Render your Image from Url and get your Image Palette by the returned Drawable.

Related