Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to check if image is already in Coil managed memory?

As the title says is there any way to know if an image is already in the cache?

I have a screen containing a list of image, clicking on an item will navigate to the detail screen for that item, the navigation is delayed until the item is successfully loaded.

My screen

I want when clicking on the loaded image, the navigation should happen immediately (the image is already in the cache). But I don't know how to check if the image is already in the cache.

Here are the codes:

@Composable
fun NavigateMap(navController: NavHostController = rememberNavController(),
            viewModel: BookViewModel = viewModel(factory = BookViewModel.Factory)) {
val context= LocalContext.current
val imageLoader = ImageLoader.Builder(context)
        .respectCacheHeaders(false)
        .build()

val uiState by viewModel.uiState.collectAsState()
NavHost(navController = navController, startDestination = BookScreenMap.Start.name) {
    composable(route = BookScreenMap.Start.name) {
        BookHome(onBookClick = { id ->
            viewModel.getPhotoById(id,imageLoader,context)
        })
    }
    composable(route = BookScreenMap.Detail.name) {
        DetailsScreen(
            uiState.thumbnail, title = uiState.title,
            authors = uiState.authors,
            description = uiState.description)
    }
}

val shouldNavigate=viewModel.shouldNavigate
LaunchedEffect(shouldNavigate) {
    if(shouldNavigate){
        navController.navigate(BookScreenMap.Detail.name)
        viewModel.shouldNavigate = false
    }
}
}

ViewMode Code:

class BookViewModel(private val bookRepository: BookRepository) : ViewModel() {
      var shouldNavigate by mutableStateOf(false)
      var fetchUiState:FetchUiState by mutableStateOf(FetchUiState.Loading)
         private set
      var fetchPhotoBook:FetchPhotoBook by mutableStateOf(FetchPhotoBook.Normal)
         private set
      private val _uiState = MutableStateFlow(BookUiState("", "","", "Chưa rõ",""))
      val uiState = _uiState.asStateFlow()

      init {
         getListBook("live",10)
      }



      fun getPhotoById(id: String, imageLoader: ImageLoader,
                 context: Context) {
         viewModelScope.launch {
         val infoBook = safeApiCall {
            bookRepository.getBookById(id).volumeInfo
      }
        infoBook?.let {
            val imageUrl = it.imageLinks?.medium.toString().replace("http:", "https:")
            updateBookUiState(
                thumbnail =imageUrl,
                title = it.title,
                authors = it.authors?.joinToString(", ") ?: "Unknown",
                description =HtmlCompat.fromHtml(it.description.orEmpty(), HtmlCompat.FROM_HTML_MODE_LEGACY).toString()
            )
            val cachedImage // how to check image in cache

            if (cachedImage ) {
                shouldNavigate=true

            } else {

                val request = ImageRequest.Builder(context)
                    .data(imageUrl)

                    .diskCachePolicy(CachePolicy.ENABLED)
                    .memoryCachePolicy(CachePolicy.ENABLED)
                    .networkCachePolicy(CachePolicy.ENABLED)
                    .diskCacheKey(imageUrl)
                    .memoryCacheKey(imageUrl)
                    .decoderFactory { _, _, _ ->
                        Decoder { DecodeResult(ColorDrawable(Color.BLACK), false) }
                    }
                    .listener(
                        onStart={ _ ->
                            fetchPhotoBook=FetchPhotoBook.Loading
                            Log.d("Request Start","Start")
                        }
                        ,
                        onSuccess = { _, _ ->
             
                            shouldNavigate=true
                        }
                        

                    )
                    .build()
                imageLoader.enqueue(request)
            }


        } ?: run {
            Log.d("BOOK_INFO", "No data found")
        }
    }
}


private suspend fun <T> safeApiCall(apiCall: suspend () -> T): T? {
    return withContext(Dispatchers.IO) {
        try {
            apiCall()
        } catch (e: HttpException) {
            Log.e("ApiCall", "HTTP exception occurred", e)
            null
        } catch (e: IOException) {
            Log.e("ApiCall", "Network or IO exception occurred", e)
            null
        } catch (e: Exception) {
            Log.e("ApiCall", "Unexpected exception occurred", e)
            null
        }
    }
}

...........

}

like image 851
Trần Thanh Vinh Avatar asked Jan 22 '26 22:01

Trần Thanh Vinh


1 Answers

I would consider not to wait with the navigation until the image is loaded.

  • When there is slow internet connection, the user will think that the app froze because nothing is happening after their click
  • The user has no chance to see what is going on in the background
  • The user has no chance to cancel the ongoing network request

Instead, I would delegate the responsability for loading the thumbnail image to the DetailScreen. You can display a loading animation in Coil using a SubComposeAsyncImage:

SubcomposeAsyncImage(
    model = "https://example.com/image.jpg",
    loading = {
        CircularProgressIndicator()
    },
    contentDescription = stringResource(R.string.description)
)

Also, as far as I can tell, your BookHome Composable already shows all thumbnails in a Grid. Are these coming from the same URL as the DetailScreen thumbnails? If yes, you can be sure that the images are already in cache.

like image 111
BenjyTec Avatar answered Jan 25 '26 15:01

BenjyTec



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!