androidandroid-jetpack-composeimage-loadingcoil

Why isn't Coil 3 displaying a valid image URL in Jetpack Compose, even though the URL is valid and StateFlow recomposes properly?


I'm migrating a user profile screen from XML + Glide to Jetpack Compose using this Coil dependency:

io.coil-kt.coil3:coil-compose:3.1.0

The profile image URL is returned from an API and correctly logged in the ViewModel. The image URL returned from the API looks like this:

https://example.com/img/icon_user.png ← PNG over HTTPS

It's a static placeholder image used as the default profile icon, and it loads fine in a browser. However, the image is not displayed on screen using rememberAsyncImagePainter, even though:


ViewModel

class UserViewModel : ViewModel() {
    private val _imageUrl = MutableStateFlow("")
    val imageUrl: StateFlow<String> = _imageUrl

    fun loadUserProfile() {
        viewModelScope.launch {
            userProfileUseCase.execute().collect {
                if (it is RequestState.Success) {
                    val body = it.response.result.body
                    val url = body.imageUrl
                    Log.d("UserViewModel", "imageUrl = $url") // ✅ URL is valid
                    _imageUrl.emit(url ?: "")
                }
            }
        }
    }
}

Composable Usage

@Composable
fun UserProfileScreen(userViewModel: UserViewModel) {
    val imageUrl by userViewModel.imageUrl.collectAsStateWithLifecycle()

    UserImageThumbnail(
        imageUrl = imageUrl,
        onClick = { /* omitted */ }
    )
}

Image Display Composable

@Composable
fun UserImageThumbnail(imageUrl: String?, onClick: () -> Unit) {
    val context = LocalContext.current

    Box(
        modifier = Modifier
            .size(107.dp)
            .clickable { onClick() }
    ) {
        if (!imageUrl.isNullOrBlank()) {
            Image(
                painter = rememberAsyncImagePainter(
                    ImageRequest.Builder(context)
                        .data(imageUrl)
                        .memoryCachePolicy(CachePolicy.DISABLED)
                        .diskCachePolicy(CachePolicy.DISABLED)
                        .build()
                ),
                contentDescription = null,
                modifier = Modifier.fillMaxSize(),
                contentScale = ContentScale.Crop
            )
        }

        Icon(
            painter = painterResource(id = R.drawable.icon_camera),
            contentDescription = null,
            modifier = Modifier
                .align(Alignment.Center)
                .size(48.dp)
        )
    }
}

Why isn't Coil 3 displaying a valid image URL in Jetpack Compose, even though the URL is correct and Compose recomposes? Is there anything specific I need to handle when using Coil 3 with image URLs from APIs?

I expected the image to appear when I updated the StateFlow with a valid image URL from the API. This includes both user-uploaded profile images and default fallback images that are always provided by the API. So the URL is never null or empty.

I confirmed in logs that the URL is valid and Compose recomposes as expected. But the image is not displayed.

I tried disabling both memory and disk cache with Coil 3, and also verified that the same URL loads successfully in Glide and in a browser. Still, nothing is shown in Compose.


Solution

  • It seems like you are missing a network dependency for Coil to access the internet. This is a new requirement starting from version 3.

    On Android you can simply use this:

    io.coil-kt.coil3:coil-network-okhttp
    

    It tells Coil to use the OkHttp client that Android already provides out-of-the-box.

    If you already have Ktor in your dependencies (a feature-rich multiplatform networking library), you can tell Coil to use it instead. For more, see here: https://coil-kt.github.io/coil/network/