Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Jetpack Compose Coil gets stuck in Loading state in a LazyColumn that uses a placeholder #1799

Open
jakepurple13 opened this issue Jun 30, 2023 · 7 comments
Labels
help wanted Issues that are up for grabs + are good candidates for community PRs

Comments

@jakepurple13
Copy link

Describe the bug
When going to a screen that has a LazyColumn, the images load. The problem is when going back a screen, then returning to the LazyColumn screen. I'm making use of Accompanist's placeholder library as well. For some reason, coil is getting stuck in a loading state.

The code that enables the placeholder:

val painter = rememberAsyncImagePainter(
    model = imageUrl,
    placeholder = painterResource(R.drawable.ic_launcher_background),
    onSuccess = {
        println("JJJ Success!")
    },
    onLoading = {
        println("JJJ Loading!")
    },
    onError = {
        println("JJJ Error!")
        it.result.throwable.printStackTrace()
    }
)

Modifier.placeholder(
    visible = painter.state !is AsyncImagePainter.State.Success,
    color = PlaceholderDefaults.shimmerHighlightColor(),
    highlight = PlaceholderHighlight.shimmer()
)

To Reproduce
This project makes it easy to reproduce:
https://github.com/jakepurple13/CoilWeirdness
When running, press Go then press on an image or back and it will return to the Go screen. Then press Go again and stuck loading state happens.

Logs/Screenshots
https://github.com/coil-kt/coil/assets/2065574/5bb84723-659f-4c0b-b820-4b08b9e09589
Screen Shot 2023-06-30 at 09 45 37

Version
Coil version: 2.4.0

@colinrtwhite
Copy link
Member

Thanks for the repro project! Will take a look unless someone beats me to it.

@colinrtwhite colinrtwhite added the help wanted Issues that are up for grabs + are good candidates for community PRs label Jul 2, 2023
@jakepurple13
Copy link
Author

I took a look into this, since I had some free time, and I think I know what's causing the issue, kind of.
After MANY logs, I found that:

eventListener.resolveSizeStart(request).also { println("before size") }
val size = request.sizeResolver.size() //TODO: This is blocking it
eventListener.resolveSizeEnd(request, size).also { println("after size") }

the middle guy is blocking it.

If I change the requestOf function to have a set size:

@Composable
@ReadOnlyComposable
internal fun requestOf(model: Any?): ImageRequest {
    if (model is ImageRequest) {
        return model
    } else {
        return ImageRequest.Builder(LocalContext.current).size(400).data(model).build() //<-- here!!!
    }
}

everything works as intended. But that is just a work around.
I plan on continuing to see what could possibly be blocking it and why.

@jakepurple13
Copy link
Author

I figured out what the problem is!

private val drawSize = MutableStateFlow(Size.Zero)

Since that guy starts with Size.Zero, when calling this:

size { drawSize.mapNotNull { it.toSizeOrNull() }.first() }

toSizeOrNull()'s check is returning null!

private fun Size.toSizeOrNull() = when {
    isUnspecified -> CoilSize.ORIGINAL
    isPositive -> CoilSize(
        width = if (width.isFinite()) Dimension(width.roundToInt()) else Dimension.Undefined,
        height = if (height.isFinite()) Dimension(height.roundToInt()) else Dimension.Undefined
    )
    else -> null
}

Since it ends up going to the else condition.
When I set the default size to:

private val drawSize = MutableStateFlow(Size.Unspecified)

it works without issue!

@colinrtwhite
Copy link
Member

colinrtwhite commented Jul 12, 2023

Thanks for taking a look! The drawSize call looks to be working correctly as we want it to return null until it's given a valid size. I'd check out here as that callback should be invoked by Compose to provide the correct constraints which are used to set drawSize. It sounds like that callback isn't being called, which causes the request to get stuck.

@jakepurple13
Copy link
Author

I have found that if I do:

val painter = rememberAsyncImagePainter(
            model = ImageRequest.Builder(LocalContext.current)
                .data(imageUrl)
                .size(Size.ORIGINAL)
                .build()
)

It works completely fine.

@colinrtwhite
Copy link
Member

colinrtwhite commented Jul 17, 2023

@jakepurple13 Setting size(Size.ORIGINAL) skips waiting for Compose to call this callback. It sounds like that callback isn't being called, which causes the request to get stuck.

@jakepurple13
Copy link
Author

Yeah, the problem is that I'm not using AsyncImage. I'm using a normal image and it only happens when a placeholder is used. Unless I'm misunderstanding something which could be the case here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
help wanted Issues that are up for grabs + are good candidates for community PRs
Projects
None yet
Development

No branches or pull requests

2 participants