Hello, it's me again! I'm finalizing my app and I...
# compose-web
n
Hello, it's me again! I'm finalizing my app and I'm testing edge and error usecases. One such use-case is offline browsing. Compose crashes when it tries to display an image while offline. The UI is completely frozen forever if one resource fails to load. Is it expected ? My code and project structure in thread 🧵
I have a Compose Multiplatform Web app gradle module
app
app/build.gradle.kts
Copy code
plugins {
    alias(libs.plugins.kotlinMultiplatform)
    alias(libs.plugins.composeMultiplatform)
    alias(libs.plugins.composeCompiler)
}

kotlin {
    js {
        browser()
        binaries.executable()
    }

    @OptIn(ExperimentalWasmDsl::class)
    wasmJs {
        browser()
        binaries.executable()
    }
    // ... dependencies, etc
}
app/src/webMain/kotlin/main.kt
Copy code
fun main() {
    ComposeViewport {
        AppTheme {
            configureWebResources {
                resourcePathMapping { path -> "./$path" }
            }
        // my Compose app with screens, etc...
    }
}
I have a Ktor module to distribute the web app and to store data (called from the Compose app)
server/build.gradle.kts
Copy code
plugins {
    alias(libs.plugins.kotlinJvm)
    alias(libs.plugins.ktor)
    application
}

group = "foo.bar"
version = "1.0.0"

application {
    mainClass.set("foo.bar.ApplicationKt")

    val isDevelopment: Boolean = project.ext.has("development")
    applicationDefaultJvmArgs = listOf("-Dio.ktor.development=$isDevelopment")
}

dependencies {
    // Ktor libs, etc...
}

ktor {
    development = true
}

val copyResourcesTask = tasks.register<Copy>("copyWebResources") {
    dependsOn(":app:wasmJsBrowserDistribution")
    from(project(":app").file("build/dist/wasmJs/productionExecutable"))
    destinationDir = file("src/main/resources/web-resources")
}

tasks.getByName("processResources") {
    dependsOn(copyResourcesTask)
}

tasks {
    register("stage") {
        dependsOn("installDist")
    }
}
server/src/main/kotlin/foo/bar/Application.kt
Copy code
fun main() {
    embeddedServer(
        factory = Netty,
        port = System.getenv("PORT")?.toIntOrNull() ?: 8081,
        module = Application::module
    ).start(wait = true)
}

suspend fun Application.module() {
    // modules like CORS, ContentNegotiation, etc..

    routing {
        static("/") {
            val staticDir = listOf(
                "../web/build/distributions",
                "web/build/distributions"
            ).map { File(it) }
                .firstOrNull { it.exists() }

            if (staticDir != null) {
                // If staticDir is not null, it means we run "./gradlew server:run" task
                staticRootFolder = staticDir
                files(".")
                default("index.html")
            } else {
                // If staticDir is null, it means we already package all with "./gradlew installDist" task
                resources("web-resources")
                files(".")
                defaultResource("web-resources/index.html")
            }
        }
    }

    // GET and POST configurations used by the app...
}
Does everything there look correct? I can't even find the post where I found the way to distribute the Compose App throught Ktor. Is it the reason why the Compose app crashes / freeze when failing to load a simple icon ? Simply trying to display this Compose while offline freeze the web app :
Copy code
Icon(
    painter = painterResource(Res.drawable.error),
    contentDescription = null,
)
(the Res.drawable.error is inside the
app
composeResources/drawable
folder