AsyncImage in LazyColumn Kotlin Compose - android

I have a list of photos from the internet and I want to load them into a LazyColumn. The problem is that some of them are loading faster than others so the photos appear in random order.
Here is the code:
val state = viewModel.photos.collectAsState()
val lastIndex = state.value.lastIndex
LazyColumn {
itemsIndexed(state.value) { i, photo ->
AsyncImage(
model = photo.urls.regular,
contentDescription = null,
modifier = Modifier.fillMaxWidth(),
)
if (i >= lastIndex-1 && !viewModel.isLoading.value) {
viewModel.getPhotos(6)
}
}
}
Sometimes image with LastIndex-1 occurs first and it triggers viewModel to get new data.
How can I make a handler for loading a list of images? Or maybe any suggestions on how to make a preloading screen which will end after loading all images into LazyColumn
I also have a Coil version too:
val model = ImageRequest.Builder(LocalContext.current)
.data(photo.urls.regular)
.size(Size.ORIGINAL)
.crossfade(true)
.build()
val painter = rememberAsyncImagePainter(model)
Image(
painter = painter,
contentDescription = null,
modifier = Modifier.fillMaxWidth(),
)

Related

Jetpack compose avoid unnecessary Image bitmap recomposition

I'm trying to emulate a Music player, and everything works, however using the layout inspector I can see that my image is being recompose every time that the "slider" value changes this is part of my code:
val albumArt by viewModel.albumArt.collectAsStateWithLifecycle() // collect a bitmap
val playbackPosition by viewModel.playbackPosition.collectAsStateWithLifecycle() // this cause a recomposition, it executes every second that is the duration of te song
#Composable
fun MusicAlbumArt(
albumArt: Bitmap?,
modifier: Modifier
) {
val painter = rememberAsyncImagePainter(
albumArt,
contentScale = ContentScale.Crop
)
AnimatedContent(targetState = painter,
transitionSpec = {
fadeIn() with fadeOut()
}) {
Image(
painter = painter,
contentDescription = null,
modifier = modifier,
contentScale = ContentScale.Crop
)
}
}
and this is the main composable:
#Composable
private fun MusicWidget(
playbackPosition: Float,
albumArt: Bitmap?,
){
BoxWithConstraints {
val width by remember { mutableStateOf(this.maxWidth) }
val height by remember { mutableStateOf(this.maxHeight) }
MusicAlbumArt(
modifier = Modifier.fillMaxSize(),
musicViewState = musicViewState,
albumArt = albumArt
)
//
MusicSlider(modifier = Modifier.fillMaxWidth()
}
The playbackPosition executes every second to update the "song duration" but analyzing this in the layout inspector the AnimatedContent is skipped however the Image is recomposed every time, I can't find the wayt to avoid recomposition on the image. What could be the possible options?

Jetpack Compose Coil Preload

I want to load the image on the spash screen so that I can set the request result to the background on other screens using the coil, but I have not been able to fully realize this in the coil. How can I save the result of my coil request to Cahce and use this result on other screens?
Splash Screen
val imageLoader = ImageLoader.Builder(this)
.memoryCache {
MemoryCache.Builder(this)
.maxSizePercent(0.25)
.strongReferencesEnabled(true)
.build()
}
.diskCache {
DiskCache.Builder()
.directory(this.cacheDir.resolve("image_cache"))
.maxSizePercent(0.02)
.build()
}
.build()
and I used like this on splash
val context = LocalContext.current
val request = ImageRequest.Builder(context)
.memoryCacheKey(Constants.Cache.BACKGROUND_IMAGE_KEY)
.data("https://www.example/image1.jpg")
.target(
onSuccess = {
viewModel.skipImageRequest()
},
onError = {
viewModel.skipImageRequest()
}
)
.build()
imageLoader.enqueue(request)
in another screen that I want to use image
#Composable
fun BackgroundImage(
model: Any?,
contentDescription: String? = "",
modifier: Modifier = Modifier.fillMaxSize(),
//placeholder: Painter? = painterResource(R.drawable.bg_placeholder),
error: Painter? = painterResource(R.drawable.bg_placeholder),
fallback: Painter? = painterResource(R.drawable.bg_placeholder),
alignment: Alignment = Alignment.Center,
contentScale: ContentScale = ContentScale.FillBounds
) {
AsyncImage(
model = ImageRequest.Builder(LocalContext.current)
.data("https://www.example/image1.jpg")
.placeholderMemoryCacheKey(Constants.Cache.BACKGROUND_IMAGE_KEY)
.build(),
contentDescription = contentDescription,
modifier = modifier,
//placeholder = placeholder,
error = error,
fallback = fallback,
alignment = alignment,
contentScale = contentScale,
)
}
There is an always problem the placeholder image is shown in initialize of page I could not construct a structure where the placeholder is not visible while the image is being loaded and the image is read and used directly from the cahce.
I would be very happy if you support
Coil internally uses OkHttp's DiskCache in 1.x version
For 2.x they have introduced a disk cache to store your result.
For further information read this

How to use `ImageRequest.Builder.target` in the new coil version in jetpack compose?

My Gradle
// Coil
implementation "io.coil-kt:coil-compose:1.4.0"
Problem Description
Previously I used the coil together with Google's accompanist, but when I migrate to the new version of the coil as the documentation suggests I'm having problems with the target method:
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.pokedex, PID: 13502
java.lang.IllegalArgumentException: request.target must be null.
at coil.compose.ImagePainterKt.rememberImagePainter(ImagePainter.kt:94)
...
Coil Implementation
When browsing the internal code of ImagePainter (coil class) you can see that the target method really needs to be null for some reason:
#Composable
fun rememberImagePainter(
request: ImageRequest,
imageLoader: ImageLoader,
onExecute: ExecuteCallback = ExecuteCallback.Default,
): ImagePainter {
requireSupportedData(request.data)
require(request.target == null) { "request.target must be null." }
...
My Code
Here's my component in jetpack compose (the image component is inside a column):
Image(
modifier = Modifier
.size(120.dp)
.align(Alignment.CenterHorizontally),
painter = rememberImagePainter(
data = entry.imageUrl,
builder = {
crossfade(true)
target {
viewModel.calcDominantColor(it) { color ->
dominantColor = color
}
}
transformations(CircleCropTransformation())
},
),
contentDescription = entry.pokemonName
)
I need the target method to do internal operations on my viewModel based on the drawable it passes as a parameter. Can someone help me?
In Coil 2.0.0 both AsyncImage and rememberAsyncImagePainter have onSuccess callback parameter, using which you can get the drawable as follows:
AsyncImage(
model = imageURL,
contentDescription = null,
onSuccess = { success ->
val drawable = success.result.drawable
}
)
Coil 1.4.0 version:
This is intended behaviour since rememberImagePainter sets the target internally.
You can track the painter state, wait for the Success and get the drawable from it. Also use it with LaunchedEffect to prevent re-calculations:
val painter = rememberImagePainter(
data = imageUrl,
builder = {
...
},
)
(painter.state as? ImagePainter.State.Success)
?.let { successState ->
LaunchedEffect(Unit) {
val drawable = successState.result.drawable
viewModel.calcDominantColor(drawable) { color ->
dominantColor = color
}
}
}
Image(
painter = painter,
contentDescription = "...",
modifier = Modifier
...
)
I know you follow this https://www.youtube.com/watch?v=jrIfGAk8PyQ&list=PLQkwcJG4YTCTimTCpEL5FZgaWdIZQuB7m&index=5
by Philipp Lackner
try this code
AsyncImage(
model = ImageRequest.Builder(LocalContext.current)
.data(entry.imageUrl)
.crossfade(true)
.build(),
contentDescription = entry.pokemonName,
onSuccess = {
viewModel.calcDominantColor(it.result.drawable) { color ->
dominantColor = color
}
},
modifier = Modifier
.size(120.dp)
.align(CenterHorizontally)
)
and use this library implementation("io.coil-kt:coil-compose:2.2.2")
and to more information https://coil-kt.github.io/coil/compose/

How to display video thumbnail in Jetpack Compose?

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

How to show image in 'ScrollableTabRow'

I need to build a ScrollableTabRow that include text and image.
:
(this screen shot was taken on compose 1.0.0-alpha09)
but after I upgrade compose to 1.0.0, the image didn't show. the image tab item is empty:
the ScrollableTabRow demo code:
#Composable
fun ScrollableRowWithImage(){
ScrollableTabRow(
backgroundColor = Color.Transparent,
selectedTabIndex = 0,
edgePadding = 24.dp,
modifier = Modifier.wrapContentSize(align = Alignment.CenterStart)
) {
(1..4).forEach{ _ ->
Tab(
selected = false,
onClick = { },
) {
Image(
painter = rememberImagePainter(
data = "http://mstphoto.cmvideo.cn:8080/clt/20210607/09/1F7I2L5NT7HQ.png",
),
contentDescription = null,
)
}
}
}
}
The image can display normally in ohter place:
#Composable
fun ScrollableRowWithImage(){
Column(modifier = Modifier.fillMaxSize()) {
ScrollableTabRow(
backgroundColor = Color.Transparent,
selectedTabIndex = 0,
edgePadding = 24.dp,
modifier = Modifier.height(80.dp)){
(1..4).forEach{ _ ->
Tab(
selected = false,
onClick = { },
) {
SampleImage()
}
}
}
Divider()
SampleImage()
}
}
#Composable
fun SampleImage(){
Image(
painter = rememberImagePainter(
data = "http://mstphoto.cmvideo.cn:8080/clt/20210607/09/1F7I2L5NT7HQ.png",
),
contentDescription = null,
)
}
You can check out state of your request to see if there's an error using state value:
val painter = rememberImagePainter("http://mstphoto.cmvideo.cn:8080/clt/20210607/09/1F7I2L5NT7HQ.png")
println("${painter.state}")
Image(
painter = painter,
contentDescription = null,
modifier = Modifier.size(128.dp)
)
In your case it's:
CLEARTEXT communication to mstphoto.cmvideo.cn not permitted by network security policy
Which is because you're using http instead of https, check out how to solve this in this answer
An other problem is with Image inside ScrollableTabRow. Looks like a coil bug, I've reported. Image doesn't start loading without size modifier. Adding .size(40.dp) or even .weight(40.dp) solves the problem
Check it with a drawable resource, see if the problem is with the fetching of the data from the web. Replace the
painter = rememberImagePainter( data = "http://mstphoto.cmvideo.cn:8080/clt/20210607/09/1F7I2L5NT7HQ.png", )
with
painter = painterResource(R.drawable.ic_launcher_foreground) // OR any other resource, if this is not available
Check if this resource renders and if it does not, I'll modify the answer.

Categories

Resources