compose-multiplatform
compose-multiplatform copied to clipboard
Document how to show OS native splash screen
Final result
https://github.com/JetBrains/compose-multiplatform/assets/29678011/e5120290-ec79-43ab-a684-4d3ff244d52e
Problem
Compose (Java) applications usually take a little time to start. I did not find any guide about how to show a native splash screen (not an "artificial" app splash screen) before the app is ready, so wanted to mention it here. Feel free to close the issue.
Solution
It seems Java supports showing operating system native splash screen while the app is being started: https://docs.oracle.com/javase/tutorial/uiswing/misc/splashscreen.html I only tested it on Windows.
So, in the build script I added this:
compose.desktop {
application {
nativeDistributions {
appResourcesRootDir = (rootDir.toPath() / "assets").toFile()
// ...
}
// The image path is relative to the app (Java) working directory
// which is the app installation root directory (where the .exe file resides)
jvmArgs += "-splash:app/resources/splash-screen.gif"
// ...
}
}
It uses the splash-screen.gif image placed in <PROJECT_DIR>/assets/common/
directory.
See Adding files to packaged applications for more information.
The splash screen works when the app is launched with its installed exe or one of run*Distributable
tasks.
To also set the splash for application fat/uber jar (created with one of package*UberJar*
tasks),
copy the splash image into classpath (like src/main/resources
directory)
and update all the Jar tasks in the build file like below:
tasks.withType<org.gradle.jvm.tasks.Jar> {
manifest {
attributes["SplashScreen-Image"] = "image.gif"
}
}
Sidenote
Probably related issue: https://github.com/JetBrains/compose-multiplatform/issues/200
It seems that Windows does not respect the GIF speed and no-loop settings and plays the animation at a low frame rate. See https://stackoverflow.com/q/25382400
In my project for a Windows Desktop app I got the splash screen working by adding the following, based on the above:
project-root
│
├── common
│ ├── src
│ │ ├── commonMain
│ └── build.gradle.kts
│
├── desktop
│ ├── assets
│ │ ├── common
│ │ │ ├── loading.gif // added gif here
│ ├── src
│ │ ├── jvmMain
│ │ │ ├── kotlin
│ │ └── ...
│ └── build.gradle.kts
├── build.gradle.kts
│ ...
In my desktop/build.gradle.kts
I added:
compose.desktop {
application {
mainClass = "MainKt"
nativeDistributions {
appResourcesRootDir.set(project.layout.projectDirectory.dir("assets")
jvmArgs += "-splash:app/resources/loading.gif"
// etc.
}
}
}
I agree that it would be helpful to get this added to the documentation!
To make it work whatever is the working directory when invoking the app I had to use:
jvmArgs += "-splash:${'$'}APPDIR/resources/splash.png"
@eskatos
jvmArgs += "-splash:${'$'}APPDIR/resources/splash.png"
this doesn't work for me. where (or when) is $APPDIR set?
is there any solution to show the splash screen on all platforms when started from an installed application (msi, exe, dmg, deb, rpm) ? none of the above seem to work
I think using a simple (undecorated) frame works. JVM itself doesn't take long. Even on my Virtual Windows (which takes 1-2 seconds to load the calculator app), the splash screen appears in less than a second.
Put the splash screen and icon in the resources directory common file as per the docs on adding files.
package com.ustadmobile.port.desktop
import java.awt.Dimension
import java.awt.Frame
import java.awt.Graphics
import java.io.File
import javax.imageio.ImageIO
/**
* Display a splash screen. We can't use the SplashScreen API because conveyor uses its own loader.
*/
class SplashScreen: Frame() {
private val splashDir = File(System.getProperty("compose.application.resources.dir"), "splash")
private val iconDir = File(System.getProperty("compose.application.resources.dir"), "icon")
private val splashImage = ImageIO.read(File(splashDir, "splash.png"))
private val iconImage = ImageIO.read(File(iconDir, "icon-512.png"))
init {
preferredSize = Dimension(splashImage.width, splashImage.height)
isUndecorated = true
pack()
setLocationRelativeTo(null)
setIconImage(iconImage)
isVisible = true
}
override fun paint(g: Graphics) {
super.paint(g)
g.drawImage(splashImage, 0, 0, this)
}
fun close() {
isVisible = false
}
}
Then in your application
fun main() {
var splashScreen: SplashScreen? = SplashScreen()
application {
...
//When ready (e.g. inside Window composable)
LaunchedEffect(Unit) {
splashScreen?.close()
splashScreen = null
}
}
@zoff99 APPDIR
environment variable is set by the jpackage
launcher. The splash screen will only show up when running the application distributable, not when running using the run
task or from a uber-jar.
To be more complete here's my Gradle setup:
compose.desktop {
application {
nativeDistributions {
appResourcesRootDir = layout.projectDirectory.dir("src/desktopMain/assets")
jvmArgs += "-splash:${'$'}APPDIR/resources/splash.png"
}
}
}
And my splash screen file is stored at src/desktopMain/assets/common/splash.png
.
When building the distributable the splash image ends up in build/compose/binaries/main/app/<your-app-name>/lib/app/resources/splash.png
hm maybe it's because my resourceroot dir ist set differently, and my directory structure is different
https://github.com/Zoxcore/trifa_material/blob/9054bdba7232bda66c8a9ad43c1ab6a1a9ce1fdd/build.gradle.kts#L118
but even then it does not work for me. i tried many combinations, also this:
https://github.com/Zoxcore/trifa_material/blob/master/build.gradle.kts#L156
what does "running the application distributable" mean exactly? does it mean install .deb and run? if so, thats not working. can i see (or log) at runtime what value is set for $APPDIR?
https://bugs.openjdk.org/browse/JDK-8231910 They are only expanded when used in --java-options, not when used in --arguments how do i add java options to the build.gradle.kts file?
@zof99 There is documentation available on appResourcesRootDir. To debug your case I'd suggest trying the following:
Included resources can be accessed via compose.application.resources.dir system property:
import java.io.File val resourcesDir = File(System.getProperty("compose.application.resources.dir")) fun main() { println(resourcesDir.resolve("resource.txt").readText()) }
You could probably figure out which directory is being set and what it's contents are using the above.
I didn't figure out how to get my splash gif to show up when just using compose desktop run
in development mode, but with my above layout it does show up when I have installed the app with in an exe or msi build and start it up.
its a horror. why is something like this not properly working out of the box in 2024?