Skip to content

Commit 917b5d4

Browse files
committed
Improve WebView file loading by adding resource fallbacks and error handling
- Adjust asset path normalization logic on Android. - Expand candidate paths for loading resources across Android, iOS, and Desktop platforms. - Introduce scoped coroutine for error fallback content in DemoToolsPanel.
1 parent 49b1dee commit 917b5d4

4 files changed

Lines changed: 68 additions & 18 deletions

File tree

demo-shared/src/commonMain/kotlin/io/github/kdroidfilter/webview/demo/DemoToolsPanel.kt

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,16 @@ import androidx.compose.material3.Switch
3131
import androidx.compose.material3.Text
3232
import androidx.compose.material3.TextButton
3333
import androidx.compose.runtime.Composable
34+
import androidx.compose.runtime.rememberCoroutineScope
3435
import androidx.compose.ui.Alignment
3536
import androidx.compose.ui.Modifier
3637
import androidx.compose.ui.unit.dp
38+
import composewebview.demo_shared.generated.resources.Res
3739
import io.github.kdroidfilter.webview.cookie.Cookie
3840
import io.github.kdroidfilter.webview.util.KLogSeverity
39-
import io.github.kdroidfilter.webview.web.WebViewFileReadType
4041
import io.github.kdroidfilter.webview.web.WebViewNavigator
4142
import io.github.kdroidfilter.webview.web.WebViewState
43+
import kotlinx.coroutines.launch
4244

4345
@Composable
4446
@OptIn(ExperimentalLayoutApi::class)
@@ -84,6 +86,7 @@ internal fun DemoToolsPanel(
8486
logSeverity: KLogSeverity,
8587
onSetLogSeverity: (KLogSeverity) -> Unit,
8688
) {
89+
val scope = rememberCoroutineScope()
8790
Surface(modifier = modifier, color = MaterialTheme.colorScheme.surface) {
8891
Column(
8992
modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(12.dp),
@@ -121,7 +124,25 @@ internal fun DemoToolsPanel(
121124
}
122125
FilledTonalButton(
123126
onClick = {
124-
navigator.loadHtmlFile("bridge_playground.html", WebViewFileReadType.ASSET_RESOURCES)
127+
scope.launch {
128+
val html =
129+
runCatching {
130+
Res.readBytes("files/bridge_playground.html").decodeToString()
131+
}.getOrElse { error ->
132+
"""
133+
<!DOCTYPE html>
134+
<html>
135+
<head><title>Error Loading Resource</title></head>
136+
<body>
137+
<h2>Error Loading Resource</h2>
138+
<p>files/bridge_playground.html</p>
139+
<pre>${error.stackTraceToString()}</pre>
140+
</body>
141+
</html>
142+
""".trimIndent()
143+
}
144+
navigator.loadHtml(html)
145+
}
125146
},
126147
) {
127148
Text("Load HTML (file + bridge)")

wrywebview-compose/src/androidMain/kotlin/io/github/kdroidfilter/webview/web/AndroidWebView.kt

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,16 +38,29 @@ internal class AndroidWebView(
3838
override suspend fun loadHtmlFile(fileName: String, readType: WebViewFileReadType) {
3939
KLogger.d(tag = "AndroidWebView") { "loadHtmlFile fileName=$fileName readType=$readType" }
4040
val normalized = fileName.removePrefix("/")
41+
val assetPath = normalized.removePrefix("assets/")
4142
when (readType) {
4243
WebViewFileReadType.ASSET_RESOURCES -> {
43-
// Prefer Compose Multiplatform resources location, fall back to regular android_asset.
44-
val composeFiles = "file:///android_asset/compose-resources/files/$normalized"
45-
val composeAssets = "file:///android_asset/compose-resources/assets/$normalized"
46-
val legacyAssets = "file:///android_asset/$normalized"
47-
webView.loadUrl(composeFiles)
48-
// If the asset doesn't exist, Android will show an error page; callers can opt to use COMPOSE_RESOURCE_FILES.
49-
// Keeping behavior simple and aligned with upstream.
50-
KLogger.d(tag = "AndroidWebView") { "loadUrl $composeFiles (fallbacks: $composeAssets, $legacyAssets)" }
44+
val candidates =
45+
listOf(
46+
"compose-resources/files/$assetPath",
47+
"composeResources/files/$assetPath",
48+
"compose-resources/assets/$assetPath",
49+
"composeResources/assets/$assetPath",
50+
assetPath,
51+
)
52+
val selected =
53+
candidates.firstOrNull { path ->
54+
try {
55+
webView.context.assets.open(path).close()
56+
true
57+
} catch (_: Exception) {
58+
false
59+
}
60+
} ?: candidates.first()
61+
val url = "file:///android_asset/$selected"
62+
webView.loadUrl(url)
63+
KLogger.d(tag = "AndroidWebView") { "loadUrl $url (candidates: ${candidates.joinToString()})" }
5164
}
5265
WebViewFileReadType.COMPOSE_RESOURCE_FILES -> webView.loadUrl(fileName)
5366
}

wrywebview-compose/src/iosMain/kotlin/io/github/kdroidfilter/webview/web/IOSWebView.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,10 +97,14 @@ internal class IOSWebView(
9797
val candidates =
9898
if (normalized.startsWith("compose-resources/") || normalized.startsWith("assets/")) {
9999
listOf(normalized)
100+
} else if (normalized.startsWith("composeResources/")) {
101+
listOf(normalized)
100102
} else {
101103
listOf(
102104
"compose-resources/files/$normalized",
103105
"compose-resources/assets/$normalized",
106+
"composeResources/files/$normalized",
107+
"composeResources/assets/$normalized",
104108
"assets/$normalized",
105109
normalized,
106110
)

wrywebview-compose/src/jvmMain/kotlin/io/github/kdroidfilter/webview/web/DesktopWebView.kt

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -45,15 +45,27 @@ internal class DesktopWebView(
4545
when (readType) {
4646
WebViewFileReadType.ASSET_RESOURCES -> {
4747
val normalized = fileName.removePrefix("/")
48-
val candidates =
49-
listOf(
50-
if (normalized.startsWith("assets/")) normalized else "assets/$normalized",
51-
if (normalized.startsWith("compose-resources/")) normalized else "compose-resources/files/$normalized",
52-
if (normalized.startsWith("compose-resources/")) normalized else "compose-resources/assets/$normalized",
53-
)
54-
val loader = this::class.java.classLoader
48+
val candidates = linkedSetOf<String>()
49+
if (
50+
normalized.startsWith("assets/") ||
51+
normalized.startsWith("compose-resources/") ||
52+
normalized.startsWith("composeResources/")
53+
) {
54+
candidates.add(normalized)
55+
}
56+
candidates.add("assets/$normalized")
57+
candidates.add("compose-resources/files/$normalized")
58+
candidates.add("compose-resources/assets/$normalized")
59+
candidates.add("composeResources/files/$normalized")
60+
candidates.add("composeResources/assets/$normalized")
61+
val loaders = listOfNotNull(Thread.currentThread().contextClassLoader, this::class.java.classLoader)
5562
candidates.asSequence()
56-
.mapNotNull { path -> loader.getResourceAsStream(path)?.use { it.readBytes().toString(Charsets.UTF_8) } }
63+
.mapNotNull { path ->
64+
loaders.asSequence()
65+
.mapNotNull { loader -> loader.getResourceAsStream(path) }
66+
.firstOrNull()
67+
?.use { it.readBytes().toString(Charsets.UTF_8) }
68+
}
5769
.firstOrNull()
5870
?: error("Resource not found: ${candidates.joinToString()}")
5971
}

0 commit comments

Comments
 (0)