coil
coil copied to clipboard
Reload image after a failed load in Jetpack Compose
Is your feature request related to a problem? Please describe.
val painter = rememberImagePainter(url)
Image(
modifier = Modifier.fillMaxSize(),
painter = painter,
contentDescription = null,
contentScale = ContentScale.Fit
)
when (val state = painter.state) {
is ImagePainter.State.Error -> {
TextButton(onClick = { }) { Text("retry") }
}
}
After a failed load, the user should be able to reload the image via the retry button.
Describe the solution you'd like
It is better to provide reload
method, but it seems that setting request
in ImagePainter
to public would also work.
Still figuring out a good public API for this, but if you need this today you can force retry by changing a parameter:
var retryHash by remember { mutableStateOf(0) }
val painter = rememberAsyncImagePainter(
model = ImageRequest.Builder(LocalContext.current)
.data(url)
.setParameter("retry_hash", retryHash)
.build()
)
Image(
painter = painter,
contentDescription = null,
contentScale = ContentScale.Fit,
modifier = Modifier.fillMaxSize(),
)
when (val state = painter.state) {
is AsyncImagePainter.State.Error -> {
TextButton(onClick = { retryHash++ }) { Text("retry") }
}
}
Currently, I'm thinking the public API should be something like this, but let me know what you think!
val requestHandle = rememberAsyncImageRequestHandle()
val painter = rememberAsyncImagePainter(
request = ImageRequest.Builder(LocalContext.current)
.data(url)
.requestHandle(requestHandle)
.build()
)
Image(
painter = painter,
contentDescription = null,
contentScale = ContentScale.Fit,
modifier = Modifier.fillMaxSize(),
)
when (val state = painter.state) {
is AsyncImagePainter.State.Error -> {
TextButton(onClick = { requestHandle.restart() }) { Text("retry") }
}
}
Thanks! I'll try it later.
The api is indeed a problem. Your example is very similar to focusRequester
. I'm ok with it. But since there must be an ImagePainter object here, I think it would be simpler to have an ImagePainter with a retry method.
Yep, adding a method to ImagePainter
might be ok (and more discoverable), though I think it might not work well with the new AsyncImage
component that'll be added in Coil 2.0 since the ImagePainter
is only exposed in AsyncImageScope
. Having the retry handler as part of the request works for both ImagePainter
and AsyncImage
.
I see. In that case, I would prefer code like this:
AsyncImage(...) { state ->
if (state is AsyncImagePainter.State.Error) {
TextButton(onClick = { painter.retry() }) { Text("retry") }
} else {
AsyncImageContent()
}
}
I don't think the retry function should be used outside of AsyncImageScope
. But some global components like BottomSheet
do cause this situation (I personally take it as a design mistake). In that case, a simple lambda should be enough:
val retryHandle = { painter.retry() }
If someone needs more complex control logic (like refreshing multiple images at once), he can use a Flow<RefreshEvent>
and collect it inside AsyncImageScope
.
Anyway, I prefer the direct api, mainly because focusRequester
messed up my code.
A retry handle would be desirable. @colinrtwhite - Is this feature something you see in a near future release?
No immediate plans for adding this (currently focused on making sure AsyncImage
is solid), though it'll likely be in the final 2.0 release or 2.1.
Are there any updates on this issue, or a workaround?
would be nice to add a support for this
is this implemented and exposed to the API? if not, is there any estimation on when it will be available?
Only works for me if .setParameter("retry_hash", retryHash, memoryCacheKey = null)
in @colinrtwhite 's solution is used without the memoryCacheKey
argument, thus .setParameter("retry_hash", retryHash)
.
Also if retryHash
is a Boolean, the model toggles between its first two instances, so it does need to have a range.
I am getting this Error when i add an image URI with an "i".
Heres the Code: AsyncImage( rememberAsyncImagePainter(my_image_uri), contentDescription = "image")
Getting this Runtime Error if this helps:
ava.lang.NoClassDefFoundError: Failed resolution of: Landroidx/compose/runtime/PrimitiveSnapshotStateKt;
at coil.compose.AsyncImagePainter.
Any updates? 👀
Hi folks, quick update on this. This is definitely something I want to address properly in Coil 3.0. Ideally, we can kill two birds with one stone and hoist AsyncImagePainter
so that way users can also observe its other properties (like state
or request
). For example:
val painter = rememberAsyncImagePainter(
model = "https://example.com/image.jpg"
)
AsyncImage(
model = painter,
contentDescription = null,
)
when (painter.state) {
is AsyncImagePainter.State.Error -> {
ErrorButton(onClick = { painter.restart() })
}
}
There are some implementation details that might cause this to not work in practice, but it's the API I'd like!
In the meantime for 2.x I'd continue to use this solution, which isn't ideal, but should force the request to restart.
Related to this I'd love to have the ability to disable "autostarting" the request, I recently had very slow connections speeds and it would have been nice to not automatically trigger some image loads.
I assume I could wrap those images and instantly fail them with an interceptor but I think it would be even nicer UX to be able to differentiate the initial request from retries. Thanks for all the work so far!
Related to this I'd love to have the ability to disable "autostarting" the request, I recently had very slow connections speeds and it would have been nice to not automatically trigger some image loads.
I assume I could wrap those images and instantly fail them with an interceptor but I think it would be even nicer UX to be able to differentiate the initial request from retries. Thanks for all the work so far!
This is not related and you are overthinking this. You can just hide your painter and async image before the user manually clicks it.