I need to reload/refresh the coil image because it has been rewritten to disk/storage
The code I am using
val context = LocalContext.current
val loader = if (!isSvg) {
LocalImageLoader.current ?: remember { ImageLoader(context) }
} else {
remember {
ImageLoader.Builder(context)
.componentRegistry {
add(SvgDecoder(context))
}.build()
}
}
val painter = rememberCoilPainter(request = request, imageLoader = loader)
LaunchedEffect(key1 = invalidate, block = {
val result = loader.execute(
ImageRequest.Builder(context)
.diskCachePolicy(if (fromCache) CachePolicy.ENABLED else CachePolicy.DISABLED)
.data(request).build()
)
Log.d("TL_ImageLoadRes", result.toString())
})
Box(modifier = Modifier.background(MaterialTheme.colors.onBackground.copy(.4f))) {
Image(
modifier = modifier,
painter = painter,
contentDescription = stringResource(R.string.random_image),
contentScale = contentScale
)
}
Every time invalidate changes , it means the image has been rewritten and now may be found and I execute the LaunchedEffect but after getting the success result I don't know where to put it , it contains a Drawable , I don't know what to do with it
Related
I tried to implement pagination in my app using compose. Here you can find full code : https://github.com/alirezaeiii/TMDb-Compose
First I am showing a loading indicator, and then loads items from TMDb API. So I have following composable method :
#Composable
fun <T : TMDbItem> PagingScreen(
viewModel: BasePagingViewModel<T>,
onClick: (TMDbItem) -> Unit,
) {
val lazyTMDbItems = viewModel.pagingDataFlow.collectAsLazyPagingItems()
when (lazyTMDbItems.loadState.refresh) {
is LoadState.Loading -> {
TMDbProgressBar()
}
is LoadState.Error -> {
val message =
(lazyTMDbItems.loadState.refresh as? LoadState.Error)?.error?.message ?: return
lazyTMDbItems.apply {
ErrorScreen(
message = message,
modifier = Modifier.fillMaxSize(),
refresh = { retry() }
)
}
}
else -> {
LazyTMDbItemGrid(lazyTMDbItems, onClick)
}
}
}
As you see in the else section it calls LazyTMDbItemGrid composable function :
#Composable
private fun <T : TMDbItem> LazyTMDbItemGrid(
lazyTMDbItems: LazyPagingItems<T>,
onClick: (TMDbItem) -> Unit
) {
LazyVerticalGrid(
columns = GridCells.Fixed(COLUMN_COUNT),
contentPadding = PaddingValues(
start = Dimens.GridSpacing,
end = Dimens.GridSpacing,
bottom = WindowInsets.navigationBars.getBottom(LocalDensity.current)
.toDp().dp.plus(
Dimens.GridSpacing
)
),
horizontalArrangement = Arrangement.spacedBy(
Dimens.GridSpacing,
Alignment.CenterHorizontally
),
content = {
items(lazyTMDbItems.itemCount) { index ->
val tmdbItem = lazyTMDbItems[index]
tmdbItem?.let {
TMDbItemContent(
it,
Modifier
.height(320.dp)
.padding(vertical = Dimens.GridSpacing),
onClick
)
}
}
lazyTMDbItems.apply {
when (loadState.append) {
is LoadState.Loading -> {
item(span = span) {
LoadingRow(modifier = Modifier.padding(vertical = Dimens.GridSpacing))
}
}
is LoadState.Error -> {
val message =
(loadState.append as? LoadState.Error)?.error?.message ?: return#apply
item(span = span) {
ErrorScreen(
message = message,
modifier = Modifier.padding(vertical = Dimens.GridSpacing),
refresh = { retry() })
}
}
else -> {}
}
}
})
}
In order to load images in the ImageView asynchronously, I have following function :
#Composable
private fun BoxScope.TMDbItemPoster(posterUrl: String?, tmdbItemName: String) {
val painter = rememberAsyncImagePainter(
model = posterUrl,
error = rememberVectorPainter(Icons.Filled.BrokenImage),
placeholder = rememberVectorPainter(Icons.Default.Movie)
)
val colorFilter = when (painter.state) {
is AsyncImagePainter.State.Loading, is AsyncImagePainter.State.Error -> ColorFilter.tint(
MaterialTheme.colors.imageTint
)
else -> null
}
val scale =
if (painter.state !is AsyncImagePainter.State.Success) ContentScale.Fit else ContentScale.FillBounds
Image(
painter = painter,
colorFilter = colorFilter,
contentDescription = tmdbItemName,
contentScale = scale,
modifier = Modifier
.fillMaxSize()
.align(Alignment.Center)
)
}
The problem is when I am scrolling very fast in the part of screen that images are not loaded yet, it is quite laggy. Based on my research there is no problem with my image loading using coil library, but I do not know why it is laggy. Do you have any suggestion about this?
Addenda :
I have reported the issue here : https://issuetracker.google.com/issues/264847068
You can try to refer to this issue address on github to solve it. At present, Coil is combined with Compose's LazyVerticalGrid and LazyColumn, and the sliding sense is lagging:
https://github.com/coil-kt/coil/issues/1337
I used asyncImage in the project, combined with lazyColumn and Swiperefresh, and called the screen through navigation, but during the test, I found that the memory will increase due to the nagate screen, and after the screen is reverted, the memory through gc will not decrease. Is there a way to clear resources in memory?
val current = LocalContext.current
val imageLoaderM = current.imageLoader
when (data) {
is String -> {
if (data.contains("https://") || data.contains("http://")) {
AsyncImage(
model = ImageRequest.Builder(current).data(data).apply {
diskCacheKey(data)
memoryCacheKey(data)
scale(Scale.FILL)
crossfade(true)
placeholder(drawableResId = placeholder)
error(drawableResId = error)
memoryCachePolicy(CachePolicy.ENABLED)
diskCachePolicy(CachePolicy.ENABLED)
}.build(),
imageLoader = imageLoaderM,
contentDescription = null,
contentScale = ContentScale.FillBounds,
modifier = modifier,
alpha = alpha
)
} else {
var bitmap = BitmapFactory.decodeFile(data)
var bitmapPainter = BitmapPainter(bitmap.asImageBitmap())
Image(
modifier = modifier,
painter = bitmapPainter,
contentDescription = "",
contentScale = contentScale,
alpha = alpha
)
DisposableEffect(key1 = bitmap, effect = {
onDispose {
bitmap = null
}
})
}
}
}
I'm implementing a simple gallery screen using Jetpack Compose which shows all video and image thumbnails on the screen
I have displayed image from file path successfully. However, I've got trouble in showing video thumbnail. How can I do that using Coil?
Here's my code to show image thumbnails:
#Composable
fun ImageLoaderFromLocal(
url: String,
placeHolderResId: Int,
modifier: Modifier,
transformation: Transformation
) {
val painter = rememberImagePainter(data = File(url),
builder = {
placeholder(placeHolderResId)
crossfade(true)
transformations(transformation)
})
Image(
painter = painter,
contentDescription = null,
modifier = modifier,
contentScale = ContentScale.Inside
)
}
According to Coil documentation, you need to add following dependency:
implementation("io.coil-kt:coil-video:$coil_version")
and specify fetcher in the builder:
val context = LocalContext.current
val painter = rememberImagePainter(
data = url,
builder = {
fetcher(VideoFrameUriFetcher(context))
// optionally set frame location
videoFrameMillis(1000)
placeholder(placeHolderResId)
crossfade(true)
transformations(transformation)
}
)
The other answer doesn't really work any more.
so i tried this one and it worked
val context = LocalContext.current
var visible by rememberSaveable { mutableStateOf(false) }
val imageLoader = ImageLoader.Builder(context)
.components {
add(VideoFrameDecoder.Factory())
}.crossfade(true)
.build()
val painter = rememberAsyncImagePainter(
model = "Your file here",
imageLoader = imageLoader,
The painter should be called in the image composable
like this
Image(
painter = painter,
contentDescription = "",
contentScale = ContentScale.Crop,
alignment = Alignment.Center,
modifier = Modifier.fillMaxSize()
)
i hope this help someone
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()
)
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,
...
)
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)
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)}))
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.
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.
You can use the onSuccess callback to get the drawable:
AsyncImage(
model = url,
contentDescription = null,
onSuccess = { success ->
val drawable = success.result.drawable
}
)
You can use the same approach also with rememberAsyncImagePainter.