3

I need to show a custom placeholder in Jetpack Compose using Coil, but that placeholder is not a drawable, it is a composable function that I customized. Is it possible to do this with the Coil? This is the code snippet where I use the Coil:

Image(
    modifier = Modifier
        .size(120.dp)
        .align(Alignment.CenterHorizontally),
    painter = rememberImagePainter(
        data = entry.imageUrl,
        builder = {
            crossfade(true)
            MyPlaceholder(resourceId = R.drawable.ic_video)
        },
    ),
    contentDescription = entry.pokemonName
)

This is my custom placeholder compose function:

@Composable
fun MyPlaceholder(@DrawableRes resourceId: Int) {
    Surface(
        modifier = Modifier.fillMaxSize(),
        color = Color(0xFFE0E0E0)
    ) {
        Box(
            modifier = Modifier.fillMaxSize(),
            contentAlignment = Alignment.Center,
        ) {
            Surface(
                modifier = Modifier.size(30.dp),
                shape = CircleShape,
                color = Color.White
            ) {
                Image(
                    modifier = Modifier
                        .padding(
                            PaddingValues(
                                start = 11.25.dp,
                                top = 9.25.dp,
                                end = 9.25.dp,
                                bottom = 9.25.dp
                            )
                        )
                        .fillMaxSize(),
                    painter = painterResource(id = resourceId),
                    contentDescription = null
                )
            }
        }
    }
}

My gradle (Coil):

// Coil
implementation 'io.coil-kt:coil-compose:1.4.0'
Phil Dukhov
  • 67,741
  • 15
  • 184
  • 220
Pierre Vieira
  • 2,252
  • 4
  • 21
  • 41

2 Answers2

7

Coil has no built-in support for composable placeholders.

You can put your composable inside Box and display the placeholder over Image depending on the state.

In my example I display it if the state is Loading or Error. You can add another view parameter for the Error case and use Crossfade instead of AnimatedVisibility.

Also I add Modifier.matchParentSize() to the Image to follow parent size calculated on the modifier parameter. You can't pass modifier parameter directly to Image, because modifiers like align only work for direct children, that's why you alway have to pass it to the container view.

@Composable
fun Image(
    painter: ImagePainter,
    placeholder: @Composable () -> Unit,
    contentDescription: String?,
    modifier: Modifier = Modifier,
    alignment: Alignment = Alignment.Center,
    contentScale: ContentScale = ContentScale.Fit,
    alpha: Float = DefaultAlpha,
    colorFilter: ColorFilter? = null,
) {
    Box(modifier) {
        Image(
            painter = painter,
            contentDescription = contentDescription,
            alignment = alignment,
            contentScale = contentScale,
            alpha = alpha,
            colorFilter = colorFilter,
            modifier = Modifier.matchParentSize()
        )

        AnimatedVisibility(
            visible = when (painter.state) {
                is ImagePainter.State.Empty,
                is ImagePainter.State.Success,
                -> false
                is ImagePainter.State.Loading,
                is ImagePainter.State.Error,
                -> true
            }
        ) {
            placeholder()
        }
    }
}

Usage:

Image(
    painter = rememberImagePainter(imageUrl),
    placeholder = {
        CustomComposableView(...)
    },
    contentDescription = "...",
    modifier = Modifier
        ...
)
Phil Dukhov
  • 67,741
  • 15
  • 184
  • 220
0

Since Coil 2.0 there is a SubcomposeAsyncImage to solve your problem. Here is an example of how you can use it:

SubcomposeAsyncImage(
    model = "https://example.com/image.jpg",
    loading = {
        CircularProgressIndicator()
    },
    ...
)

For more information refer to the official documentation.

Vadik Sirekanyan
  • 3,332
  • 1
  • 22
  • 29