saket / telephoto

Building blocks for designing media experiences in Compose UI
https://saket.github.io/telephoto/
Apache License 2.0
869 stars 28 forks source link

IllegalStateException: Coil returned a null cache snapshot #82

Closed RegianeF closed 2 months ago

RegianeF commented 2 months ago

Hello,

Congratulations on the work, the zoom quality is veryy good.

I have a problem happening and I would like help if possible, I even viewed open issue #69 , but it didn't help me.

Excpetion:

java.lang.IllegalStateException: Coil returned a null cache snapshot
  at me.saket.telephoto.zoomable.coil.Resolver.toSubSamplingImageSource(CoilImageSource.kt:135)
  at me.saket.telephoto.zoomable.coil.Resolver.work(CoilImageSource.kt:110)
  at me.saket.telephoto.zoomable.coil.Resolver$work$1.invokeSuspend(Unknown Source:14)
  at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
  at kotlinx.coroutines.internal.ScopeCoroutine.afterResume(Scopes.kt:28)
  at kotlinx.coroutines.AbstractCoroutine.resumeWith(AbstractCoroutine.kt:99)
  at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46)
  at kotlinx.coroutines.DispatchedTaskKt.resume(DispatchedTask.kt:231)
  at kotlinx.coroutines.DispatchedTaskKt.resumeUnconfined(DispatchedTask.kt:187)
  at kotlinx.coroutines.DispatchedTaskKt.dispatch(DispatchedTask.kt:159)
  at kotlinx.coroutines.CancellableContinuationImpl.dispatchResume(CancellableContinuationImpl.kt:470)
  at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl(CancellableContinuationImpl.kt:504)
  at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl$default(CancellableContinuationImpl.kt:493)
  at kotlinx.coroutines.CancellableContinuationImpl.resumeWith(CancellableContinuationImpl.kt:364)
  at kotlinx.coroutines.ResumeAwaitOnCompletion.invoke(JobSupport.kt:1403)
  at kotlinx.coroutines.JobSupport.notifyCompletion(JobSupport.kt:1492)
  at kotlinx.coroutines.JobSupport.completeStateFinalization(JobSupport.kt:322)
  at kotlinx.coroutines.JobSupport.finalizeFinishingState(JobSupport.kt:239)
  at kotlinx.coroutines.JobSupport.tryMakeCompletingSlowPath(JobSupport.kt:907)
  at kotlinx.coroutines.JobSupport.tryMakeCompleting(JobSupport.kt:864)
  at kotlinx.coroutines.JobSupport.makeCompletingOnce$kotlinx_coroutines_core(JobSupport.kt:829)
  at kotlinx.coroutines.AbstractCoroutine.resumeWith(AbstractCoroutine.kt:97)
  at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46)
  at kotlinx.coroutines.UndispatchedCoroutine.afterResume(CoroutineContext.kt:266)
  at kotlinx.coroutines.AbstractCoroutine.resumeWith(AbstractCoroutine.kt:99)
  at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46)
  at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:104)
  at android.os.Handler.handleCallback(Handler.java:938)
  at android.os.Handler.dispatchMessage(Handler.java:99)
  at android.os.Looper.loopOnce(Looper.java:201)
  at android.os.Looper.loop(Looper.java:288)
  at android.app.ActivityThread.main(ActivityThread.java:7839)
  at java.lang.reflect.Method.invoke(Native Method)
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1003)
  Suppressed: kotlinx.coroutines.internal.DiagnosticCoroutineContextException: [StandaloneCoroutine{Cancelling}@553d403, Dispatchers.Main.immediate]

My code:

HorizontalPager(
  state = pagerState,
  modifier = Modifier.fillMaxWidth()
) { page ->

  val surfaceColor = MaterialTheme.colorScheme.surface

  var backgroundColor by remember { mutableStateOf(surfaceColor) }

  Box(modifier = Modifier.background(backgroundColor)) {

    val imageState = rememberZoomableImageState(
      rememberZoomableState(
        zoomSpec = ZoomSpec(maxZoomFactor = 4f)
      )
    )

    var isLoading by remember { mutableStateOf(false) }

    AnimatedVisibility(
      modifier = Modifier.align(Alignment.Center),
      visible = isLoading
    ) {
      CircularProgressIndicator(
        modifier = Modifier
          .padding(paddingValues)
          .align(Alignment.Center)
      )
    }

    var isError by remember { mutableStateOf(false) }

    AnimatedVisibility(
      modifier = Modifier.align(Alignment.Center),
      visible = isError
    ) {
      Image(
        modifier = Modifier.fillMaxSize(),
        painter = rememberVectorPainter(Icons.Outlined.HideImage),
        contentDescription = null
      )
    }

    ZoomableAsyncImage(
      state = imageState,
      modifier = Modifier.fillMaxSize(),
      model = ImageRequest.Builder(LocalContext.current)
        .data(currentImage)
        .allowHardware(false)
        .diskCachePolicy(CachePolicy.READ_ONLY)
        .memoryCachePolicy(CachePolicy.READ_ONLY)
        .crossfade(300)
        .listener(
          remember {
            object : ImageRequest.Listener {
              override fun onSuccess(
                request: ImageRequest,
                result: SuccessResult
              ) {

                Timber.i("Image is loaded")
                isLoading = false
                Palette.Builder(result.drawable.toBitmap())
                  .generate { palette ->
                    val generatedColor =
                      palette?.getDarkMutedColor(
                        backgroundColor.toArgb()
                      )

                    if (generatedColor != null && generatedColor != backgroundColor.toArgb()) {
                      backgroundColor =
                        Color(generatedColor)
                    }
                  }
                super.onSuccess(request, result)
              }

              override fun onError(
                request: ImageRequest,
                result: ErrorResult
              ) {
                Timber.i("Image has error")
                isLoading = false
                isError = true
                super.onError(request, result)
              }

              override fun onStart(request: ImageRequest) {
                Timber.i("Image start load")
                isLoading = true
                super.onStart(request)
              }

              override fun onCancel(request: ImageRequest) {
                Timber.i("Image is cancel")
                isLoading = false
                super.onCancel(request)
              }
            }
          }
        )
        .build(),
      contentDescription = null,
    )

  }
}

CurrentImage is urls?.get(pagerState.currentPage)

I need set: diskCachePolicy(CachePolicy.READ_ONLY) and memoryCachePolicy(CachePolicy.READ_ONLY) because has another exception: java.io.FileNotFoundException: No content provider: (...) look like #50 . If I put it this way I can't reproduce the problem.

For my test i used 11 imagens from https://unsplash.com/ and then this crash happend when insert app for the first time ou clean cache. It happens quite often, and happens most time in the last image from carousel.

Thanks for helping :)

saket commented 2 months ago

I need set: diskCachePolicy(CachePolicy.READ_ONLY)

This isn't viable because telephoto needs to stream large bitmaps from the disk for optimum performance. In fact, the disk policy is overridden by telephoto to CachePolicy.ENABLED here:

https://github.com/saket/telephoto/blob/5ce949a0dbbb05b11309b220161dc09044c1fb54/zoomable-image/coil/src/main/kotlin/me/saket/telephoto/zoomable/coil/CoilImageSource.kt#L87-L92

What's your reason for not saving downloaded photos to the disk?

For my test i used 11 imagens from https://unsplash.com/ and then this crash happend when insert app for the first time ou clean cache. It happens quite often, and happens most time in the last image from carousel.

If the app crashes only if you clear the cache in background then yep this is the same as https://github.com/saket/telephoto/issues/69. Is currentImage a String/Uri type?

RegianeF commented 2 months ago

What's your reason for not saving downloaded photos to the disk?

I just tried some solutions without much reason behind it, basically I had some fileprovider Exception, placing the caches that way I didn't notice the problem anymore. And if I remove these lines the problem returns. This is weird, i don't understand.

Captura de tela 2024-04-10 222751

If the app crashes only if you clear the cache in background then yep this is the same as #69. Is currentImage a String/Uri type?

I'm using just string for these tests.


I'm making a new app to show errors on my github, today an error I caught with Coil returned null. I can't currently play the file provider in this new app, I'm still going to adjust some things.

If will say in percetage, is like 5% of trys i get this erros.

https://github.com/RegianeF/SampleImageProject

The erros from this app related IllegalStateException: Coil returned a null cache snapshot:

Captura de tela 2024-04-10 203225 Captura de tela 2024-04-10 203305

Thanks for helping :)

L7ColWinters commented 2 months ago

We are also seeing this same error and I can't help but notice the !! in the CoilImageSource as well as the kotlin assertions used which crash the app instead of silently failing.

saket commented 2 months ago

Merging this with https://github.com/saket/telephoto/issues/37

saket commented 2 months ago

This should be fixed by v0.11.2. ZoomableAsyncImage() will now reload images if the disk cache is found empty.