9

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()
)
Phil Dukhov
  • 67,741
  • 15
  • 184
  • 220
Raw Hasan
  • 1,096
  • 1
  • 9
  • 25

3 Answers3

16

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,
    ...
)
Phil Dukhov
  • 67,741
  • 15
  • 184
  • 220
  • Thanks! Loved the clean code!! However, it is not providing the expected behavior, although I can't understand why this should not work! :( – Raw Hasan Aug 10 '21 at 13:56
  • 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! – Raw Hasan Aug 10 '21 at 14:01
  • 1
    @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 point – Phil Dukhov Aug 10 '21 at 14:02
  • 1
    @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 described – Phil Dukhov Aug 10 '21 at 14:06
  • 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? https://github.com/rawhasan/compose-exercises-image-source – Raw Hasan Aug 10 '21 at 14:23
  • 1
    @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 models](https://developer.android.com/jetpack/compose/state#viewmodel-state) – Phil Dukhov Aug 10 '21 at 14:41
  • Thanks, @Philip! I am just temporarily using the global variables. Moving to a view model finally is the plan. – Raw Hasan Aug 10 '21 at 14:53
  • `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? – Raw Hasan Aug 10 '21 at 14:57
  • 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? – Raw Hasan Aug 10 '21 at 14:58
  • @Hasan no, there's no way. that's why I suggested you a solution with local mutable state value – Phil Dukhov Aug 10 '21 at 15:33
  • 1
    @Hasan you have error here: https://github.com/rawhasan/compose-exercises-image-source/blob/9e99c6228d8b2fc820431617eeafe193cb42da90/app/src/main/java/com/example/imagesource/MainActivity.kt#L150, you should pass `currentlySelectedImageUri.value` to `previousImageUri`, but your're passing new uri – Phil Dukhov Aug 10 '21 at 15:35
  • Awesome! Working nicely now!! Accepting this as the answer. Thanks a lot! – Raw Hasan Aug 10 '21 at 16:00
3

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)
Narek Hayrapetyan
  • 1,731
  • 15
  • 27
  • Thanks for the answer! Will the `placeholder` take an URL as a parameter? - That is my issue. I am currently using the drawable. – Raw Hasan Aug 10 '21 at 15:28
  • 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 think – Narek Hayrapetyan Aug 10 '21 at 15:52
  • Link for placeholder : https://google.github.io/accompanist/api/placeholder-material3/com.google.accompanist.placeholder.material3/index.html – Đốc.tc Jul 06 '23 at 07:07
3

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)}))

Douaa Su
  • 51
  • 4