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

[Coil 3] Local image files don't show on PC #2014

Open
alekseyHunter opened this issue Jan 6, 2024 · 25 comments
Open

[Coil 3] Local image files don't show on PC #2014

alekseyHunter opened this issue Jan 6, 2024 · 25 comments

Comments

@alekseyHunter
Copy link

Describe the bug
Hello!
Local image files don't show on PC.
Error type is not in the function error()

To Reproduce

val platformContext = LocalPlatformContext.current

val uri = "file:///H:/1.png".toUri()

val request = ImageRequest.Builder(platformContext)
    .data(uri)
    .error {
        println("error")
        null
    }.build()

AsyncImage(
    model = request,
    contentDescription = "",
    contentScale = ContentScale.FillHeight,
    modifier = Modifier.fillMaxSize()
)

Logs/Screenshots
image

image

image

Version
Coil 3.0.0-alpha01

kotlin.version=1.9.21
agp.version=8.1.4
compose.version=1.5.11

@colinrtwhite
Copy link
Member

What's the exception stacktrace from AsyncImage's onError?

@alekseyHunter
Copy link
Author

@colinrtwhite The exception stacktrace is the following:

java.nio.file.InvalidPathException: Illegal char <:> at index 2: /H:/1.png
	at java.base/sun.nio.fs.WindowsPathParser.normalize(WindowsPathParser.java:182)
	at java.base/sun.nio.fs.WindowsPathParser.parse(WindowsPathParser.java:153)
	at java.base/sun.nio.fs.WindowsPathParser.parse(WindowsPathParser.java:77)
	at java.base/sun.nio.fs.WindowsPath.parse(WindowsPath.java:92)
	at java.base/sun.nio.fs.WindowsFileSystem.getPath(WindowsFileSystem.java:232)
	at java.base/java.nio.file.Path.of(Path.java:147)
	at java.base/java.nio.file.Paths.get(Paths.java:69)
	at okio.Path.toNioPath(Path.kt:102)
	at okio.NioSystemFileSystem.metadataOrNull(NioSystemFileSystem.kt:35)
	at okio.internal.-FileSystem.commonMetadata(FileSystem.kt:36)
	at okio.FileSystem.metadata(FileSystem.kt:33)
	at coil3.key.FileUriKeyer.key(FileUriKeyer.kt:16)
	at coil3.key.FileUriKeyer.key(FileUriKeyer.kt:8)
	at coil3.ComponentRegistry.key(ComponentRegistry.kt:56)
	at coil3.memory.MemoryCacheService.newCacheKey(MemoryCacheService.kt:45)
	at coil3.intercept.EngineInterceptor.intercept(EngineInterceptor.kt:52)
	at coil3.intercept.RealInterceptorChain.proceed(RealInterceptorChain.kt:31)
	at coil3.RealImageLoader$executeMain$result$1.invokeSuspend(RealImageLoader.common.kt:136)
	at coil3.RealImageLoader$executeMain$result$1.invoke(RealImageLoader.common.kt)
	at coil3.RealImageLoader$executeMain$result$1.invoke(RealImageLoader.common.kt)
	at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:65)
	at kotlinx.coroutines.BuildersKt__Builders_commonKt.withContext(Builders.common.kt:167)
	at kotlinx.coroutines.BuildersKt.withContext(Unknown Source)
	at coil3.RealImageLoader.executeMain(RealImageLoader.common.kt:127)
	at coil3.RealImageLoader.access$executeMain(RealImageLoader.common.kt:42)
	at coil3.RealImageLoader$executeMain$1.invokeSuspend(RealImageLoader.common.kt)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
	at kotlinx.coroutines.DispatchedTaskKt.resume(DispatchedTask.kt:235)
	at kotlinx.coroutines.DispatchedTaskKt.resumeUnconfined(DispatchedTask.kt:191)
	at kotlinx.coroutines.DispatchedTaskKt.dispatch(DispatchedTask.kt:163)
	at kotlinx.coroutines.CancellableContinuationImpl.dispatchResume(CancellableContinuationImpl.kt:474)
	at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl(CancellableContinuationImpl.kt:508)
	at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl$default(CancellableContinuationImpl.kt:497)
	at kotlinx.coroutines.CancellableContinuationImpl.resumeWith(CancellableContinuationImpl.kt:368)
	at kotlinx.coroutines.flow.StateFlowSlot.makePending(StateFlow.kt:284)
	at kotlinx.coroutines.flow.StateFlowImpl.updateState(StateFlow.kt:349)
	at kotlinx.coroutines.flow.StateFlowImpl.setValue(StateFlow.kt:316)
	at coil3.compose.ConstraintsSizeResolver.measure-3p2s80s(ConstraintsSizeResolver.kt:32)
	at androidx.compose.ui.node.BackwardsCompatNode.measure-3p2s80s(BackwardsCompatNode.kt:311)
	at androidx.compose.ui.node.LayoutModifierNodeCoordinator.measure-BRTryo0(LayoutModifierNodeCoordinator.kt:116)
	at androidx.compose.foundation.layout.FillNode.measure-3p2s80s(Size.kt:698)
	at androidx.compose.ui.node.LayoutModifierNodeCoordinator.measure-BRTryo0(LayoutModifierNodeCoordinator.kt:116)
	at androidx.compose.ui.node.LayoutNodeLayoutDelegate$performMeasure$2.invoke(LayoutNodeLayoutDelegate.kt:1499)
	at androidx.compose.ui.node.LayoutNodeLayoutDelegate$performMeasure$2.invoke(LayoutNodeLayoutDelegate.kt:1495)
	at androidx.compose.runtime.snapshots.Snapshot$Companion.observe(Snapshot.kt:2300)
	at androidx.compose.runtime.snapshots.SnapshotStateObserver$ObservedScopeMap.observe(SnapshotStateObserver.kt:471)
	at androidx.compose.runtime.snapshots.SnapshotStateObserver.observeReads(SnapshotStateObserver.kt:234)
	at androidx.compose.ui.node.OwnerSnapshotObserver.observeReads$ui(OwnerSnapshotObserver.kt:133)
	at androidx.compose.ui.node.OwnerSnapshotObserver.observeMeasureSnapshotReads$ui(OwnerSnapshotObserver.kt:113)
	at androidx.compose.ui.node.LayoutNodeLayoutDelegate.performMeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:1495)
	at androidx.compose.ui.node.LayoutNodeLayoutDelegate.access$performMeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:35)
	at androidx.compose.ui.node.LayoutNodeLayoutDelegate$MeasurePassDelegate.remeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:560)
	at androidx.compose.ui.node.LayoutNodeLayoutDelegate$MeasurePassDelegate.measure-BRTryo0(LayoutNodeLayoutDelegate.kt:539)
	at androidx.compose.foundation.layout.BoxKt$boxMeasurePolicy$1.measure-3p2s80s(Box.kt:114)
	at androidx.compose.ui.node.InnerNodeCoordinator.measure-BRTryo0(InnerNodeCoordinator.kt:126)
	at androidx.compose.ui.graphics.SimpleGraphicsLayerModifier.measure-3p2s80s(GraphicsLayerModifier.kt:646)
	at androidx.compose.ui.node.LayoutModifierNodeCoordinator.measure-BRTryo0(LayoutModifierNodeCoordinator.kt:116)
	at androidx.compose.foundation.layout.SizeNode.measure-3p2s80s(Size.kt:837)
	at androidx.compose.ui.node.LayoutModifierNodeCoordinator.measure-BRTryo0(LayoutModifierNodeCoordinator.kt:116)
	at androidx.compose.foundation.layout.FillNode.measure-3p2s80s(Size.kt:698)
	at androidx.compose.ui.node.LayoutModifierNodeCoordinator.measure-BRTryo0(LayoutModifierNodeCoordinator.kt:116)
	at androidx.compose.foundation.layout.PaddingNode.measure-3p2s80s(Padding.kt:397)
	at androidx.compose.ui.node.LayoutModifierNodeCoordinator.measure-BRTryo0(LayoutModifierNodeCoordinator.kt:116)
	at androidx.compose.ui.node.LayoutNodeLayoutDelegate$performMeasure$2.invoke(LayoutNodeLayoutDelegate.kt:1499)
	at androidx.compose.ui.node.LayoutNodeLayoutDelegate$performMeasure$2.invoke(LayoutNodeLayoutDelegate.kt:1495)
	at androidx.compose.runtime.snapshots.Snapshot$Companion.observe(Snapshot.kt:2300)
	at androidx.compose.runtime.snapshots.SnapshotStateObserver$ObservedScopeMap.observe(SnapshotStateObserver.kt:471)
	at androidx.compose.runtime.snapshots.SnapshotStateObserver.observeReads(SnapshotStateObserver.kt:234)
	at androidx.compose.ui.node.OwnerSnapshotObserver.observeReads$ui(OwnerSnapshotObserver.kt:133)
	at androidx.compose.ui.node.OwnerSnapshotObserver.observeMeasureSnapshotReads$ui(OwnerSnapshotObserver.kt:113)
	at androidx.compose.ui.node.LayoutNodeLayoutDelegate.performMeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:1495)
	at androidx.compose.ui.node.LayoutNodeLayoutDelegate.access$performMeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:35)
	at androidx.compose.ui.node.LayoutNodeLayoutDelegate$MeasurePassDelegate.remeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:560)
	at androidx.compose.ui.node.LayoutNodeLayoutDelegate$MeasurePassDelegate.measure-BRTryo0(LayoutNodeLayoutDelegate.kt:539)
	at androidx.compose.foundation.layout.RowColumnMeasurementHelper.measureWithoutPlacing-_EkL_-Y(RowColumnMeasurementHelper.kt:112)
	at androidx.compose.foundation.layout.RowColumnImplKt$rowColumnMeasurePolicy$1.measure-3p2s80s(RowColumnImpl.kt:72)
	at androidx.compose.ui.node.InnerNodeCoordinator.measure-BRTryo0(InnerNodeCoordinator.kt:126)
	at androidx.compose.foundation.layout.PaddingNode.measure-3p2s80s(Padding.kt:397)
	at androidx.compose.ui.node.LayoutModifierNodeCoordinator.measure-BRTryo0(LayoutModifierNodeCoordinator.kt:116)
	at androidx.compose.ui.node.LayoutNodeLayoutDelegate$performMeasure$2.invoke(LayoutNodeLayoutDelegate.kt:1499)
	at androidx.compose.ui.node.LayoutNodeLayoutDelegate$performMeasure$2.invoke(LayoutNodeLayoutDelegate.kt:1495)
	at androidx.compose.runtime.snapshots.Snapshot$Companion.observe(Snapshot.kt:2300)
	at androidx.compose.runtime.snapshots.SnapshotStateObserver$ObservedScopeMap.observe(SnapshotStateObserver.kt:471)
	at androidx.compose.runtime.snapshots.SnapshotStateObserver.observeReads(SnapshotStateObserver.kt:234)
	at androidx.compose.ui.node.OwnerSnapshotObserver.observeReads$ui(OwnerSnapshotObserver.kt:133)
	at androidx.compose.ui.node.OwnerSnapshotObserver.observeMeasureSnapshotReads$ui(OwnerSnapshotObserver.kt:113)
	at androidx.compose.ui.node.LayoutNodeLayoutDelegate.performMeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:1495)
	at androidx.compose.ui.node.LayoutNodeLayoutDelegate.access$performMeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:35)
	at androidx.compose.ui.node.LayoutNodeLayoutDelegate$MeasurePassDelegate.remeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:560)
	at androidx.compose.ui.node.LayoutNode.remeasure-_Sx5XlM$ui(LayoutNode.kt:1140)
	at androidx.compose.ui.node.LayoutNode.remeasure-_Sx5XlM$ui$default(LayoutNode.kt:1131)
	at androidx.compose.ui.node.MeasureAndLayoutDelegate.doRemeasure-sdFAvZA(MeasureAndLayoutDelegate.kt:323)
	at androidx.compose.ui.node.MeasureAndLayoutDelegate.remeasureAndRelayoutIfNeeded(MeasureAndLayoutDelegate.kt:458)
	at androidx.compose.ui.node.MeasureAndLayoutDelegate.access$remeasureAndRelayoutIfNeeded(MeasureAndLayoutDelegate.kt:39)
	at androidx.compose.ui.node.MeasureAndLayoutDelegate.measureAndLayout(MeasureAndLayoutDelegate.kt:344)
	at androidx.compose.ui.platform.SkiaBasedOwner.measureAndLayout(SkiaBasedOwner.skiko.kt:246)
	at androidx.compose.ui.node.Owner.measureAndLayout$default(Owner.kt:223)
	at androidx.compose.ui.ComposeScene.render(ComposeScene.skiko.kt:546)
	at androidx.compose.ui.awt.ComposeBridge$skikoView$1$onRender$1.invoke(ComposeBridge.desktop.kt:178)
	at androidx.compose.ui.awt.ComposeBridge$skikoView$1$onRender$1.invoke(ComposeBridge.desktop.kt:177)
	at androidx.compose.ui.awt.ComposeBridge.catchExceptions(ComposeBridge.desktop.kt:150)
	at androidx.compose.ui.awt.ComposeBridge.access$catchExceptions(ComposeBridge.desktop.kt:64)
	at androidx.compose.ui.awt.ComposeBridge$skikoView$1.onRender(ComposeBridge.desktop.kt:177)
	at org.jetbrains.skiko.SkiaLayer.update$skiko(SkiaLayer.awt.kt:548)
	at org.jetbrains.skiko.redrawer.AWTRedrawer.update(AWTRedrawer.kt:54)
	at org.jetbrains.skiko.redrawer.Direct3DRedrawer$frameDispatcher$1.invokeSuspend(Direct3DRedrawer.kt:49)
	at org.jetbrains.skiko.redrawer.Direct3DRedrawer$frameDispatcher$1.invoke(Direct3DRedrawer.kt)
	at org.jetbrains.skiko.redrawer.Direct3DRedrawer$frameDispatcher$1.invoke(Direct3DRedrawer.kt)
	at org.jetbrains.skiko.FrameDispatcher$job$1.invokeSuspend(FrameDispatcher.kt:33)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:108)
	at java.desktop/java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:318)
	at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:773)
	at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:720)
	at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:714)
	at java.base/java.security.AccessController.doPrivileged(AccessController.java:399)
	at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:86)
	at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:742)
	at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:203)
	at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:124)
	at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:113)
	at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:109)
	at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
	at java.desktop/java.awt.EventDispatchThread.run(EventDispatchThread.java:90)

@alekseyHunter
Copy link
Author

alekseyHunter commented Jan 11, 2024

@colinrtwhite After updating to version 3.0.0-alpha02 and changing the file path from "file:///H:/1.png" to "H://1.png" the exception stacktrace looks like this:

java.lang.IllegalStateException: Unable to create a fetcher that supports: H:\1.png
	at coil3.intercept.EngineInterceptor.fetch(EngineInterceptor.kt:145)
	at coil3.intercept.EngineInterceptor.execute(EngineInterceptor.kt:109)
	at coil3.intercept.EngineInterceptor.access$execute(EngineInterceptor.kt:29)
	at coil3.intercept.EngineInterceptor$intercept$2.invokeSuspend(EngineInterceptor.kt:63)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:108)
	at kotlinx.coroutines.internal.LimitedDispatcher$Worker.run(LimitedDispatcher.kt:115)
	at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:103)
	at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:589)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:806)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:710)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:697)

@devmike01
Copy link

devmike01 commented Jan 12, 2024

Looks like this has nothing to do with the coil library. /H: is not a valid directory path. Remove the colon : after the H path.

@alekseyHunter
Copy link
Author

@devmike01 This doesn't work, I got another exception - FileNotFoundException ;)

java.io.FileNotFoundException: no such file: /H/1.png
	at okio.internal.-FileSystem.commonMetadata(FileSystem.kt:36)
	at okio.FileSystem.metadata(FileSystem.kt:33)
	at coil3.key.FileUriKeyer.key(FileUriKeyer.kt:16)
	at coil3.key.FileUriKeyer.key(FileUriKeyer.kt:8)
	at coil3.ComponentRegistry.key(ComponentRegistry.kt:66)

@colinrtwhite
Copy link
Member

@alekseyHunter I agree with @devmike01 I think there might be something wrong with your path, though I'm not sure exactly how to represent a Windows drive name in an Okio path. I'd try: file://\H:\\1.png.

There might also be issues with Coil relying on / as Windows uses \ for file paths.

@alekseyHunter
Copy link
Author

@colinrtwhite This doesn't work either (file://\H:\\1.png). I caught the same exception:

java.lang.IllegalStateException: Unable to create a fetcher that supports: file://\H:\\1.png
	at coil3.intercept.EngineInterceptor.fetch(EngineInterceptor.kt:145)
	at coil3.intercept.EngineInterceptor.execute(EngineInterceptor.kt:109)
	at coil3.intercept.EngineInterceptor.access$execute(EngineInterceptor.kt:29)
	at coil3.intercept.EngineInterceptor$intercept$2.invokeSuspend(EngineInterceptor.kt:63)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:108)
	at kotlinx.coroutines.internal.LimitedDispatcher$Worker.run(LimitedDispatcher.kt:115)
	at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:103)
	at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:589)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:806)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:710)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:697)

@devmike01
Copy link

devmike01 commented Jan 19, 2024

@alekseyHunter Fix your error or post the issue on StackOverflow. Try accessing this image(file://\H:\\1.png) file from your browser and see if you are able to do so. I doubt it. Again, this has nothing to do with Coil.

@alekseyHunter
Copy link
Author

alekseyHunter commented Jan 19, 2024

Fix your error or post the issue on StackOverflow.

@devmike01 What's the logic? How to fix the error, if there is an error in the library.

I doubt it. Again, this has nothing to do with Coil.

@devmike01 Can you load a local picture on your computer using the library? Will you show the result on video?

Try accessing this image(file://\H:\1.png) file from your browser and see if you are able to do so. I doubt it.

@devmike01 Everything loads fine.
image
image

@devmike01
Copy link

I meant web browser, not your image browser.

@alekseyHunter
Copy link
Author

@devmike01 Yes, the second screenshot shows the result in the web browser (pixlr.com). Everything works in it ;)

@devmike01
Copy link

The second images is hosted on a server. I meant load the photo from your folder path in your web browser.

@ejektaflex
Copy link

ejektaflex commented Feb 15, 2024

I just had the same issue, it's nice to know that it wasn't me. I'm using 3.0.0-Alpha 04.

When passing in a File, Coil fails to load the image on Jetbrains Compose (JVM) on Windows.

I had to do this to get it to work:

val loc = "file://" + imgFiles[1].absolutePath.replace("\\", "/")

AsyncImage(
  model = loc, contentDescription = "Test Photo"
)

I would assume that we should expect to just pass a File object to Coil, and it would work correctly, right?

It definitely does not seem to like Windows file paths.

@ejektaflex
Copy link

While the above temporary fix does work on my C drive, it also does not work when referencing a file on another drive.

@green-pico
Copy link

green-pico commented Feb 20, 2024

I run into the exact same problem. I am on 3.0.0-alpha04 implementing a KMP project targeting Windows and Android.

The file I want to show on Windows is located at "C:\Users\<user>\AppData\Roaming\<project>\Thumbnails\tool2019fearinoculum.jpg" (c+p from file explorer)

When I run

File("C:/Users/<user>/AppData/Roaming/<project>/Thumbnails/tool2019fearinoculum.jpg").exists()

or

File("C:\\Users\\<user>\\AppData\\Roaming\\<project>\\Thumbnails\\tool2019fearinoculum.jpg").exists()

it returns true. However, when I run one of the above like this

AsyncImage(
    model = File("C:/Users/<user>/AppData/Roaming/<project>/Thumbnails/tool2019fearinoculum.jpg"),
    contentDescription = "null",
    onError = { println(it) }
)

it gives me throwable=java.lang.IllegalStateException: Unable to create a fetcher that supports: file://C:\Users\<user>\AppData\Roaming\<project>\Thumbnails\tool2019fearinoculum.jpg.

I agree with @ejektaflex. If I am not doing anything completely wrong, I would also assume this code to work.

Edit:
Putting file://C:\Users\<user>\AppData\Roaming\<project>\Thumbnails\tool2019fearinoculum.jpg into the browser shows the image. Passing it as the model parameter, however, does not work.

@serandel
Copy link

serandel commented Feb 20, 2024

Same here. Compose for Desktop in a Windows 11 machine.

AsyncImage(
   model = imageFile,
   contentDescription = "test",
   contentScale = ContentScale.Fit,
   modifier = Modifier.fillMaxSize(),
   onError = { println(it.result.throwable)}
)

This shows nothing in my UI, but outputs the following error:

java.lang.IllegalStateException: Unable to create a fetcher that supports: file://.myapp\190f6e6c-e527-456a-84bc-301a62bf5486.png

I'm positive the file is OK, because I'm scanning a folder, and I'm migrating from another image loader that displayed it with no problem. Also, if I pass an URL of a remote image, it loads and displays perfectly fine.

@serandel
Copy link

serandel commented Feb 20, 2024

This workaround is fine in my Windows, but I suppose it will break anywhere else.

val loc = "file://" + imageFile.absolutePath.replace("\\", "/")

@green-pico
Copy link

This workaround is fine in my Windows, but I suppose it will break anywhere else.

val loc = "file://" + imageFile.absolutePath.replace("\\", "/")

Would you mind posting the exact contents of the String that you passed to AsyncImage? My images are located in C:...\AppData\ and I tried all possibilities with \ and / and nothing would work for me...

@serandel
Copy link

This workaround is fine in my Windows, but I suppose it will break anywhere else.

val loc = "file://" + imageFile.absolutePath.replace("\\", "/")

Would you mind posting the exact contents of the String that you passed to AsyncImage? My images are located in C:...\AppData\ and I tried all possibilities with \ and / and nothing would work for me...

Of course, this is the original file: .myapp\190f6e6c-e527-456a-84bc-301a62bf5486.png (I've tried passing both the File and its path as a String) and the workaround is using the String file://.myapp/190f6e6c-e527-456a-84bc-301a62bf5486.png instead.

@green-pico
Copy link

green-pico commented Feb 22, 2024

This workaround is fine in my Windows, but I suppose it will break anywhere else.

val loc = "file://" + imageFile.absolutePath.replace("\\", "/")

Would you mind posting the exact contents of the String that you passed to AsyncImage? My images are located in C:...\AppData\ and I tried all possibilities with \ and / and nothing would work for me...

Of course, this is the original file: .myapp\190f6e6c-e527-456a-84bc-301a62bf5486.png (I've tried passing both the File and its path as a String) and the workaround is using the String file://.myapp/190f6e6c-e527-456a-84bc-301a62bf5486.png instead.

Thank you. What exactly is .myapp? Is it the root directory of your app? I was looking for a workaround where I can load images from the "AppData" folder on the C: drive. Any ideas? Because using the absolute path with the drive letter seems to create the confusion within Coil.

@green-pico
Copy link

I also tried it with Okio itself:

FileSystem.SYSTEM.exists("C:/Users/<user>/AppData/Roaming/<project>/Thumbnails/tool2019fearinoculum.jpg".toPath())

returns true. If I put this Okio path into the AsyncImage like this

AsyncImage(
    model = "C:/Users/<user>/AppData/Roaming/<project>/Thumbnails/tool2019fearinoculum.jpg".toPath(),
    contentDescription = "null",
    onError = { println(it) }
)

it throws java.io.FileNotFoundException: no such file: /Users/<user>/AppData/Roaming/<project>/Thumbnails/tool2019fearinoculum.jpg. It seems to swallow the drive letter thus making the path invalid.

@serandel
Copy link

This workaround is fine in my Windows, but I suppose it will break anywhere else.

val loc = "file://" + imageFile.absolutePath.replace("\\", "/")

Would you mind posting the exact contents of the String that you passed to AsyncImage? My images are located in C:...\AppData\ and I tried all possibilities with \ and / and nothing would work for me...

Of course, this is the original file: .myapp\190f6e6c-e527-456a-84bc-301a62bf5486.png (I've tried passing both the File and its path as a String) and the workaround is using the String file://.myapp/190f6e6c-e527-456a-84bc-301a62bf5486.png instead.

Thank you. What exactly is .myapp? Is it the root directory of your app? I was looking for a workaround where I can load images from the "AppData" folder on the C: drive. Any ideas? Because using the absolute path with the drive letter seems to create the confusion within Coil.

It's just a subfolder of the working directory of my app, nothing special.

@acarlsen
Copy link

acarlsen commented Feb 27, 2024

I have the same issue on Windows. Using workaround below works on C drive only, not on other drive letters.

val loc = "file://" + imageFile.absolutePath.replace("\\", "/")

I think the issue is in FileUriFetcher.kt in Coil.

It has this code:

    override suspend fun fetch(): FetchResult {
        val path = checkNotNull(uri.path) { "path == null" }.toPath()
        return SourceFetchResult(
            source = ImageSource(path, options.fileSystem),
            mimeType = MimeTypeMap.getMimeTypeFromExtension(path.extension),
            dataSource = DataSource.DISK,
        )
    }

For example input file on drive D "file://D:/861746.jpg" the resulting path in this code becomes "/861768.jpg", thus the files location cannot be resolved.

It works for C drive, because that is the default drive.

The solution is the above code should take drive letter into consideration.

EDIT:
I think it might even work with normal windows style path like D:\861768.jp looking and OKIO toPath code, but then the issue is the Coil ComponentRegistry never gets to use the FileUriFetcher..

@acarlsen
Copy link

acarlsen commented Feb 27, 2024

Possible workaround to use normal windows path.

Create a custom fetcher:

internal class WindowsFileUriFetcher(
        private val uri: Uri,
        private val options: Options,
) : Fetcher {

    @OptIn(InternalCoilApi::class)
    override suspend fun fetch(): FetchResult {
        val path = uri.toString().toPath()
        return SourceFetchResult(
                source = ImageSource(path, options.fileSystem),
                mimeType = MimeTypeMap.getMimeTypeFromExtension(path.name.substringAfterLast('.', "")),
                dataSource = DataSource.DISK,
        )
    }

    class Factory : Fetcher.Factory<Uri> {

        private val regex = "^[a-zA-Z]:\\\\.*".toRegex()

        override fun create(
                data: Uri,
                options: Options,
                imageLoader: ImageLoader,
        ): Fetcher? {
            if (hostOs != OS.Windows || !regex.matches(data.toString())) return null
            return WindowsFileUriFetcher(data, options)
        }
    }
}

Register it:

ImageLoader.Builder(context)
            .components {
                add(OkHttpNetworkFetcherFactory())
                add(WindowsFileUriFetcher.Factory())
            }
            .build()

@serandel
Copy link

Is this still happening? Do you need any help creating a patch here?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants