Variable ImagePainter on Jetpack Compose - android

I'm working on an Android app using Jetpack Compose 1.0.0 and I'm trying to make a composable that uses a nullable image URL string and, if it's null, it will show a placeholder with painterResource and, if it's not null, it will display the actual image using rememberImagePainter.
The way I was doing that was:
#Composable
fun VariableImagePainterExample (
imageURL: String?
) {
val painter = rememberCoilPainter(
null,
previewPlaceholder = R.drawable.ic_placeholder_user,
fadeIn = true,
)
LaunchedEffect(imageURL) {
painter.request = imageURL
}
Image(painter = painter, contentDescription = null)
}
Unfortunately, the rememberCoilPainter became deprecated from accompanist-coil and it's suggested now to use the rememberImagePainter. However, the ImagePainter.request can't be changed like above. I then tried the following code:
#Composable
fun VariableImagePainterExample (
imageURL: String?
) {
val painter = remember {
mutableStateOf<ImagePainter>(painterResource(id = R.drawable.ic_placeholder_user))
}
LaunchedEffect(imageURL) {
painter.value = rememberImagePainter(imageURL)
}
Image(painter = painter.value, contentDescription = null)
}
But this doesn't work because painterResource and rememberImagePainter must be used on the #Composable function. How can i achieve the same effect as before?

In Coil 2.0.0 both AsyncImage and rememberAsyncImagePainter have placeholder parameter that takes any other painter:
AsyncImage(
model = imageURL,
placeholder = painterResource(R.drawable.placeholder),
contentDescription = null,
)
In Coil 1.4.0 you can use builder like this:
Image(
rememberImagePainter(
imageURL,
builder = {
placeholder(R.drawable.placeholder)
}
),
contentDescription = null,
)

For Coil 2.2.+
It has remeberAsyncImagePainter instead off rememberImagePainter
Image(painter = rememberAsyncImagePainter(model = imageUrl,
imageLoader = ImageLoader.Builder(context).crossfade(true).build()),
contentDescription = "images")

Related

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 set an image URL as error placeholder on Coil in Jetpack Compose

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

Jetpack Compose Testing: Assert specific image is set

I have an Image in compose like the following:
Image(
bitmap = ImageBitmap.imageResource(id = R.drawable.testimage),
contentDescription = null, // Only decorative image
contentScale = ContentScale.FillWidth,
modifier = Modifier
.requiredHeightIn(max = 250.dp)
.fillMaxWidth()
.semantics { testTag = "MyTestTag" },
)
During an Instrumentation test I want to make sure the correct drawable is set. I did not find anything to achieve this in classes like SemanticsProperties to write a custom matcher. Can anyone help?
You can add a semantic yourself.
val DrawableId = SemanticsPropertyKey<Int>("DrawableResId")
var SemanticsPropertyReceiver.drawableId by DrawableId
val resId = R.drawable.my_drawable
Image(
painter = painterResource(id = resId),
contentDescription = null,
Modifier.semantics { drawableId = resId }
)
And test it with
fun hasDrawable(#DrawableRes id: Int): SemanticsMatcher =
SemanticsMatcher.expectValue(DrawableId, id)
composeRule.onNode(hasDrawable(R.drawable.my_drawable))
.assertIsDisplayed()
If you prefer to keep things simple and avoid defining your own semantics, use the testTag:
val resId = R.drawable.some_image
Image(
painter = painterResource(id = resId),
contentDescription = null,
Modifier.testTag = resId.toString()
)
and in your test:
composeTestRule.onNodeWithTag(R.drawable.someImage.toString())
.assertIsDisplayed()
You can use espresso for that
Espresso.onView(withId(R.drawable.testimage)).check(matches(isDisplayed()))
https://developer.android.com/jetpack/compose/testing#espresso-interop
The semantics apparently are not aware of what image is displayed.

In Compose how to access drawable once Coil loads image from URL

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.

Categories

Resources