I recently migrated from Accompanist's ImagePainter to Coil's, below is the pertinent code after my updates.
val painter = rememberImagePainter(DRAWABLE_RESOURCE_ID)
when (painter.state) {
is ImagePainter.State.Empty -> Timber.w("Empty")
is ImagePainter.State.Loading -> {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier.wrapContentSize()
) {
CircularProgressIndicator()
}
}
is ImagePainter.State.Success -> {
Image(
painter = painter,
contentDescription = null,
contentScale = ContentScale.Fit,
modifier = Modifier
.padding(8.dp)
.size(84.dp)
.clip(RoundedCornerShape(corner = CornerSize(16.dp)))
)
}
is ImagePainter.State.Error -> Timber.e("Error")
}
Now those images don't render and painter.state is always Empty. My legacy Accompanist implementation displayed images by this point in the code. It also works if I use the stock painterResource(resId) from Compose.
What am I missing to execute Coil's new painter through its states?
As suggested by #Philip Dukhov you don't need coil to load local resources.
If you want to use it, you can simply your code using:
val painter = rememberImagePainter(R.drawable.xxx)
val state = painter.state
Box(
contentAlignment = Alignment.Center,
modifier = Modifier.wrapContentSize()
) {
AnimatedVisibility(visible = (state is ImagePainter.State.Loading)) {
CircularProgressIndicator()
}
Image(
painter = painter,
contentDescription = null,
modifier = Modifier.size(128.dp)
)
}
You can use the drawable's resource ID as model for the coil's AsyncImage composable.
AsyncImage(
model = R.drawable.bg_gradient,
contentDescription = "Gradient background",
)
There's a significant performance difference between the composables - Image and AsyncImage. So Coil's AsyncImage loading is very handy at times.
You don't need coil to load local resources. You can use system painterResource:
Image(
painter = painterResource(id = R.drawable.test),
contentDescription = null,
contentScale = ContentScale.Fit,
modifier = Modifier
.padding(8.dp)
.size(84.dp)
.clip(RoundedCornerShape(corner = CornerSize(16.dp)))
)
If you would use it for remove image loading: since move from accompanist to coil, painter won't start loading unless Image is in the view tree hierarchy. So you can move Image into a Box with your while:
Box(contentAlignment = Alignment.Center) {
val painter = rememberImagePainter(R.drawable.test)
Image(
painter = painter,
contentDescription = null,
contentScale = ContentScale.Fit,
modifier = Modifier
.padding(8.dp)
.size(84.dp)
.clip(RoundedCornerShape(corner = CornerSize(16.dp)))
)
when (painter.state) {
is ImagePainter.State.Empty -> Timber.w("Empty")
is ImagePainter.State.Loading -> {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier.wrapContentSize()
) {
CircularProgressIndicator()
}
}
is ImagePainter.State.Success -> {
}
is ImagePainter.State.Error -> Timber.e("Error")
}
}
Also it may not start loading when you're not providing enough size modifiers(that's not your case, just for you to know). Check out this answer for more information.
val context = LocalContext.current
val imageLoader = ImageLoader(context)
val request = ImageRequest.Builder(context)
.data(thumbnailUrl)
.build()
val painter = rememberImagePainter(
request = request,
imageLoader = imageLoader
)
val state = painter.state
Image(
painter = painter,
contentDescription = "thumbnail image",
modifier = Modifier
.fillMaxSize()
.placeholder(
visible = state is ImagePainter.State.Loading,
color = PlaceholderDefaults.color(
backgroundColor = SMXTheme.colors.shimmer.copy(0.1f),
),
highlight = PlaceholderHighlight.shimmer(),
),
contentScale = ContentScale.Crop
)
Related
I want to make same Image size in all pages of HorizontalPager. I am manually added size in Image and it looks perfect. I want to remove the specific size, so is it possible to do in jetpack compose?
HorizontalPager(
count = 5,
state = pagerState,
) { currentPage ->
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Column(
Modifier.height(height = 428.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Image(
modifier = Modifier
.size(280.dp)
.padding(top = 80.dp),
painter = painterResource(imageResId),
contentDescription = null,
contentScale = ContentScale.Fit,
)
Text(
modifier = Modifier.padding(top = 20.dp),
text = "Xyz",
)
Text(
modifier = Modifier.padding(top = 20.dp),
text = "description",
textAlign = TextAlign.Center,
)
}
HorizontalPagerIndicator(
modifier = Modifier
.padding(top = 80.dp),
pagerState = pagerState,
activeColor = Teal,
inactiveColor = Platinum,
)
}
}
Image uses intrinsic size of Bitmap/Painter, parent Constraints or dimensions in simple manner and ContentScale to draw a resource.
Based on image resource aspect ratios they are to be fit inside an Image constrained with 428.dp minus size of other Text composables, this is the biggest height Image can get based on its inner calculations. Simple solution for this is to match one dimension of Image to parent and using Modifier.aspectRatio to have all images have same dimensions without setting a fixed size modifier.
These 2 png files have different aspect ratios and drawn as
#Preview
#Composable
private fun Test(){
Column(modifier = Modifier.fillMaxSize()) {
Column(modifier = Modifier.height(200.dp).border(2.dp, Color.Red)) {
Image(
painter = painterResource(id = R.drawable.landscape11),
contentDescription = null,
contentScale = ContentScale.Fit
)
}
Column(modifier = Modifier.height(200.dp).border(2.dp, Color.Green)) {
Image(
painter = painterResource(id = R.drawable.landscape3),
contentDescription = null,
contentScale = ContentScale.Fit
)
}
}
}
After setting aspect ratio modifier
#Preview
#Composable
private fun Test(){
Column(modifier = Modifier.fillMaxSize()) {
Column(modifier = Modifier.height(200.dp).border(2.dp, Color.Red)) {
Image(
modifier = Modifier.aspectRatio(4/3f),
painter = painterResource(id = R.drawable.landscape11),
contentDescription = null,
contentScale = ContentScale.Fit
)
}
Column(modifier = Modifier.height(200.dp).border(2.dp, Color.Green)) {
Image(
modifier = Modifier.aspectRatio(4/3f),
painter = painterResource(id = R.drawable.landscape3),
contentDescription = null,
contentScale = ContentScale.Fit
)
}
}
}
I want to display web images by URL links. I want to display progress bar until image will be loaded and then hide progress bar and display image.
How to detect when painter loaded image?
HorizontalPager(count = photoUrlList.size, state = photoPagerState) { page ->
var shouldBeProgressBarDisplayed by mutableStateOf(true)
val painter = rememberImagePainter(photoUrlList[page], builder = {
this.listener(
onSuccess = { request, ex ->
shouldBeProgressBarDisplayed = false
})
})
if (shouldBeProgressBarDisplayed) {
ProgressBar()
} else {
Image(
painter = painter,
modifier = Modifier
.padding(vertical = 100.dp)
.fillMaxSize(),
contentScale = ContentScale.Crop,
contentDescription = ""
)
}
}
You can use painter.state to achieve this
HorizontalPager(count = photoUrlList.size, state = photoPagerState) { page ->
Box(
modifier = Modifier
.fillMaxSize()
.padding(vertical = 100.dp)
) {
val painter = rememberImagePainter(
data = photoUrlList[page],
builder = {
crossfade(500)
})
val painterState = painter.state
if (painterState is ImagePainter.State.Loading) {
CircularProgressIndicator(
modifier = Modifier.align(Alignment.Center),
color = MaterialTheme.colors.secondary
)
} else {
Image(
painter = painter,
modifier = Modifier
.fillMaxSize(),
contentScale = ContentScale.Crop,
contentDescription = ""
)
}
}
}
This question already has answers here:
Auto Height Jetpack Compose Coil Image
(4 answers)
Closed 1 year ago.
Am trying to load multiple images inside a LazyRow composable , the painter state is always giving me an empty state, i dont know what am missing.
The code of the composable
fun NetworkImage(
url: String,
contentDescription: String = "",
modifier: Modifier,
alignment: Alignment = Alignment.Center,
contentScale: ContentScale = ContentScale.Fit,
alpha: Float = DefaultAlpha,
colorFilter: ColorFilter? = null,
placeholderDrawableRes: Int? = null,
crossFade: Int? = null,
transformations: List<Transformation>? = null,
onLoading: #Composable () -> Unit,
onError: #Composable () -> Unit
) {
Box(
modifier = modifier
){
val painter = rememberImagePainter(
data = url,
builder = {
placeholderDrawableRes?.let {
placeholder( drawableResId = it )
}
error(
R.drawable.ic_warning
)
crossFade?.let {
crossfade(durationMillis = it)
}
transformations?.let {
transformations(transformations = it)
}
}
)
val imageState = painter.state
if(imageState is ImagePainter.State.Loading){
onLoading()
}
if(imageState is ImagePainter.State.Error){
onError()
}
Image(
painter = painter,
contentDescription = contentDescription,
contentScale = contentScale,
alignment = alignment,
alpha = alpha,
colorFilter = colorFilter
)
}
}
Inside The LazyRow
LazyRow(modifier = modifier) {
items(items = urls){ url ->
NetworkImage(
url = url,
contentScale = ContentScale.Crop,
modifier = Modifier.fillMaxSize(),
onLoading = {
ConstraintLayout(
modifier = Modifier.fillMaxSize()
) {
val indicator = createRef()
CircularProgressIndicator(
modifier = Modifier.constrainAs(indicator) {
top.linkTo(parent.top)
bottom.linkTo(parent.bottom)
start.linkTo(parent.start)
end.linkTo(parent.end)
}
)
}
},
onError = {
Icon(
painter = painterResource(id = R.drawable.ic_warning),
contentDescription = "",
tint = Color.Red
)
}
)
}
}
When i use the NetworkImage Composable only once it works fine , but when i use it inside a loop or a LazyRow , it renders an empty Box.
Any hints.
You better use Accompanists Pager(https://google.github.io/accompanist/pager/)
add Pagers liberary to app level module
implementation "com.google.accompanist:accompanist-pager:0.18.0"
then use this code as a sample
Box(contentAlignment = Alignment.Center, modifier = Modifier.fillMaxSize()) {
val pagerState = rememberPagerState(pageCount = urls.size)
HorizontalPager(state = pagerState) { index ->
NetworkImage(
url = urls[index],
contentScale = ContentScale.Crop,
modifier = Modifier
.fillMaxWidth()
.height(300.dp),
onLoading = {
ConstraintLayout(
modifier = Modifier.fillMaxSize()
) {
val indicator = createRef()
CircularProgressIndicator(
modifier = Modifier.constrainAs(indicator) {
top.linkTo(parent.top)
bottom.linkTo(parent.bottom)
start.linkTo(parent.start)
end.linkTo(parent.end)
}
)
}
},
onError = {
Icon(
painter = painterResource(id = R.drawable.ic_warning),
contentDescription = "",
tint = Color.Red
)
}
)
}
}
use NetworkImage composable as it is.
I am trying to show an image from the gallery if anything chosen by the user, or an image from an image file in the drawable resource as the default image, which is not working. I am using Coil for Compose and added the dependency already. Here is the code:
class MainActivity : ComponentActivity() {
private var imageUriState = mutableStateOf<Uri?>(null)
private val selectImageLauncher = registerForActivityResult(GetContent()) { uri ->
imageUriState.value = uri
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ImageSourceActivityScreen()
}
}
#Composable
fun ImageSourceActivityScreen() {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center
) {
Image(
painter = rememberImagePainter(
if (imageUriState != null) {
imageUriState.value
} else {
R.drawable.blank_profile_picture
}
),
contentDescription = "profile image",
contentScale = ContentScale.Crop,
modifier = Modifier.fillMaxWidth()
)
...
}
No error is showing, but the default image is also not showing. Please help to make it work. Thanks!
Your code doesn't work because you're comparing imageUriState with null which is always true
You have two options:
Specify painter depending on your state value
Image(
painter = if (imageUriState.value != null) {
rememberImagePainter(
imageUriState.value
)
} else {
painterResource(id = R.drawable.blank_profile_picture)
},
contentDescription = "profile image",
contentScale = ContentScale.Crop,
modifier = Modifier.fillMaxWidth()
)
By default, coil won't display you a placeholder if you pass null as data, which generally makes sense. If you wanna see your placeholder both when there's no image, and when you're waiting image to be loaded, you can create such function:
#Composable
inline fun rememberImagePainter(
data: Any?,
#DrawableRes emptyPlaceholder: Int,
builder: ImageRequest.Builder.() -> Unit = {},
): Painter {
val painter = rememberImagePainter(
data,
builder = {
placeholder(emptyPlaceholder)
builder()
}
)
if (data == null) {
return painterResource(emptyPlaceholder)
}
return painter
}
// usage
Image(
painter = rememberImagePainter(
"https://i.stack.imgur.com/rkyep.jpg",
emptyPlaceholder = R.drawable.test,
),
contentDescription = "profile image",
contentScale = ContentScale.Crop,
modifier = Modifier.fillMaxSize()
)
You can use the builder parameter to set a placeholder:
val painter = rememberImagePainter(
imageUriState.value,
builder = {
placeholder(R.drawable.blank_profile_picture)
}
)
Image(
painter,
contentDescription = "profile image",
contentScale = ContentScale.Crop,
modifier = Modifier.fillMaxWidth()
)
Found hints of where to look for the bug from #Gabriele Mariotti's answer. I was calling imageUriState without .value. Fixed that, and it is working now. Thanks, everyone for your help!
Image(
painter = rememberImagePainter(
if (imageUriState.value != null) {
imageUriState.value
} else {
R.drawable.blank_profile_picture
}
),
contentDescription = "profile image",
contentScale = ContentScale.Crop,
modifier = Modifier.fillMaxWidth()
)
I'm using coil:0.10.0 in my project but it's not as expected, I want to change the image's background
val painter = rememberCoilPainter(
fadeIn = true,
request = ImageRequest.Builder(LocalContext.current)
.data(entry.imageUrl)
.target {
viewModel.calcDominantColor(it) { color ->
dominantColor = color
}
}
.build()
)
Column {
Image(
painter = painter,
contentDescription = entry.pokemonName,
modifier = Modifier
.size(120.dp)
.align(CenterHorizontally)
)
}
}
Expect:
Try Modifier.background(brush = verticalGradient(listOf(dominantColor, Color.White)))