How to set an image URL as error placeholder on Coil in Jetpack Compose
Asked Answered
F

3

15

Coil accepts a drawable resource as an error placeholder. Is there a way to use an image URL here instead?

Here is the code I am working on:

// Global variables
var currentlySelectedImageUri = mutableStateOf<Uri?>(null)
var previousImageUri: Uri? = null

// @Composable fun() {...
Image(
    painter = rememberImagePainter(
    if (currentlySelectedImageUri.value != null) { // use the currently selected image
        currentlySelectedImageUri.value
    } else {
        if (previousImageUri != null) { // use the previously selected image
            previousImageUri
        } else {
            R.drawable.blank_profile_picture // use the placeholder image
        }
    }, builder = {
        placeholder(R.drawable.blank_profile_picture)
        error(R.drawable.blank_profile_picture) // FIXME: Set the previously selected image
    }),
    contentDescription = "profile image",
    contentScale = ContentScale.Crop,
    modifier = Modifier.fillMaxWidth()
)
Foreshadow answered 10/8, 2021 at 13:17 Comment(0)
G
28

In Coil 2.0.0 both AsyncImage and rememberAsyncImagePainter have error parameter that takes any other painter:

AsyncImage(
    model = imageURL,
    contentDescription = null,
    error = painterResource(R.drawable.error)
)

Coil 1.4.0 version:

You can check painter.state value.

Initially it's ImagePainter.State.Empty, while image is loading it's ImagePainter.State.Loading, and if it failed - it becomes ImagePainter.State.Error. At this point you can change coil url, as an example, using local remember variable:

val localImagePainterUrl = remember { mutableStateOf<Uri?>(null) }
val painter = rememberImagePainter(
    data = localImagePainterUrl.value
        ?: currentlySelectedImageUri.value
        ?: previousImageUri
        ?: R.drawable.blank_profile_picture,
    builder = {
        placeholder(R.drawable.blank_profile_picture)
    })
val isError = painter.state is ImagePainter.State.Error
LaunchedEffect(isError) {
    if (isError) {
        localImagePainterUrl.value = previousImageUri
    }
}

Image(
    painter = painter,
    ...
)
Goldberg answered 10/8, 2021 at 13:26 Comment(12)
Thanks! Loved the clean code!! However, it is not providing the expected behavior, although I can't understand why this should not work! :(Foreshadow
If I provide a valid URL with a non-existing image, it initially shows the blank image, then disappears. It looks like the whole image view gets disappeared, as if I select a valid image next, nothing shows up!Foreshadow
@Hasan I've checked with local values and it works: put invalid url in currentlySelectedImageUri and valid url in previousImageUri. Check if local values will work in your case so you know that my part works fine. Also you can add print inside LaunchedEffect to check if your request fails, and see which value are you passing to localImagePainterUrl at this pointGoldberg
@Hasan I've tried with valid currentlySelectedImageUri - work fine too. Once thing I haven't noticed is that you need to reset localImagePainterUrl when your change currentlySelectedImageUri, not sure how can I do it if it's a global variable.. But this shouldn't cause the bug you've describedGoldberg
I think something is going wrong here with recomposition, as it is initially showing the blank profile image after providing a non-existing image URL, and changing that afterward. Can you please have a look at the code here? github.com/rawhasan/compose-exercises-image-sourceForeshadow
@Hasan according to your code currentlySelectedImageUri should always have the same value as previousImageUri. So when you pass there invalid image url, it fails to load it and then falls back to the same invalid url. also storing anything in global variables is for sure not the best practice, consider moving to view modelsGoldberg
Thanks, @Philip! I am just temporarily using the global variables. Moving to a view model finally is the plan.Foreshadow
previousImageUri will be different once we provide currentlySelectedImageUri a valid URL. What I need is, showing the image in previousImageUri when the currentlySelectedImageUri has been supplied an invalid URL or null, instead of the blank image according to my original code. Any idea how to achieve that?Foreshadow
My original issue was fixing this line: error(R.drawable.blank_profile_picture). Is there any way to convert an URL to a drawable value?Foreshadow
@Hasan no, there's no way. that's why I suggested you a solution with local mutable state valueGoldberg
@Hasan you have error here: github.com/rawhasan/compose-exercises-image-source/blob/…, you should pass currentlySelectedImageUri.value to previousImageUri, but your're passing new uriGoldberg
Awesome! Working nicely now!! Accepting this as the answer. Thanks a lot!Foreshadow
O
6

There is a function inside coil ImageRequest Builder class

  fun placeholder(@DrawableRes drawableResId: Int) = apply {
      this.placeholderResId = drawableResId
      this.placeholderDrawable = null
  }

Usage:

Image(
        painter = rememberImagePainter(
            data = url,
            builder = {
                placeholder(R.drawable.your_placeholder_drawable) // or bitmap
            }
        )
    )

UPDATE:

Also use: com.google.accompanist.placeholder

Dependency gradle: com.google.accompanist:accompanist-placeholder:accompanist_version

// accompanist_version = 0.19.0

Modifier.placeholder(
        visible =  true/false,
        color = color,
        highlight = PlaceholderHighlight.shimmer(color),
        shape = imageShape // RectangleShape)
Onega answered 10/8, 2021 at 15:20 Comment(3)
Thanks for the answer! Will the placeholder take an URL as a parameter? - That is my issue. I am currently using the drawable.Foreshadow
No there is no such a function like you mentioned, but you can load placeholder image get as drawable and set as placeHolder drawable But what if placeholder can't be loaded? placeholder need to be local I thinkOnega
Link for placeholder : google.github.io/accompanist/api/placeholder-material3/…Pennyweight
R
4

Actually there is an easier way from coil compose api , you can just add error(R.drawable.your_placeholder_drawable) to the builder and that's it

Image(painter = rememberImagePainter(data = url, builder = {error(R.drawable.your_placeholder_drawable)}))

Roam answered 14/12, 2021 at 18:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.