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

Custom fetcher - Image disappears after scrolling away & returning #629

Closed
timusus opened this issue Jan 11, 2021 · 4 comments
Closed

Custom fetcher - Image disappears after scrolling away & returning #629

timusus opened this issue Jan 11, 2021 · 4 comments
Labels
bug Something isn't working

Comments

@timusus
Copy link

timusus commented Jan 11, 2021

Describe the bug

I have a custom fetcher to retrieve images from files via the Storage Access Framework. These images are displayed in the items of a RecyclerView. The image is successfully loaded, but after scrolling the view off-screen, and then back again, the image occasionally disappears, leaving a blank image view. No placeholder, error or fallback drawable is provided.

Interestingly, this does not occur with other custom fetchers that I'm using.

I have bitmapPoolingEnabled(false) and allowHardware(false). I haven't found anything particularly interesting in the logs. If I had to guess, I think some stale Cancelled or Failed event is causing this ViewHolder's ImageView to have its image cleared. Perhaps a race condition related to recycling?

The fetcher looks like so:

class DirectorySongFetcher(private val context: Context) : Fetcher<Song> {

    private val pattern by lazy { Pattern.compile("(folder|cover|album).*\\.(jpg|jpeg|png)", Pattern.CASE_INSENSITIVE) }

    override suspend fun fetch(pool: BitmapPool, data: Song, size: Size, options: Options): FetchResult {
        val parentDocumentFile = if (DocumentsContract.isDocumentUri(context, data.path.toUri())) {
            val parent = data.path.substringBeforeLast("%2F", "")
            if (parent.isNotEmpty()) {
                DocumentFile.fromTreeUri(context, parent.toUri())
            } else {
                null
            }
        } else {
            File(data.path).parentFile?.let { parent ->
                DocumentFile.fromFile(parent)
            }
        }
        
        try {
            parentDocumentFile?.listFiles()
                ?.filter {
                    it.type?.startsWith("image") == true
                            && it.length() > 1024
                            && pattern.matcher(it.name ?: "").matches()
                }
                ?.maxByOrNull { it.length() }
                ?.let { documentFile ->
                    context.contentResolver.openInputStream(documentFile.uri)?.let { inputStream ->
                        return SourceResult(
                            inputStream.source().buffer(),
                            documentFile.type,
                            DataSource.DISK
                        )
                    }
                }
        } catch (e: UnsupportedOperationException) {
            Timber.i("Failed to list files.")
        }

        throw IllegalArgumentException("Failed to find image")
    }

    override fun key(data: Song): String? {
        return "${data.albumArtist}:${data.album}"
    }
}

Expected behavior

When scrolling the RecyclerView back towards an item which is in the memory cache, the item should be displayed, and remain on display without disappearing.

To Reproduce
I don't have a small project to reproduce the issue at this stage, but I can provide access to the private repo on request. You'd need to have some local images stored on disk somewhere.

Logs/Screenshots

A snippet of the logs, taken after having loaded the image successfully, the between scrolling until it was off screen, andd scrolling back again:

2021-01-11 22:45:54.161 1992-1992/com.simplecityapps.shuttle.dev I/RealImageLoader: 🧠 Successful (MEMORY_CACHE) - Album(name='Add Violence', albumArtist='Nine Inch Nails', songCount=10, duration=1635144, year=0, playCount=0, sortKey=Add Violence)
2021-01-11 22:45:54.169 1992-1992/com.simplecityapps.shuttle.dev I/chatty: uid=10317(com.simplecityapps.shuttle.dev) identical 1 line
2021-01-11 22:45:54.170 1992-1992/com.simplecityapps.shuttle.dev I/RealImageLoader: 🧠 Successful (MEMORY_CACHE) - Album(name='Add Violence', albumArtist='Nine Inch Nails', songCount=10, duration=1635144, year=0, playCount=0, sortKey=Add Violence)
2021-01-11 22:45:54.172 1992-1992/com.simplecityapps.shuttle.dev I/RealImageLoader: 🏗  Cancelled - Album(name='10,000 Days', albumArtist='Tool', songCount=22, duration=4551419, year=0, playCount=0, sortKey=10000 Days)
2021-01-11 22:45:54.180 1992-1992/com.simplecityapps.shuttle.dev I/RealImageLoader: 🏗  Cancelled - Album(name='10,000 Days', albumArtist='Tool', songCount=22, duration=4551419, year=0, playCount=0, sortKey=10000 Days)
2021-01-11 22:45:54.310 1992-1992/com.simplecityapps.shuttle.dev I/RealImageLoader: 🚨 Failed - Album(name='Hungry Ghost', albumArtist='Violent Soho', songCount=22, duration=2671177, year=0, playCount=0, sortKey=Hungry Ghost) - java.lang.IllegalArgumentException: Failed to find image
2021-01-11 22:45:54.310 1992-1992/com.simplecityapps.shuttle.dev E/RealImageLoader: java.lang.IllegalArgumentException: Failed to find image
        at au.com.simplecityapps.shuttle.imageloading.coil.fetchers.DirectorySongFetcher.fetch(DirectorySongFetcher.kt:72)
        at au.com.simplecityapps.shuttle.imageloading.coil.fetchers.DirectoryAlbumFetcher.fetch(DirectoryAlbumFetcher.kt:20)
        at au.com.simplecityapps.shuttle.imageloading.coil.fetchers.DirectoryAlbumFetcher.fetch(DirectoryAlbumFetcher.kt:13)
        at coil.intercept.EngineInterceptor$intercept$2.invokeSuspend(EngineInterceptor.kt:403)
        at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
        at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
        at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:750)
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
2021-01-11 22:45:54.345 1992-1992/com.simplecityapps.shuttle.dev I/RealImageLoader: 🚨 Failed - Album(name='Chocolates & Cigarettes', albumArtist='Angus & Julia Stone', songCount=12, duration=1733504, year=2007, playCount=0, sortKey=Chocolates & Cigarettes) - java.lang.IllegalArgumentException: Failed to find image

'Add Violence' is the name of the Album in question.

Interestingly, there were 3 successfuls memory cache hits in the span of 10ms, starting at 2021-01-11 22:45:54.161.

I've attached a screen recording of the issue:

device-2021-01-11-224947.mp4

Version
Library version: Coil v1.1.0
Device: Pixel 5
OS: Android 11
Compiled against API 30.

@timusus timusus added the bug Something isn't working label Jan 11, 2021
@timusus
Copy link
Author

timusus commented Jan 12, 2021

I was able to resolve this by wrapping the contents of the fetch() call with withContext(Dispatchers.Default). This was just a stab in the dark, and I'm not sure why this solves the problem..

@colinrtwhite
Copy link
Member

colinrtwhite commented Jan 14, 2021

Interesting - in the logs you posted java.lang.IllegalArgumentException: Failed to find image comes up a couple times. Is that expected? Also can you confirm when you scroll down up the requests are being served from the memory cache (according to the logs)?

@colinrtwhite
Copy link
Member

Going to close this out. It looks like java.lang.IllegalArgumentException: Failed to find image might be related to your missing images. Either way it's not possible to debug the issue without a small sample project that reproduces the bug.

@timusus
Copy link
Author

timusus commented Feb 8, 2021

Sorry I didn't get back to you.

The IllegalArgumentException is expected, and logs in the instance where an image cannot be found - this is not related to images which are found and then suddenly dissappear.

If I find the time to write a sample project with the custom fetchers, I'll let you know :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants