Skip to content

Commit af216e4

Browse files
authored
Dispose SwingInteropContainer after closing the scene (#2277)
In `ComposeSceneMediator.dispose()`, we currently call (swing)`interopContainer.dispose()` before disposing the scene, but disposing the scene schedules calls for the interop container, which are then never executed because the mediator is already disposed. Fixes https://youtrack.jetbrains.com/issue/CMP-8679/ComposePanel-doesnt-remove-interop-components-on-own-removal ## Testing Added a unit test ## Release Notes ### Fixes - Desktop - Correctly remove `SwingPanel` children of `ComposePanel`, when the compose panel is itself removed from the hierarchy.
1 parent ec0dfb3 commit af216e4

File tree

2 files changed

+42
-4
lines changed

2 files changed

+42
-4
lines changed

compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/scene/ComposeSceneMediator.desktop.kt

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -490,17 +490,18 @@ internal class ComposeSceneMediator(
490490

491491
unsubscribe(contentComponent)
492492

493-
interopContainer.root.removeContainerListener(interopContainerListener)
494-
// Since rendering will not happen after, we need to execute all scheduled updates
495-
interopContainer.dispose()
496-
497493
container.remove(contentComponent)
498494
container.remove(invisibleComponent)
499495
container.transferHandler = null
500496
container.dropTarget = null
501497

502498
scene.close()
503499
skiaLayerComponent.dispose()
500+
501+
interopContainer.root.removeContainerListener(interopContainerListener)
502+
// Since rendering will not happen after, we need to execute all scheduled updates
503+
interopContainer.dispose()
504+
504505
_onComponentAttached = null
505506
}
506507

compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/awt/ComposePanelTest.kt

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ import javax.swing.JFrame
5353
import javax.swing.JPanel
5454
import junit.framework.TestCase.assertTrue
5555
import kotlin.test.assertEquals
56+
import kotlin.test.assertFalse
5657
import kotlinx.coroutines.delay
5758
import kotlinx.coroutines.runBlocking
5859
import org.jetbrains.skiko.ExperimentalSkikoApi
@@ -508,4 +509,40 @@ class ComposePanelTest {
508509
window.dispose()
509510
}
510511
}
512+
513+
@Test
514+
fun `ComposePanel clears SwingPanels when removed`() = runApplicationTest {
515+
val jPanels = mutableListOf<JPanel>()
516+
val composePanel = ComposePanel()
517+
composePanel.setContent {
518+
SwingPanel(
519+
factory = {
520+
JPanel().also {
521+
it.size = Dimension(100, 100)
522+
jPanels.add(it)
523+
}
524+
},
525+
modifier = Modifier.size(100.dp)
526+
)
527+
}
528+
529+
val window = JFrame()
530+
window.size = Dimension(200, 200)
531+
try {
532+
window.contentPane.add(composePanel, BorderLayout.CENTER)
533+
window.isVisible = true
534+
awaitIdle()
535+
window.contentPane.remove(composePanel)
536+
awaitIdle()
537+
window.contentPane.add(composePanel)
538+
awaitIdle()
539+
540+
assertEquals(2, jPanels.size)
541+
assertFalse(composePanel.isAncestorOf(jPanels[0]))
542+
assertTrue(composePanel.isAncestorOf(jPanels[1]))
543+
assertTrue(jPanels[1].isShowing)
544+
} finally {
545+
window.dispose()
546+
}
547+
}
511548
}

0 commit comments

Comments
 (0)