skydoves / landscapist

🌻 A pluggable, highly optimized Jetpack Compose and Kotlin Multiplatform image loading library that fetches and displays network images with Glide, Coil, and Fresco.
https://skydoves.github.io/landscapist/
Apache License 2.0
2.13k stars 114 forks source link

LazyVerticalGrid Fresco scroll crash #132

Closed andreas-umbricht closed 2 years ago

andreas-umbricht commented 2 years ago

Describe the Bug:

When using a LazyVerticalGrid with following minimal example, the app crashes upon scrolling, when using more than 30+ image Items. I can't reproduce the crash with my Huawei P30 Pro which runs Android 11, so it could also be a Android 12 kind of problem. But even when I test on my Huawei P30 Pro device, the pictures, which are really not that big, are loaded very slowly.

@Composable
fun GalleryScreen(navController: NavController, imageUrls: List<String>?) {
    if (i == null) return

    LazyVerticalGrid(
        columns = GridCells.Fixed(3)) {
        items(imageUrls) { imageUrl ->
            Box(modifier = Modifier.aspectRatio(1f)) {
                FrescoImage(
                    imageUrl = imageUrl,
                    contentScale = ContentScale.Crop
                )
            }
        }
    }
}

Error message is:

E/AndroidRuntime: FATAL EXCEPTION: main
    Process: app.festiguide, PID: 7549
    java.lang.RuntimeException: Canvas: trying to use a recycled bitmap android.graphics.Bitmap@8a09c7c
        at android.graphics.BaseCanvas.throwIfCannotDraw(BaseCanvas.java:74)
        at android.graphics.RecordingCanvas.throwIfCannotDraw(RecordingCanvas.java:263)
        at android.graphics.BaseRecordingCanvas.drawBitmap(BaseRecordingCanvas.java:94)
        at androidx.compose.ui.graphics.AndroidCanvas.drawImageRect-HPBpro0(AndroidCanvas.android.kt:271)
        at androidx.compose.ui.graphics.drawscope.CanvasDrawScope.drawImage-AZ2fEMs(CanvasDrawScope.kt:263)
        at androidx.compose.ui.node.LayoutNodeDrawScope.drawImage-AZ2fEMs(Unknown Source:39)
        at androidx.compose.ui.graphics.drawscope.DrawScope$DefaultImpls.drawImage-AZ2fEMs$default(DrawScope.kt:508)
        at androidx.compose.ui.graphics.painter.BitmapPainter.onDraw(BitmapPainter.kt:93)
        at androidx.compose.ui.graphics.painter.Painter.draw-x_KDEd0(Painter.kt:212)
        at androidx.compose.ui.draw.PainterModifier.draw(PainterModifier.kt:281)
        at androidx.compose.ui.node.DrawEntity.draw(DrawEntity.kt:98)
        at androidx.compose.ui.node.LayoutNodeWrapper.drawContainedDrawModifiers(LayoutNodeWrapper.kt:316)
        at androidx.compose.ui.node.LayoutNodeWrapper.draw(LayoutNodeWrapper.kt:306)
        at androidx.compose.ui.node.ModifiedLayoutNode.performDraw(ModifiedLayoutNode.kt:139)
        at androidx.compose.ui.node.LayoutNodeWrapper.drawContainedDrawModifiers(LayoutNodeWrapper.kt:314)
        at androidx.compose.ui.node.LayoutNodeWrapper.access$drawContainedDrawModifiers(LayoutNodeWrapper.kt:60)
        at androidx.compose.ui.node.LayoutNodeWrapper$invoke$1.invoke(LayoutNodeWrapper.kt:336)
        at androidx.compose.ui.node.LayoutNodeWrapper$invoke$1.invoke(LayoutNodeWrapper.kt:335)
        at androidx.compose.runtime.snapshots.Snapshot$Companion.observe(Snapshot.kt:2098)
        at androidx.compose.runtime.snapshots.SnapshotStateObserver.observeReads(SnapshotStateObserver.kt:112)
        at androidx.compose.ui.node.OwnerSnapshotObserver.observeReads$ui_release(OwnerSnapshotObserver.kt:78)
        at androidx.compose.ui.node.LayoutNodeWrapper.invoke(LayoutNodeWrapper.kt:335)
        at androidx.compose.ui.node.LayoutNodeWrapper.invoke(LayoutNodeWrapper.kt:60)
        at androidx.compose.ui.platform.RenderNodeApi29.record(RenderNodeApi29.android.kt:180)
        at androidx.compose.ui.platform.RenderNodeLayer.updateDisplayList(RenderNodeLayer.android.kt:297)
        at androidx.compose.ui.platform.RenderNodeLayer.drawLayer(RenderNodeLayer.android.kt:238)
        at androidx.compose.ui.node.LayoutNodeWrapper.draw(LayoutNodeWrapper.kt:301)
        at androidx.compose.ui.node.ModifiedLayoutNode.performDraw(ModifiedLayoutNode.kt:139)
        at androidx.compose.ui.node.LayoutNodeWrapper.drawContainedDrawModifiers(LayoutNodeWrapper.kt:314)
        at androidx.compose.ui.node.LayoutNodeWrapper.draw(LayoutNodeWrapper.kt:306)
        at androidx.compose.ui.node.ModifiedLayoutNode.performDraw(ModifiedLayoutNode.kt:139)
        at androidx.compose.ui.node.LayoutNodeWrapper.drawContainedDrawModifiers(LayoutNodeWrapper.kt:314)
        at androidx.compose.ui.node.LayoutNodeWrapper.draw(LayoutNodeWrapper.kt:306)
        at androidx.compose.ui.node.LayoutNode.draw$ui_release(LayoutNode.kt:831)
        at androidx.compose.ui.node.InnerPlaceable.performDraw(InnerPlaceable.kt:90)
        at androidx.compose.ui.node.LayoutNodeWrapper.drawContainedDrawModifiers(LayoutNodeWrapper.kt:314)
        at androidx.compose.ui.node.LayoutNodeWrapper.draw(LayoutNodeWrapper.kt:306)
        at androidx.compose.ui.node.LayoutNode.draw$ui_release(LayoutNode.kt:831)
        at androidx.compose.ui.node.InnerPlaceable.performDraw(InnerPlaceable.kt:90)
        at androidx.compose.ui.node.LayoutNodeWrapper.drawContainedDrawModifiers(LayoutNodeWrapper.kt:314)
        at androidx.compose.ui.node.LayoutNodeWrapper.draw(LayoutNodeWrapper.kt:306)
        at androidx.compose.ui.node.ModifiedLayoutNode.performDraw(ModifiedLayoutNode.kt:139)
        at androidx.compose.ui.node.LayoutNodeWrapper.drawContainedDrawModifiers(LayoutNodeWrapper.kt:314)
        at androidx.compose.ui.node.LayoutNodeWrapper.access$drawContainedDrawModifiers(LayoutNodeWrapper.kt:60)
        at androidx.compose.ui.node.LayoutNodeWrapper$invoke$1.invoke(LayoutNodeWrapper.kt:336)
        at androidx.compose.ui.node.LayoutNodeWrapper$invoke$1.invoke(LayoutNodeWrapper.kt:335)
        at androidx.compose.runtime.snapshots.Snapshot$Companion.observe(Snapshot.kt:2098)
        at androidx.compose.runtime.snapshots.SnapshotStateObserver.observeReads(SnapshotStateObserver.kt:112)
        at androidx.compose.ui.node.OwnerSnapshotObserver.observeReads$ui_release(OwnerSnapshotObserver.kt:78)
        at androidx.compose.ui.node.LayoutNodeWrapper.invoke(LayoutNodeWrapper.kt:335)
        at androidx.compose.ui.node.LayoutNodeWrapper.invoke(LayoutNodeWrapper.kt:60)
        at androidx.compose.ui.platform.RenderNodeApi29.record(RenderNodeApi29.android.kt:180)
        at androidx.compose.ui.platform.RenderNodeLayer.updateDisplayList(RenderNodeLayer.android.kt:297)
        at androidx.compose.ui.platform.RenderNodeLayer.drawLayer(RenderNodeLayer.android.kt:238)
        at androidx.compose.ui.node.LayoutNodeWrapper.draw(LayoutNodeWrapper.kt:301)
        at androidx.compose.ui.node.LayoutNode.draw$ui_release(LayoutNode.kt:831)
        at androidx.compose.ui.node.InnerPlaceable.performDraw(InnerPlaceable.kt:90)
        at androidx.compose.ui.node.LayoutNodeDrawScope.drawContent(LayoutNodeDrawScope.kt:48)
        at androidx.compose.foundation.gestures.DrawOverScrollModifier.draw(AndroidOverScroll.kt:75)
        at androidx.compose.ui.node.DrawEntity.draw(DrawEntity.kt:98)
        at androidx.compose.ui.node.LayoutNodeWrapper.drawContainedDrawModifiers(LayoutNodeWrapper.kt:316)
        at androidx.compose.ui.node.LayoutNodeWrapper.access$drawContainedDrawModifiers(LayoutNodeWrapper.kt:60)
        at androidx.compose.ui.node.LayoutNodeWrapper$invoke$1.invoke(LayoutNodeWrapper.kt:336)
        at androidx.compose.ui.node.LayoutNodeWrapper$invoke$1.invoke(LayoutNodeWrapper.kt:335)
        at androidx.compose.runtime.snapshots.Snapshot$Companion.observe(Snapshot.kt:2098)
        at androidx.compose.runtime.snapshots.SnapshotStateObserver.observeReads(SnapshotStateObserver.kt:112)
        at androidx.compose.ui.node.OwnerSnapshotObserver.observeReads$ui_release(OwnerSnapshotObserver.kt:78)
        at androidx.compose.ui.node.LayoutNodeWrapper.invoke(LayoutNodeWrapper.kt:335)
        at androidx.compose.ui.node.LayoutNodeWrapper.invoke(LayoutNodeWrapper.kt:60)
        at androidx.compose.ui.platform.RenderNodeApi29.record(RenderNodeApi29.android.kt:180)
        at androidx.compose.ui.platform.RenderNodeLayer.updateDisplayList(RenderNodeLayer.android.kt:297)
        at androidx.compose.ui.platform.AndroidComposeView.dispatchDraw(AndroidComposeView.android.kt:948)
        at android.view.View.draw(View.java:22707)
        at android.view.View.updateDisplayListIfDirty(View.java:21579)
        at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4512)
        at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4485)
        at android.view.View.updateDisplayListIfDirty(View.java:21535)
        at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4512)
        at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4485)
        at android.view.View.updateDisplayListIfDirty(View.java:21535)
        at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4512)
        at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4485)
        at android.view.View.updateDisplayListIfDirty(View.java:21535)
        at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4512)
        at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4485)
        at android.view.View.updateDisplayListIfDirty(View.java:21535)
        at android.view.ThreadedRenderer.updateViewTreeDisplayList(ThreadedRenderer.java:534)
        at android.view.ThreadedRenderer.updateRootDisplayList(ThreadedRenderer.java:540)
        at android.view.ThreadedRenderer.draw(ThreadedRenderer.java:616)
        at android.view.ViewRootImpl.draw(ViewRootImpl.java:4525)
        at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:4245)
        at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:3374)
E/AndroidRuntime:     at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:2179)
        at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:8787)
        at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1037)
        at android.view.Choreographer.doCallbacks(Choreographer.java:845)
        at android.view.Choreographer.doFrame(Choreographer.java:780)
        at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1022)
        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:7870)
        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)

To facilitate reproducing the error you can use the following images.

            listOf(
                "https://live.staticflickr.com/65535/52140374025_d4f6fbbb61_z.jpg",
                "https://live.staticflickr.com/65535/52115578994_31a9d3f9d4_z.jpg",
                "https://live.staticflickr.com/65535/52115842270_a8083c1a31_z.jpg",
                "https://live.staticflickr.com/65535/52115842105_2449d96d93_z.jpg",
                "https://live.staticflickr.com/65535/52115578264_38a35faa82_z.jpg",
                "https://live.staticflickr.com/65535/52115332496_e3be093187_z.jpg",
                "https://live.staticflickr.com/65535/52114308052_7d47e89981_z.jpg",
                "https://live.staticflickr.com/65535/52115840490_72571bbaa8_z.jpg",
                "https://live.staticflickr.com/65535/52115840220_46a94fdb1a_z.jpg",
                "https://live.staticflickr.com/65535/52115840005_385b248a30_z.jpg",

                "https://live.staticflickr.com/65535/52115330526_72373e8750_z.jpg",
                "https://live.staticflickr.com/65535/52115371733_d363ef5a9a_z.jpg",
                "https://live.staticflickr.com/65535/52115839350_908cde3ac8_z.jpg",
                "https://live.staticflickr.com/65535/52115373383_f0f534b1e3_z.jpg",
                "https://live.staticflickr.com/65535/52115577404_0902caf6d4_z.jpg",
                "https://live.staticflickr.com/65535/52115840715_8d77dbda48_z.jpg",
                "https://live.staticflickr.com/65535/52114305022_8a55b5b037_z.jpg",
                "https://live.staticflickr.com/65535/52114304832_2e322f70cd_z.jpg",
                "https://live.staticflickr.com/65535/52115328916_59a10be33e_z.jpg",
                "https://live.staticflickr.com/65535/52114304502_a397e38038_z.jpg",

                "https://live.staticflickr.com/65535/52121460286_aa7cb20e1d_z.jpg",
                "https://live.staticflickr.com/65535/52121490528_fbbb1e0aa6_z.jpg",
                "https://live.staticflickr.com/65535/52121959385_ab62aecccb_z.jpg",
                "https://live.staticflickr.com/65535/52121959350_871200e394_z.jpg",
                "https://live.staticflickr.com/65535/52121705619_b0ea6ce267_z.jpg",
                "https://live.staticflickr.com/65535/52121959265_d642b29314_z.jpg",
                "https://live.staticflickr.com/65535/52121959205_75acaa67b0_z.jpg",
                "https://live.staticflickr.com/65535/52121490263_a4d3ea603f_z.jpg",
                "https://live.staticflickr.com/65535/52121490208_22d31dcba9_z.jpg",
                "https://live.staticflickr.com/65535/52121459831_3baeb07703_z.jpg",

                "https://live.staticflickr.com/65535/52121490113_d8e5c9a9b2_z.jpg",
                "https://live.staticflickr.com/65535/52121958980_d8f5eaa067_z.jpg",
                "https://live.staticflickr.com/65535/52121958945_c5818b15e2_z.jpg",
                "https://live.staticflickr.com/65535/52121705284_6a8e5ceba1_z.jpg",
                "https://live.staticflickr.com/65535/52121459606_2ebf4b9939_z.jpg",
                "https://live.staticflickr.com/65535/52121958815_7466a5489d_z.jpg",
                "https://live.staticflickr.com/65535/52121459501_c433bd01d6_z.jpg",
                "https://live.staticflickr.com/65535/52120425527_c8b93f0e39_z.jpg",
                "https://live.staticflickr.com/65535/52121958660_3c84e93a7c_z.jpg",
                "https://live.staticflickr.com/65535/52120425482_dd4e5a234c_z.jpg",
            )

Expected Behavior:

When I use LazyColumn instead of LazyVerticalGrid everything works fine, so I expect a similar behavior just like when I use LazyColumn. Generally the photos are loaded much slower within a LazyVerticalGrid than in a LazyColumn.

    LazyColumn {
        items(imageUrls.value) { imageUrl ->
            Box(modifier = Modifier.aspectRatio(1f)) {
                FrescoImage(
                    imageUrl = imageUrl,
                    contentScale = ContentScale.Crop,
                    placeHolder = ImageBitmap.imageResource(R.drawable.img_placeholder_gallery)
                )
            }
        }
    }

I hope you can understand and reproduce my problem. If you need any more information, just ask.

skydoves commented 2 years ago

Hi, thank you for describing the details.

I have two questions below:

Thanks!

andreas-umbricht commented 2 years ago

When I use the same example with com.github.skydoves:landscapist-glide:1.5.2 everything works as expected. Thanks for the hint!

LazyVerticalGrid(
        columns = GridCells.Fixed(3)) {
        items(imageUrls.value) { imageUrl ->
            Box(modifier = Modifier.aspectRatio(1f)) {
                GlideImage(
                    imageModel = imageUrl,
                    contentScale = ContentScale.Crop
                )
            }
        }
    }

When I use the example with com.github.skydoves:landscapist-coil:1.5.2 it also takes forever to load the images, or they don't get loaded at all. At least it doesn't crash unlike Fresco.

Sadly, I wasn't able to test 1.5.3-SNAPSHOT. I tried implementation "com.github.skydoves:landscapist-fresco:1.5.3-SNAPSHOT". But all I get is Could not find com.github.skydoves:landscapist-fresco:1.5.3-SNAPSHOT. when trying to build the app.

If you can help me to get 1.5.3-SNAPSHOT working I will gladly also test Fresco, Coil and Glide with that. Until then, I will simply use Glide.

skydoves commented 2 years ago

Thank you for checking this! You should add the repository URL below in your gradle.build file:

repositories {
   maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
}

You can see the instruction here: https://github.com/skydoves/landscapist#snapshot

andreas-umbricht commented 2 years ago

Ah, silly me. Thanks for the instruction. Using 1.5.3-SNAPSHOT, Fresco does not crash anymore luckily, but Fresco and Coil still don't load all images or it takes ages to do so. I think, I will stick with Glide for the moment.

skydoves commented 2 years ago

Thanks for checking the details. It seems a library issue for the Coil and Fresco, but let me debug the details and keep you updated on this. Thanks!