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

Crash when I use JFXPanel #519

Closed
gtf35 opened this issue Mar 19, 2021 · 15 comments
Closed

Crash when I use JFXPanel #519

gtf35 opened this issue Mar 19, 2021 · 15 comments
Assignees

Comments

@gtf35
Copy link

gtf35 commented Mar 19, 2021

I want to use JavaFX in Compose-jb, but Compose-jb only support swing, so I use JFXPanel

But,,,,, kotlin.UninitializedPropertyAccessException: lateinit property layout has not been initialized when I use JFXPanel

My Code

    @Composable
    private fun createMainSwingScreen() {
        SwingPanel(
            background = Color.White,
            modifier = Modifier.fillMaxSize(),
            factory = {
                JFXPanel().also { jfxPanel ->
                    PlatformImpl.startup {
                }
            }
        }
    )
}

Error log

    Exception in thread "AWT-EventQueue-0 @coroutine#5" kotlin.UninitializedPropertyAccessException: lateinit property layout has not been initialized
	at androidx.compose.desktop.ComponentInfo.getLayout(SwingPanel.desktop.kt:113)
	at androidx.compose.desktop.SwingPanel_desktopKt$SwingPanel-euL9pac$$inlined$onGloballyPositioned$1.onGloballyPositioned(OnGloballyPositionedModifier.kt:60)
	at androidx.compose.ui.node.LayoutNode.dispatchOnPositionedCallbacks$ui(LayoutNode.kt:1095)
	at androidx.compose.ui.node.OnPositionedDispatcher.dispatchHierarchy(OnPositionedDispatcher.kt:51)
	at androidx.compose.ui.node.OnPositionedDispatcher.dispatchHierarchy(OnPositionedDispatcher.kt:55)
	at androidx.compose.ui.node.OnPositionedDispatcher.dispatch(OnPositionedDispatcher.kt:44)
	at androidx.compose.ui.node.MeasureAndLayoutDelegate.dispatchOnPositionedCallbacks(MeasureAndLayoutDelegate.kt:253)
	at androidx.compose.ui.node.MeasureAndLayoutDelegate.dispatchOnPositionedCallbacks$default(MeasureAndLayoutDelegate.kt:249)
	at androidx.compose.ui.platform.DesktopOwner.measureAndLayout(DesktopOwner.desktop.kt:227)
	at androidx.compose.ui.platform.DesktopOwner.render(DesktopOwner.desktop.kt:207)
	at androidx.compose.ui.platform.DesktopOwners.onFrame(DesktopOwners.desktop.kt:121)
	at androidx.compose.desktop.ComposeLayer$1.onRender(ComposeLayer.desktop.kt:123)
	at org.jetbrains.skiko.SkiaLayer.update$skiko(SkiaLayer.kt:117)
	at org.jetbrains.skiko.redrawer.Direct3DRedrawer.update(Direct3DRedrawer.kt:42)
	at org.jetbrains.skiko.redrawer.Direct3DRedrawer.redrawImmediately(Direct3DRedrawer.kt:37)
	at org.jetbrains.skiko.SkiaLayer$redraw$1.run(SkiaLayer.kt:82)
	at java.desktop/java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:316)
	at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:770)
	at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:721)
	at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:715)
	at java.base/java.security.AccessController.doPrivileged(AccessController.java:391)
	at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:85)
	at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:740)
	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.WaitDispatchSupport$2.run(WaitDispatchSupport.java:188)
	at java.desktop/java.awt.WaitDispatchSupport$4.run(WaitDispatchSupport.java:235)
	at java.desktop/java.awt.WaitDispatchSupport$4.run(WaitDispatchSupport.java:233)
	at java.base/java.security.AccessController.doPrivileged(AccessController.java:312)
	at java.desktop/java.awt.WaitDispatchSupport.enter(WaitDispatchSupport.java:233)
	at javafx.embed.swing.JFXPanel.initFx(JFXPanel.java:241)
	at javafx.embed.swing.JFXPanel.(JFXPanel.java:267)
	at top.gtf35.nekohelper.ui.file_manager.FileManagerScreen$createMainSwingScreen$1.invoke(FileManagerScreen.kt:92)
	at top.gtf35.nekohelper.ui.file_manager.FileManagerScreen$createMainSwingScreen$1.invoke(FileManagerScreen.kt:91)
	at androidx.compose.desktop.SwingPanel_desktopKt$SwingPanel$4.invoke(SwingPanel.desktop.kt:84)
	at androidx.compose.desktop.SwingPanel_desktopKt$SwingPanel$4.invoke(SwingPanel.desktop.kt:83)
	at androidx.compose.runtime.DisposableEffectImpl.onRemembered(Effects.kt:81)
	at androidx.compose.runtime.ComposerImpl$RememberEventDispatcher.dispatchRememberObservers(Composer.kt:1344)
	at androidx.compose.runtime.ComposerImpl.applyChanges$runtime(Composer.kt:1409)
	at androidx.compose.runtime.CompositionImpl.applyChanges(Composition.kt:414)
	at androidx.compose.runtime.Recomposer.composeInitial$runtime(Recomposer.kt:699)
	at androidx.compose.runtime.CompositionImpl.setContent(Composition.kt:304)
	at androidx.compose.ui.platform.Wrapper_desktopKt.setContent(Wrapper.desktop.kt:39)
	at androidx.compose.desktop.ComposeLayer.initOwner(ComposeLayer.desktop.kt:257)
	at androidx.compose.desktop.ComposeLayer.access$initOwner(ComposeLayer.desktop.kt:50)
	at androidx.compose.desktop.ComposeLayer$Wrapped.init(ComposeLayer.desktop.kt:83)
	at org.jetbrains.skiko.HardwareLayer.checkIsShowing(HardwareLayer.kt:30)
	at org.jetbrains.skiko.HardwareLayer.access$checkIsShowing(HardwareLayer.kt:7)
	at org.jetbrains.skiko.HardwareLayer$1.hierarchyChanged(HardwareLayer.kt:22)
	at java.desktop/java.awt.Component.processHierarchyEvent(Component.java:6781)
	at java.desktop/java.awt.Component.processEvent(Component.java:6400)
	at java.desktop/java.awt.Component.dispatchEventImpl(Component.java:4990)
	at java.desktop/java.awt.Component.dispatchEvent(Component.java:4822)
	at java.desktop/java.awt.Component.createHierarchyEvents(Component.java:5628)
	at java.desktop/java.awt.Container.createHierarchyEvents(Container.java:1466)
	at java.desktop/java.awt.Container.createHierarchyEvents(Container.java:1466)
	at java.desktop/java.awt.Container.createHierarchyEvents(Container.java:1466)
	at java.desktop/java.awt.Container.createHierarchyEvents(Container.java:1466)
	at java.desktop/java.awt.Container.createHierarchyEvents(Container.java:1466)
	at java.desktop/java.awt.Component.show(Component.java:1680)
	at java.desktop/java.awt.Window.show(Window.java:1056)
	at java.desktop/java.awt.Component.show(Component.java:1717)
	at java.desktop/java.awt.Component.setVisible(Component.java:1664)
	at java.desktop/java.awt.Window.setVisible(Window.java:1028)
	at androidx.compose.desktop.ComposeWindow.setVisible(ComposeWindow.desktop.kt:85)
	at androidx.compose.desktop.AppWindow.show(AppWindow.desktop.kt:446)
	at androidx.compose.desktop.AppWindow.show$default(AppWindow.desktop.kt:432)
	at top.gtf35.nekohelper.ui.file_manager.FileManagerScreen.dispatch(FileManagerScreen.kt:78)
	at top.gtf35.nekohelper.ui.file_manager.FileManagerScreen$dispatch$3.invoke(FileManagerScreen.kt)
	at top.gtf35.nekohelper.ui.file_manager.FileManagerScreen$dispatch$3.invoke(FileManagerScreen.kt)
	at androidx.compose.runtime.RecomposeScopeImpl.compose(RecomposeScopeImpl.kt:91)
	at androidx.compose.runtime.ComposerImpl.recomposeToGroupEnd(Composer.kt:2252)
	at androidx.compose.runtime.ComposerImpl.skipCurrentGroup(Composer.kt:2499)
	at androidx.compose.runtime.ComposerImpl.recompose$runtime(Composer.kt:2625)
	at androidx.compose.runtime.CompositionImpl.recompose(Composition.kt:406)
	at androidx.compose.runtime.Recomposer.performRecompose(Recomposer.kt:724)
	at androidx.compose.runtime.Recomposer.access$performRecompose(Recomposer.kt:100)
	at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$2.invoke(Recomposer.kt:437)
	at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$2.invoke(Recomposer.kt:411)
	at androidx.compose.runtime.BroadcastFrameClock$FrameAwaiter.resume(BroadcastFrameClock.kt:41)
	at androidx.compose.runtime.BroadcastFrameClock.sendFrame(BroadcastFrameClock.kt:62)
	at androidx.compose.ui.platform.DesktopOwners.onFrame(DesktopOwners.desktop.kt:116)
	at androidx.compose.desktop.ComposeLayer$1.onRender(ComposeLayer.desktop.kt:123)
	at org.jetbrains.skiko.SkiaLayer.update$skiko(SkiaLayer.kt:117)
	at org.jetbrains.skiko.redrawer.Direct3DRedrawer.update(Direct3DRedrawer.kt:42)
	at org.jetbrains.skiko.redrawer.Direct3DRedrawer.access$update(Direct3DRedrawer.kt:11)
	at org.jetbrains.skiko.redrawer.Direct3DRedrawer$frameDispatcher$1.invokeSuspend(Direct3DRedrawer.kt:20)
	at org.jetbrains.skiko.redrawer.Direct3DRedrawer$frameDispatcher$1.invoke(Direct3DRedrawer.kt)
	at org.jetbrains.skiko.FrameDispatcher$job$1.invokeSuspend(FrameDispatcher.kt:32)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
	at java.desktop/java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:316)
	at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:770)
	at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:721)
	at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:715)
	at java.base/java.security.AccessController.doPrivileged(AccessController.java:391)
	at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:85)
	at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:740)
	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)
@Rsedaikin
Copy link
Contributor

Rsedaikin commented Mar 22, 2021

Hello! It looks like the initialization of the JFXPanel happens before the initialization of the SwingPanel, because both Swing and JavaFX have their own thread to dispatch events. So, if you try the following approach, it should solve your problem:

import androidx.compose.desktop.LocalAppWindow
import androidx.compose.desktop.Window
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.size
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.SideEffect
import androidx.compose.ui.Alignment
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.round
import java.awt.Container
import java.awt.BorderLayout
import javafx.embed.swing.JFXPanel
import javafx.application.Platform
import javafx.scene.Group
import javafx.scene.Scene
import javax.swing.JPanel
import javafx.scene.paint.Color as JFXColor
import javafx.scene.text.Font as JFXFont
import javafx.scene.text.Text as JFXText

fun main() = Window(
        title = "MyApp",
        size = IntSize(600, 550)
) {
    // JavaFX components
    val jfxpanel = remember { JFXPanel() }
    val jfxtext = remember { JFXText() }

    // The current container (depending on how you are using the CFD,
    // this could be ComposeWindow or ComposePanel)
    val container = LocalAppWindow.current.window // ComposeWindow

    val counter = remember { mutableStateOf(0) }
    val inc: () -> Unit = {
        counter.value++
        // update JavaFX text component
        Platform.runLater {
            jfxtext.text = "Welcome JavaFX! ${counter.value}"
        }
    }

    Box(
        modifier = Modifier.fillMaxWidth().height(60.dp).padding(top = 20.dp),
        contentAlignment = Alignment.Center
    ) {
        Text("Counter: ${counter.value}")
    }
        
    Box(
        modifier = Modifier.fillMaxSize(),
        contentAlignment = Alignment.Center
    ) {
        Column(
            modifier = Modifier.padding(top = 80.dp, bottom = 20.dp)
        ) {
            Button("1. Compose Button: increment", inc)
            Spacer(modifier = Modifier.height(20.dp))
            // The "Box" is strictly necessary to properly sizing and positioning the JFXPanel container. 
            Box(
                modifier = Modifier.height(200.dp).fillMaxWidth()
            ) {
                JavaFXPanel(
                    root = container,
                    panel = jfxpanel,
                    // function to initialize JFXPanel, Group, Scene
                    onCreate = {
                        Platform.runLater {
                            val root = Group()
                            val scene = Scene(root, JFXColor.GRAY)
                            jfxtext.x = 40.0
                            jfxtext.y = 40.0
                            jfxtext.font = JFXFont(25.0)
                            jfxtext.text = "Welcome JavaFX! ${counter.value}"
                            root.children.add(jfxtext)
                            jfxpanel.scene = scene
                        }
                    }
                )
            }
            Spacer(modifier = Modifier.height(20.dp))
        }
    }
}

@Composable
fun Button(text: String = "", action: (() -> Unit)? = null) {
    Button(
        modifier = Modifier.size(270.dp, 40.dp),
        onClick = { action?.invoke() }
    ) {
        Text(text)
    }
}

@Composable
public fun JavaFXPanel(
    root: Container,
    panel: JFXPanel,
    onCreate: () -> Unit
) {
    val container = remember { JPanel() }
    val density = LocalDensity.current.density

    Layout(
        content = {},
        modifier = Modifier.onGloballyPositioned { childCoordinates ->
            val coordinates = childCoordinates.parentCoordinates!!
            val location = coordinates.localToWindow(Offset.Zero).round()
            val size = coordinates.size
            container.setBounds(
                (location.x / density).toInt(),
                (location.y / density).toInt(),
                (size.width / density).toInt(),
                (size.height / density).toInt()
            )
            container.validate()
            container.repaint()
        },
        measurePolicy = { _, _ ->
            layout(0, 0) {}
        }
    )

    DisposableEffect(Unit) {
        container.apply {
            setLayout(BorderLayout(0, 0))
            add(panel)
        }
        root.add(container)
        onCreate.invoke()
        onDispose {
            root.remove(container)
        }
    }
}

@gtf35
Copy link
Author

gtf35 commented Mar 22, 2021

Looks so coooool!
Running well!
Thank you very much for providing the code that is easy to reuse 💖
Thanks a lot!

@gtf35 gtf35 closed this as completed Mar 22, 2021
@gtf35
Copy link
Author

gtf35 commented Mar 22, 2021

Can JavaFXPanel be used in each item in the Compose list?

That will create a lot of JFXPanel().

Is it okay to do this?

@Rsedaikin
Copy link
Contributor

I think this is ok, but also I think that if you need to create a list from a large number of JavaFX items, you can use one JavaFXPanel as a container for a JavaFX ListView (or any other suitable component, because I am not very good at JavaFX framework) and fill it with JavaFX components with the proper styling to match your current CFD style.

@gtf35
Copy link
Author

gtf35 commented Mar 22, 2021

I see ~, thanks a lot 💕

@ximplia-paolo-pasianot
Copy link

how do i solve "LocalAppWindow.current.window" in compose 1.0.0-beta5?

@yuchuangu85
Copy link

@Rsedaikin
Thank you .

@TreeFrogApps
Copy link

TreeFrogApps commented Jan 13, 2022

@Rsedaikin

Great work with this example - currently using it with a WebView and Mapbox.js - works very well and I'm pleased with the performance.

I have noticed an implicit problem with using the JavaFXPanel.

Scenerio :

  1. Display a JavaFXPanel composable using above code.
  2. Remove the composable (feasible if replacing window content)
  3. DisposableEffect::onDispose called as expected, JPanel which hosts the JFXPanel removed from the ComponentWindow
  4. Display the JavaFXPanel again back in the same application session.

Expected : JFXPanel displayed as before
Result : JFXPanel is not displayed, only the JFrame

Digging deeper :

It appears the first time you add a JavaFXPanel it will attempt to register a PlatformImpl.FinishListener method call : JFXPanel::registerFinishListener. This is then proxied to PlatformImpl::addListener(finishListener).

So far all good, however when this is called root.remove(container) from the onDispose lambda invocation the JPanel will also notify the JFXPanel that it has been removed from the window. This then invokes JFXPanel::deregisterFinishListener which proxies a call to : PlatformImpl.removeListener(finishListener).

Once this method is called it will remove the listener, which makes sense for the lifecycle of this JFXPanel. However in removing the listener this method is called PlatformImpl::checkIdle. If the PlatformImpl.FinishListener count hits 0 PlatformImpl::tkExit is called and this exits all event loops.

The end result is the compose application is still running but any calls to Platform::runLater do nothing for the rest of the application session.

Workaround :

Register a PlatformImpl.FinishListener manually for the lifetime of the application to prevent the event loop from exiting when finished listeners are unregistered keeping the count of registered listeners at least 1 example :

fun main() = application(exitProcessOnExit = true) {
    val finishListener = object : PlatformImpl.FinishListener {
        override fun idle(implicitExit: Boolean) {}
        override fun exitCalled() {}
    }
    PlatformImpl.addListener(finishListener)

    Window(
        title = "My App",
        icon = icon,
        resizable = false,
        state = WindowState(
            placement = Floating,
            size = size),
        onCloseRequest = {
            PlatformImpl.removeListener(finishListener)
            exitApplication()
         },
        content = { ..... })
}

@theone55
Copy link

It's work when i run the project via IDEA
but after build distributive seems like webview doesn't work at all =( Maybe you know how to fix it?

@gtf35
Copy link
Author

gtf35 commented Mar 30, 2022

It's work when i run the project via IDEA but after build distributive seems like webview doesn't work at all =( Maybe you know how to fix it?

Sorry, I've found that I'll have a lot less trouble using swing , so I've given up on JavaFX

@theone55
Copy link

It's work when i run the project via IDEA but after build distributive seems like webview doesn't work at all =( Maybe you know how to fix it?

Sorry, I've found that I'll have a lot less trouble using swing , so I've given up on JavaFX

but anyway it will use WEbView from JavaFX, or you know some other webview implementations without javafx ? (i wonna create chat with youtube player or site preview inside, but javaXF webview only one solution what i found... =( )

@aro311
Copy link

aro311 commented Oct 7, 2022

It's work when i run the project via IDEA but after build distributive seems like webview doesn't work at all =( Maybe you know how to fix it?

Same behavior happens to me. @theone55 Did you find solutions for this?

@jasonsparc
Copy link

jasonsparc commented Nov 27, 2022

Hi @aro311 @theone55

It seems that the problem was some ClassNotFoundException. However, Compose simply swallows any exception, which is why your app won't crash and it would simply appear that the WebView didn't load.

To fix that, I followed the tutorial at, Configuring included JDK modules | Native distributions & local execution | JetBrains/compose-jb, and as suggested by that tutorial, I ran the suggestModules gradle task on my project. It suggested that I add modules("java.instrument", "java.net.http", "jdk.jfr", "jdk.jsobject", "jdk.unsupported", "jdk.unsupported.desktop", "jdk.xml.dom") under compose.desktop.application.nativeDistributions.


Demo

Using the provided desktop-template, I made the following modifications to ./build.gradle.kts and ./src/main/kotlin/main.kt, and then I added a new file ./src/main/kotlin/JFXWebView.kt (sources below):

For ./build.gradle.kts:

import org.jetbrains.compose.desktop.application.dsl.TargetFormat

plugins {
    kotlin("jvm")
    id("org.jetbrains.compose")
    id("org.openjfx.javafxplugin") version "0.0.13"
}

repositories {
    mavenCentral()
    maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
    google()
}

javafx {
    version = "19"
    modules("javafx.swing", "javafx.web")
}

dependencies {
    // Note, if you develop a library, you should use compose.desktop.common.
    // compose.desktop.currentOs should be used in launcher-sourceSet
    // (in a separate module for demo project and in testMain).
    // With compose.desktop.common you will also lose @Preview functionality
    implementation(compose.desktop.currentOs)
}

compose.desktop {
    application {
        mainClass = "MainKt"
        nativeDistributions {
            modules("java.instrument", "java.net.http", "jdk.jfr", "jdk.jsobject", "jdk.unsupported", "jdk.unsupported.desktop", "jdk.xml.dom")
            targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
            packageName = "KotlinJvmComposeDesktopApplication"
            packageVersion = "1.0.0"
        }
    }
}

For ./src/main/kotlin/main.kt:

import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.ui.Modifier
import androidx.compose.ui.awt.SwingPanel
import androidx.compose.ui.window.singleWindowApplication

fun main() = singleWindowApplication {
     SwingPanel(
          factory = { JFXWebView() },
          modifier = Modifier.fillMaxSize(),
     )
}

For ./src/main/kotlin/JFXWebView.kt:

import javafx.application.Platform
import javafx.embed.swing.JFXPanel
import javafx.scene.Scene
import javafx.scene.web.WebView

/**
  * From, https://stackoverflow.com/a/26028556
  */
class JFXWebView : JFXPanel() {
     init {
          Platform.runLater(::initialiseJavaFXScene)
     }

     private fun initialiseJavaFXScene() {
          val webView = WebView()
          val webEngine = webView.engine
          webEngine.load("https://html5test.com/")
          val scene = Scene(webView)
          setScene(scene)
     }
}

Then run ./gradlew packageDistributionForCurrentOS, install the resulting native distribution, and upon running, you should get something like:

Capture


P.S. Not sure if we should also worry about firewall settings, which may prevent the JavaFX WebView from also working, but then other parts of your app that connects to the internet might also stop working.

Edit: The simplified JFXWebView class is not my original idea. Credits goes to Luke Quinane.

@ionull
Copy link

ionull commented Mar 11, 2023

Any idea Apple Sign In shows "Failed to verify your identity. Tray again." with JFX Webview?

@Kashif-E
Copy link

Kashif-E commented Aug 9, 2023

This is how i am using webviews, but its not getting disposed properly it opens for the first time but the second time it shows blank screen.

@Composable
fun DesktopWebView(
    modifier: Modifier,
    url: String,
) {
    val jPanel: JPanel = remember { JPanel() }
    val jfxPanel = JFXPanel()

    SwingPanel(
        factory = {
            jfxPanel.apply { buildWebView(url) }
            jPanel.add(jfxPanel)
        },
        modifier = modifier,
    )

    DisposableEffect(url) { onDispose { jPanel.remove(jfxPanel) } }
}

private fun JFXPanel.buildWebView(url: String) {

    Platform.runLater {
        val webView = WebView()
        val webEngine = webView.engine

        // Set the user agent to simulate a browser for YouTube
        webEngine.userAgent =
            "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3"

        // Enable JavaScript support for YouTube embed player
        webEngine.isJavaScriptEnabled = true

        // Load the YouTube video using the embed URL
        webEngine.load(url)
        val scene = Scene(webView)
        setScene(scene)
    }
}

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