diff --git a/Sources/SkipSyntax/Kotlin/KotlinBridgeToKotlinVisitor.swift b/Sources/SkipSyntax/Kotlin/KotlinBridgeToKotlinVisitor.swift index 4c854378..95b6592f 100644 --- a/Sources/SkipSyntax/Kotlin/KotlinBridgeToKotlinVisitor.swift +++ b/Sources/SkipSyntax/Kotlin/KotlinBridgeToKotlinVisitor.swift @@ -13,6 +13,7 @@ final class KotlinBridgeToKotlinVisitor { private let includesUI: Bool private var swiftDefinitions: [SwiftDefinition] = [] private var cdeclFunctions: [CDeclFunction] = [] + private var needsObservationImport = false init?(for syntaxTree: KotlinSyntaxTree, options: KotlinBridgeOptions, translator: KotlinTranslator) { guard syntaxTree.isBridgeFile, !syntaxTree.root.isInIfSkipBlock, let codebaseInfo = translator.codebaseInfo else { @@ -121,8 +122,12 @@ final class KotlinBridgeToKotlinVisitor { } let swiftDefinitions = self.swiftDefinitions let cdeclFunctions = self.cdeclFunctions + let needsObservationImport = self.needsObservationImport let outputNode = SwiftDefinition { output, indentation, _ in output.append("import SkipBridge\n\n") + if needsObservationImport, !importDeclarations.contains(where: { $0.modulePath == ["Observation"] }) { + output.append("import Observation\n") + } for importDeclaration in importDeclarations { let path = importDeclaration.modulePath.joined(separator: ".") output.append(indentation).append("import ").append(path).append("\n") @@ -1047,6 +1052,9 @@ final class KotlinBridgeToKotlinVisitor { } classDeclaration.inherits = mappedInherits + if swiftUIType == .view { + classDeclaration.inherits.append(.named("skip.ui.Renderable", [])) + } classDeclaration.extras = Self.bridgeExtras(classDeclaration.extras) if clearSuperclassCall { classDeclaration.superclassCall = nil @@ -1757,10 +1765,34 @@ final class KotlinBridgeToKotlinVisitor { private func swiftUIBodyImplementation(_ swiftUIType: TypeSignature.SwiftUIType, for classDeclaration: KotlinClassDeclaration, visibility: Modifiers.Visibility) -> (statements: [KotlinStatement], swift: [String], cdeclFunctions: [CDeclFunction]) { let classType = ClassType(classDeclaration) let externalName = "Swift_composableBody" - let externalParameters = swiftUIType == .view || swiftUIType == .toolbarContent ? classType.peerExternalParameter : "\(classType.peerExternalParameter), content: skip.ui.View" + var externalParameters = swiftUIType == .view || swiftUIType == .toolbarContent ? classType.peerExternalParameter : "\(classType.peerExternalParameter), content: skip.ui.View" + externalParameters += ", onChange: () -> Unit" let externalArguments = swiftUIType == .view || swiftUIType == .toolbarContent ? classType.peerExternalArgument : "\(classType.peerExternalArgument), content" let externalSourceCode = "private external fun \(externalName)(\(externalParameters)): skip.ui.View?" - let functionSourceCode = "return skip.ui.ComposeBuilder { composectx: skip.ui.ComposeContext -> \(externalName)(\(externalArguments))?.Compose(composectx) ?: skip.ui.ComposeResult.ok }" + let bodyFunctionSourceCode: [String] + let renderFunctionSourceCode: [String]? + if swiftUIType == .view { + bodyFunctionSourceCode = [ + "return skip.ui.ComposeBuilder { composectx: skip.ui.ComposeContext ->", + " Render(composectx)", + " skip.ui.ComposeResult.ok", + "}", + ] + renderFunctionSourceCode = [ + "val observationInvalidation = androidx.compose.runtime.remember { androidx.compose.runtime.mutableStateOf(0) }", + "observationInvalidation.value", + "\(externalName)(\(externalArguments), onChange = { observationInvalidation.value += 1 })?.Compose(context)", + ] + } else { + bodyFunctionSourceCode = [ + "return skip.ui.ComposeBuilder { composectx: skip.ui.ComposeContext ->", + " val observationInvalidation = androidx.compose.runtime.remember { androidx.compose.runtime.mutableStateOf(0) }", + " observationInvalidation.value", + " \(externalName)(\(externalArguments), onChange = { observationInvalidation.value += 1 })?.Compose(composectx) ?: skip.ui.ComposeResult.ok", + "}", + ] + renderFunctionSourceCode = nil + } let externalFunctionDeclaration = KotlinRawStatement(sourceCode: externalSourceCode) let functionDeclaration = KotlinFunctionDeclaration(name: "body") @@ -1770,10 +1802,25 @@ final class KotlinBridgeToKotlinVisitor { functionDeclaration.returnType = .skipUIView functionDeclaration.modifiers = Modifiers(visibility: .public, isOverride: true) functionDeclaration.extras = .singleNewline - functionDeclaration.body = KotlinCodeBlock(statements: [KotlinRawStatement(sourceCode: functionSourceCode)]) + functionDeclaration.body = KotlinCodeBlock(statements: bodyFunctionSourceCode.map { KotlinRawStatement(sourceCode: $0) }) functionDeclaration.body?.disallowSingleStatementAppend = true functionDeclaration.parent = classDeclaration + let renderFunctionDeclaration: KotlinFunctionDeclaration? + if let renderFunctionSourceCode { + let declaration = KotlinFunctionDeclaration(name: "Render") + declaration.parameters = [Parameter(externalLabel: "context", declaredType: .named("skip.ui.ComposeContext", []))] + declaration.modifiers = Modifiers(visibility: .public, isOverride: true) + declaration.attributes.attributes.append(Attribute(signature: .named("androidx.compose.runtime.Composable", []))) + declaration.extras = .singleNewline + declaration.body = KotlinCodeBlock(statements: renderFunctionSourceCode.map { KotlinRawStatement(sourceCode: $0) }) + declaration.body?.disallowSingleStatementAppend = true + declaration.parent = classDeclaration + renderFunctionDeclaration = declaration + } else { + renderFunctionDeclaration = nil + } + var swift: [String] = [] let visibilityString = visibility.swift(suffix: " ") if swiftUIType == .view || swiftUIType == .toolbarContent { @@ -1789,30 +1836,38 @@ final class KotlinBridgeToKotlinVisitor { if swiftUIType != .view && swiftUIType != .toolbarContent { cdeclParameters.append(TypeSignature.Parameter(label: "content", type: .javaObjectPointer)) } + cdeclParameters.append(TypeSignature.Parameter(label: "onChange", type: .javaObjectPointer)) let cdeclSignature: TypeSignature = .function(cdeclParameters, .optional(.javaObjectPointer), APIFlags(), nil) var cdeclSource = classType.peerSwiftAssignment(to: classDeclaration, optionsString: "[]") - let bodyInvocation: String + let bodyExpression: String if swiftUIType == .view || swiftUIType == .toolbarContent { if classType == .generic { - bodyInvocation = "let body = \(classType.peerSwiftTarget).body()" + bodyExpression = "\(classType.peerSwiftTarget).body()" } else { - bodyInvocation = "let body = \(classType.peerSwiftTarget).body" + bodyExpression = "\(classType.peerSwiftTarget).body" } } else { cdeclSource.append("let content_swift = JavaBackedView(content)!") if classType == .generic { - bodyInvocation = "let body = \(classType.peerSwiftTarget).body(content_swift)" + bodyExpression = "\(classType.peerSwiftTarget).body(content_swift)" } else { - bodyInvocation = "let body = \(classType.peerSwiftTarget).body(content: content_swift)" + bodyExpression = "\(classType.peerSwiftTarget).body(content: content_swift)" } } + needsObservationImport = true + cdeclSource.append("let onChange_swift = SwiftClosure0.closure(forJavaObject: onChange, options: [])! as @Sendable () -> Void") cdeclSource.append("return SkipBridge.assumeMainActorUnchecked {") - cdeclSource.append(1, bodyInvocation) + cdeclSource.append(1, "let body = withObservationTracking {") + cdeclSource.append(2, "return \(bodyExpression)") + cdeclSource.append(1, "} onChange: {") + cdeclSource.append(2, "onChange_swift()") + cdeclSource.append(1, "}") cdeclSource.append(1, "return ((body as? SkipUIBridging)?.Java_view as? JConvertible)?.toJavaObject(options: [])") cdeclSource.append("}") let cdeclFunction = CDeclFunction(name: cdeclName, cdecl: cdecl, signature: cdeclSignature, body: cdeclSource) - return ([functionDeclaration, externalFunctionDeclaration], swift, [cdeclFunction]) + let declarations = [functionDeclaration] + (renderFunctionDeclaration.map { [$0] } ?? []) + [externalFunctionDeclaration] + return (declarations, swift, [cdeclFunction]) } } diff --git a/Tests/SkipSyntaxTests/BridgeToKotlinTests.swift b/Tests/SkipSyntaxTests/BridgeToKotlinTests.swift index c5167ed1..7bde97f2 100644 --- a/Tests/SkipSyntaxTests/BridgeToKotlinTests.swift +++ b/Tests/SkipSyntaxTests/BridgeToKotlinTests.swift @@ -7296,7 +7296,7 @@ final class BridgeToKotlinTests: XCTestCase { } } """, kotlin: """ - class V: skip.ui.View, skip.bridge.SwiftPeerBridged, skip.lib.SwiftProjecting { + class V: skip.ui.View, skip.ui.Renderable, skip.bridge.SwiftPeerBridged, skip.lib.SwiftProjecting { var Swift_peer: skip.bridge.SwiftObjectPointer = skip.bridge.SwiftObjectNil constructor(Swift_peer: skip.bridge.SwiftObjectPointer, marker: skip.bridge.SwiftPeerMarker?) { @@ -7319,9 +7319,19 @@ final class BridgeToKotlinTests: XCTestCase { override fun hashCode(): Int = Swift_peer.hashCode() override fun body(): skip.ui.View { - return skip.ui.ComposeBuilder { composectx: skip.ui.ComposeContext -> Swift_composableBody(Swift_peer)?.Compose(composectx) ?: skip.ui.ComposeResult.ok } + return skip.ui.ComposeBuilder { composectx: skip.ui.ComposeContext -> + Render(composectx) + skip.ui.ComposeResult.ok + } + } + + @androidx.compose.runtime.Composable + override fun Render(context: skip.ui.ComposeContext) { + val observationInvalidation = androidx.compose.runtime.remember { androidx.compose.runtime.mutableStateOf(0) } + observationInvalidation.value + Swift_composableBody(Swift_peer, onChange = { observationInvalidation.value += 1 })?.Compose(context) } - private external fun Swift_composableBody(Swift_peer: skip.bridge.SwiftObjectPointer): skip.ui.View? + private external fun Swift_composableBody(Swift_peer: skip.bridge.SwiftObjectPointer, onChange: () -> Unit): skip.ui.View? val i: Int get() = Swift_i(Swift_peer) @@ -7339,6 +7349,7 @@ final class BridgeToKotlinTests: XCTestCase { } """, swiftBridgeSupport: """ + import Observation import SkipFuseUI extension V: BridgedToKotlin, SkipUIBridging, SkipUI.View { nonisolated private static let Java_class = try! JClass(name: "V") @@ -7383,10 +7394,15 @@ final class BridgeToKotlinTests: XCTestCase { return SwiftClosure0.javaObject(for: factory, options: [])! } @_cdecl("Java_V_Swift_1composableBody") - public func V_Swift_composableBody(_ Java_env: JNIEnvPointer, _ Java_target: JavaObjectPointer, _ Swift_peer: SwiftObjectPointer) -> JavaObjectPointer? { + public func V_Swift_composableBody(_ Java_env: JNIEnvPointer, _ Java_target: JavaObjectPointer, _ Swift_peer: SwiftObjectPointer, _ onChange: JavaObjectPointer) -> JavaObjectPointer? { let peer_swift: SwiftValueTypeBox = Swift_peer.pointee()! + let onChange_swift = SwiftClosure0.closure(forJavaObject: onChange, options: [])! as @Sendable () -> Void return SkipBridge.assumeMainActorUnchecked { - let body = peer_swift.value.body + let body = withObservationTracking { + return peer_swift.value.body + } onChange: { + onChange_swift() + } return ((body as? SkipUIBridging)?.Java_view as? JConvertible)?.toJavaObject(options: []) } } @@ -7409,7 +7425,7 @@ final class BridgeToKotlinTests: XCTestCase { } } """, kotlin: """ - internal class V: skip.ui.View, skip.bridge.SwiftPeerBridged, skip.lib.SwiftProjecting { + internal class V: skip.ui.View, skip.ui.Renderable, skip.bridge.SwiftPeerBridged, skip.lib.SwiftProjecting { var Swift_peer: skip.bridge.SwiftObjectPointer = skip.bridge.SwiftObjectNil constructor(Swift_peer: skip.bridge.SwiftObjectPointer, marker: skip.bridge.SwiftPeerMarker?) { @@ -7441,15 +7457,26 @@ final class BridgeToKotlinTests: XCTestCase { private external fun Swift_syncState_count(Swift_peer: skip.bridge.SwiftObjectPointer, support: skip.ui.StateSupport) override fun body(): skip.ui.View { - return skip.ui.ComposeBuilder { composectx: skip.ui.ComposeContext -> Swift_composableBody(Swift_peer)?.Compose(composectx) ?: skip.ui.ComposeResult.ok } + return skip.ui.ComposeBuilder { composectx: skip.ui.ComposeContext -> + Render(composectx) + skip.ui.ComposeResult.ok + } + } + + @androidx.compose.runtime.Composable + override fun Render(context: skip.ui.ComposeContext) { + val observationInvalidation = androidx.compose.runtime.remember { androidx.compose.runtime.mutableStateOf(0) } + observationInvalidation.value + Swift_composableBody(Swift_peer, onChange = { observationInvalidation.value += 1 })?.Compose(context) } - private external fun Swift_composableBody(Swift_peer: skip.bridge.SwiftObjectPointer): skip.ui.View? + private external fun Swift_composableBody(Swift_peer: skip.bridge.SwiftObjectPointer, onChange: () -> Unit): skip.ui.View? override fun Swift_projection(options: Int): () -> Any = Swift_projectionImpl(options) private external fun Swift_projectionImpl(options: Int): () -> Any } """, swiftBridgeSupport: """ + import Observation import SkipFuseUI extension V: BridgedToKotlin, SkipUIBridging, SkipUI.View { nonisolated private static let Java_class = try! JClass(name: "V") @@ -7502,10 +7529,15 @@ final class BridgeToKotlinTests: XCTestCase { } } @_cdecl("Java_V_Swift_1composableBody") - public func V_Swift_composableBody(_ Java_env: JNIEnvPointer, _ Java_target: JavaObjectPointer, _ Swift_peer: SwiftObjectPointer) -> JavaObjectPointer? { + public func V_Swift_composableBody(_ Java_env: JNIEnvPointer, _ Java_target: JavaObjectPointer, _ Swift_peer: SwiftObjectPointer, _ onChange: JavaObjectPointer) -> JavaObjectPointer? { let peer_swift: SwiftValueTypeBox = Swift_peer.pointee()! + let onChange_swift = SwiftClosure0.closure(forJavaObject: onChange, options: [])! as @Sendable () -> Void return SkipBridge.assumeMainActorUnchecked { - let body = peer_swift.value.body + let body = withObservationTracking { + return peer_swift.value.body + } onChange: { + onChange_swift() + } return ((body as? SkipUIBridging)?.Java_view as? JConvertible)?.toJavaObject(options: []) } } @@ -7553,7 +7585,7 @@ final class BridgeToKotlinTests: XCTestCase { } } """, kotlin: """ - internal class V: skip.ui.View, skip.bridge.SwiftPeerBridged, skip.lib.SwiftProjecting { + internal class V: skip.ui.View, skip.ui.Renderable, skip.bridge.SwiftPeerBridged, skip.lib.SwiftProjecting { var Swift_peer: skip.bridge.SwiftObjectPointer = skip.bridge.SwiftObjectNil constructor(Swift_peer: skip.bridge.SwiftObjectPointer, marker: skip.bridge.SwiftPeerMarker?) { @@ -7585,15 +7617,26 @@ final class BridgeToKotlinTests: XCTestCase { private external fun Swift_syncState_t(Swift_peer: skip.bridge.SwiftObjectPointer, support: skip.ui.StateSupport) override fun body(): skip.ui.View { - return skip.ui.ComposeBuilder { composectx: skip.ui.ComposeContext -> Swift_composableBody(Swift_peer)?.Compose(composectx) ?: skip.ui.ComposeResult.ok } + return skip.ui.ComposeBuilder { composectx: skip.ui.ComposeContext -> + Render(composectx) + skip.ui.ComposeResult.ok + } + } + + @androidx.compose.runtime.Composable + override fun Render(context: skip.ui.ComposeContext) { + val observationInvalidation = androidx.compose.runtime.remember { androidx.compose.runtime.mutableStateOf(0) } + observationInvalidation.value + Swift_composableBody(Swift_peer, onChange = { observationInvalidation.value += 1 })?.Compose(context) } - private external fun Swift_composableBody(Swift_peer: skip.bridge.SwiftObjectPointer): skip.ui.View? + private external fun Swift_composableBody(Swift_peer: skip.bridge.SwiftObjectPointer, onChange: () -> Unit): skip.ui.View? override fun Swift_projection(options: Int): () -> Any = Swift_projectionImpl(options) private external fun Swift_projectionImpl(options: Int): () -> Any } """, swiftBridgeSupport: """ + import Observation import SkipFuseUI extension V: BridgedToKotlin, SkipUIBridging, SkipUI.View { nonisolated private static var Java_class: JClass { try! JClass(name: "V") } @@ -7666,10 +7709,15 @@ final class BridgeToKotlinTests: XCTestCase { } } @_cdecl("Java_V_Swift_1composableBody") - public func V_Swift_composableBody(_ Java_env: JNIEnvPointer, _ Java_target: JavaObjectPointer, _ Swift_peer: SwiftObjectPointer) -> JavaObjectPointer? { + public func V_Swift_composableBody(_ Java_env: JNIEnvPointer, _ Java_target: JavaObjectPointer, _ Swift_peer: SwiftObjectPointer, _ onChange: JavaObjectPointer) -> JavaObjectPointer? { let peer_swift: V_TypeErased = Swift_peer.pointee()! + let onChange_swift = SwiftClosure0.closure(forJavaObject: onChange, options: [])! as @Sendable () -> Void return SkipBridge.assumeMainActorUnchecked { - let body = peer_swift.body() + let body = withObservationTracking { + return peer_swift.body() + } onChange: { + onChange_swift() + } return ((body as? SkipUIBridging)?.Java_view as? JConvertible)?.toJavaObject(options: []) } } @@ -7686,7 +7734,7 @@ final class BridgeToKotlinTests: XCTestCase { } } """, kotlin: """ - internal class V: skip.ui.View, skip.bridge.SwiftPeerBridged, skip.lib.SwiftProjecting { + internal class V: skip.ui.View, skip.ui.Renderable, skip.bridge.SwiftPeerBridged, skip.lib.SwiftProjecting { var Swift_peer: skip.bridge.SwiftObjectPointer = skip.bridge.SwiftObjectNil constructor(Swift_peer: skip.bridge.SwiftObjectPointer, marker: skip.bridge.SwiftPeerMarker?) { @@ -7718,15 +7766,26 @@ final class BridgeToKotlinTests: XCTestCase { private external fun Swift_syncState_i(Swift_peer: skip.bridge.SwiftObjectPointer, support: skip.ui.StateSupport) override fun body(): skip.ui.View { - return skip.ui.ComposeBuilder { composectx: skip.ui.ComposeContext -> Swift_composableBody(Swift_peer)?.Compose(composectx) ?: skip.ui.ComposeResult.ok } + return skip.ui.ComposeBuilder { composectx: skip.ui.ComposeContext -> + Render(composectx) + skip.ui.ComposeResult.ok + } } - private external fun Swift_composableBody(Swift_peer: skip.bridge.SwiftObjectPointer): skip.ui.View? + + @androidx.compose.runtime.Composable + override fun Render(context: skip.ui.ComposeContext) { + val observationInvalidation = androidx.compose.runtime.remember { androidx.compose.runtime.mutableStateOf(0) } + observationInvalidation.value + Swift_composableBody(Swift_peer, onChange = { observationInvalidation.value += 1 })?.Compose(context) + } + private external fun Swift_composableBody(Swift_peer: skip.bridge.SwiftObjectPointer, onChange: () -> Unit): skip.ui.View? override fun Swift_projection(options: Int): () -> Any = Swift_projectionImpl(options) private external fun Swift_projectionImpl(options: Int): () -> Any } """, swiftBridgeSupport: """ + import Observation import SkipFuseUI extension V: BridgedToKotlin, SkipUIBridging, SkipUI.View { nonisolated private static let Java_class = try! JClass(name: "V") @@ -7779,10 +7838,15 @@ final class BridgeToKotlinTests: XCTestCase { } } @_cdecl("Java_V_Swift_1composableBody") - public func V_Swift_composableBody(_ Java_env: JNIEnvPointer, _ Java_target: JavaObjectPointer, _ Swift_peer: SwiftObjectPointer) -> JavaObjectPointer? { + public func V_Swift_composableBody(_ Java_env: JNIEnvPointer, _ Java_target: JavaObjectPointer, _ Swift_peer: SwiftObjectPointer, _ onChange: JavaObjectPointer) -> JavaObjectPointer? { let peer_swift: SwiftValueTypeBox = Swift_peer.pointee()! + let onChange_swift = SwiftClosure0.closure(forJavaObject: onChange, options: [])! as @Sendable () -> Void return SkipBridge.assumeMainActorUnchecked { - let body = peer_swift.value.body + let body = withObservationTracking { + return peer_swift.value.body + } onChange: { + onChange_swift() + } return ((body as? SkipUIBridging)?.Java_view as? JConvertible)?.toJavaObject(options: []) } } @@ -7802,14 +7866,24 @@ final class BridgeToKotlinTests: XCTestCase { } } """, kotlin: """ - enum class E: skip.ui.View, skip.lib.SwiftProjecting { + enum class E: skip.ui.View, skip.ui.Renderable, skip.lib.SwiftProjecting { a; override fun body(): skip.ui.View { - return skip.ui.ComposeBuilder { composectx: skip.ui.ComposeContext -> Swift_composableBody(name)?.Compose(composectx) ?: skip.ui.ComposeResult.ok } + return skip.ui.ComposeBuilder { composectx: skip.ui.ComposeContext -> + Render(composectx) + skip.ui.ComposeResult.ok + } + } + + @androidx.compose.runtime.Composable + override fun Render(context: skip.ui.ComposeContext) { + val observationInvalidation = androidx.compose.runtime.remember { androidx.compose.runtime.mutableStateOf(0) } + observationInvalidation.value + Swift_composableBody(name, onChange = { observationInvalidation.value += 1 })?.Compose(context) } - private external fun Swift_composableBody(name: String): skip.ui.View? + private external fun Swift_composableBody(name: String, onChange: () -> Unit): skip.ui.View? override fun Swift_projection(options: Int): () -> Any = Swift_projectionImpl(options) private external fun Swift_projectionImpl(options: Int): () -> Any @@ -7819,6 +7893,7 @@ final class BridgeToKotlinTests: XCTestCase { } """, swiftBridgeSupport: """ + import Observation import SkipSwiftUI extension E: BridgedToKotlin, SkipUIBridging, SkipUI.View { nonisolated private static let Java_class = try! JClass(name: "E") @@ -7853,11 +7928,16 @@ final class BridgeToKotlinTests: XCTestCase { return SwiftClosure0.javaObject(for: factory, options: [])! } @_cdecl("Java_E_Swift_1composableBody") - public func E_Swift_composableBody(_ Java_env: JNIEnvPointer, _ Java_target: JavaObjectPointer, _ name: JavaString) -> JavaObjectPointer? { + public func E_Swift_composableBody(_ Java_env: JNIEnvPointer, _ Java_target: JavaObjectPointer, _ name: JavaString, _ onChange: JavaObjectPointer) -> JavaObjectPointer? { let name_swift = String.fromJavaObject(name, options: []) let peer_swift = E.fromJavaName(name_swift) + let onChange_swift = SwiftClosure0.closure(forJavaObject: onChange, options: [])! as @Sendable () -> Void return SkipBridge.assumeMainActorUnchecked { - let body = peer_swift.body + let body = withObservationTracking { + return peer_swift.body + } onChange: { + onChange_swift() + } return ((body as? SkipUIBridging)?.Java_view as? JConvertible)?.toJavaObject(options: []) } } @@ -7877,7 +7957,7 @@ final class BridgeToKotlinTests: XCTestCase { } } """, kotlin: """ - sealed class E: skip.ui.View, skip.bridge.SwiftPeerBridged, skip.lib.SwiftProjecting { + sealed class E: skip.ui.View, skip.ui.Renderable, skip.bridge.SwiftPeerBridged, skip.lib.SwiftProjecting { class ACase(val associated0: T): E() { } @@ -7899,9 +7979,19 @@ final class BridgeToKotlinTests: XCTestCase { override fun hashCode(): Int = Swift_peer.hashCode() override fun body(): skip.ui.View { - return skip.ui.ComposeBuilder { composectx: skip.ui.ComposeContext -> Swift_composableBody(Swift_peer)?.Compose(composectx) ?: skip.ui.ComposeResult.ok } + return skip.ui.ComposeBuilder { composectx: skip.ui.ComposeContext -> + Render(composectx) + skip.ui.ComposeResult.ok + } + } + + @androidx.compose.runtime.Composable + override fun Render(context: skip.ui.ComposeContext) { + val observationInvalidation = androidx.compose.runtime.remember { androidx.compose.runtime.mutableStateOf(0) } + observationInvalidation.value + Swift_composableBody(Swift_peer, onChange = { observationInvalidation.value += 1 })?.Compose(context) } - private external fun Swift_composableBody(Swift_peer: skip.bridge.SwiftObjectPointer): skip.ui.View? + private external fun Swift_composableBody(Swift_peer: skip.bridge.SwiftObjectPointer, onChange: () -> Unit): skip.ui.View? override fun Swift_projection(options: Int): () -> Any = Swift_projectionImpl(options) private external fun Swift_projectionImpl(options: Int): () -> Any @@ -7912,6 +8002,7 @@ final class BridgeToKotlinTests: XCTestCase { } """, swiftBridgeSupport: """ + import Observation import SkipSwiftUI extension E: BridgedToKotlin, SkipUIBridging, SkipUI.View { nonisolated private static var Java_class: JClass { try! JClass(name: "E") } @@ -7966,10 +8057,15 @@ final class BridgeToKotlinTests: XCTestCase { return SwiftClosure0.javaObject(for: factory, options: [])! } @_cdecl("Java_E_Swift_1composableBody") - public func E_Swift_composableBody(_ Java_env: JNIEnvPointer, _ Java_target: JavaObjectPointer, _ Swift_peer: SwiftObjectPointer) -> JavaObjectPointer? { + public func E_Swift_composableBody(_ Java_env: JNIEnvPointer, _ Java_target: JavaObjectPointer, _ Swift_peer: SwiftObjectPointer, _ onChange: JavaObjectPointer) -> JavaObjectPointer? { let peer_swift: E_TypeErased = Swift_peer.pointee()! + let onChange_swift = SwiftClosure0.closure(forJavaObject: onChange, options: [])! as @Sendable () -> Void return SkipBridge.assumeMainActorUnchecked { - let body = peer_swift.body() + let body = withObservationTracking { + return peer_swift.body() + } onChange: { + onChange_swift() + } return ((body as? SkipUIBridging)?.Java_view as? JConvertible)?.toJavaObject(options: []) } } @@ -7986,7 +8082,7 @@ final class BridgeToKotlinTests: XCTestCase { } } """, kotlin: """ - internal class V: skip.ui.View, skip.bridge.SwiftPeerBridged, skip.lib.SwiftProjecting { + internal class V: skip.ui.View, skip.ui.Renderable, skip.bridge.SwiftPeerBridged, skip.lib.SwiftProjecting { var Swift_peer: skip.bridge.SwiftObjectPointer = skip.bridge.SwiftObjectNil constructor(Swift_peer: skip.bridge.SwiftObjectPointer, marker: skip.bridge.SwiftPeerMarker?) { @@ -8018,15 +8114,26 @@ final class BridgeToKotlinTests: XCTestCase { private external fun Swift_syncState_focused(Swift_peer: skip.bridge.SwiftObjectPointer, support: skip.ui.StateSupport) override fun body(): skip.ui.View { - return skip.ui.ComposeBuilder { composectx: skip.ui.ComposeContext -> Swift_composableBody(Swift_peer)?.Compose(composectx) ?: skip.ui.ComposeResult.ok } + return skip.ui.ComposeBuilder { composectx: skip.ui.ComposeContext -> + Render(composectx) + skip.ui.ComposeResult.ok + } + } + + @androidx.compose.runtime.Composable + override fun Render(context: skip.ui.ComposeContext) { + val observationInvalidation = androidx.compose.runtime.remember { androidx.compose.runtime.mutableStateOf(0) } + observationInvalidation.value + Swift_composableBody(Swift_peer, onChange = { observationInvalidation.value += 1 })?.Compose(context) } - private external fun Swift_composableBody(Swift_peer: skip.bridge.SwiftObjectPointer): skip.ui.View? + private external fun Swift_composableBody(Swift_peer: skip.bridge.SwiftObjectPointer, onChange: () -> Unit): skip.ui.View? override fun Swift_projection(options: Int): () -> Any = Swift_projectionImpl(options) private external fun Swift_projectionImpl(options: Int): () -> Any } """, swiftBridgeSupport: """ + import Observation import SkipFuseUI extension V: BridgedToKotlin, SkipUIBridging, SkipUI.View { nonisolated private static let Java_class = try! JClass(name: "V") @@ -8079,10 +8186,15 @@ final class BridgeToKotlinTests: XCTestCase { } } @_cdecl("Java_V_Swift_1composableBody") - public func V_Swift_composableBody(_ Java_env: JNIEnvPointer, _ Java_target: JavaObjectPointer, _ Swift_peer: SwiftObjectPointer) -> JavaObjectPointer? { + public func V_Swift_composableBody(_ Java_env: JNIEnvPointer, _ Java_target: JavaObjectPointer, _ Swift_peer: SwiftObjectPointer, _ onChange: JavaObjectPointer) -> JavaObjectPointer? { let peer_swift: SwiftValueTypeBox = Swift_peer.pointee()! + let onChange_swift = SwiftClosure0.closure(forJavaObject: onChange, options: [])! as @Sendable () -> Void return SkipBridge.assumeMainActorUnchecked { - let body = peer_swift.value.body + let body = withObservationTracking { + return peer_swift.value.body + } onChange: { + onChange_swift() + } return ((body as? SkipUIBridging)?.Java_view as? JConvertible)?.toJavaObject(options: []) } } @@ -8099,7 +8211,7 @@ final class BridgeToKotlinTests: XCTestCase { } } """, kotlin: """ - internal class V: skip.ui.View, skip.bridge.SwiftPeerBridged, skip.lib.SwiftProjecting { + internal class V: skip.ui.View, skip.ui.Renderable, skip.bridge.SwiftPeerBridged, skip.lib.SwiftProjecting { var Swift_peer: skip.bridge.SwiftObjectPointer = skip.bridge.SwiftObjectNil constructor(Swift_peer: skip.bridge.SwiftObjectPointer, marker: skip.bridge.SwiftPeerMarker?) { @@ -8131,15 +8243,26 @@ final class BridgeToKotlinTests: XCTestCase { private external fun Swift_syncState_i(Swift_peer: skip.bridge.SwiftObjectPointer, support: skip.ui.StateSupport) override fun body(): skip.ui.View { - return skip.ui.ComposeBuilder { composectx: skip.ui.ComposeContext -> Swift_composableBody(Swift_peer)?.Compose(composectx) ?: skip.ui.ComposeResult.ok } + return skip.ui.ComposeBuilder { composectx: skip.ui.ComposeContext -> + Render(composectx) + skip.ui.ComposeResult.ok + } } - private external fun Swift_composableBody(Swift_peer: skip.bridge.SwiftObjectPointer): skip.ui.View? + + @androidx.compose.runtime.Composable + override fun Render(context: skip.ui.ComposeContext) { + val observationInvalidation = androidx.compose.runtime.remember { androidx.compose.runtime.mutableStateOf(0) } + observationInvalidation.value + Swift_composableBody(Swift_peer, onChange = { observationInvalidation.value += 1 })?.Compose(context) + } + private external fun Swift_composableBody(Swift_peer: skip.bridge.SwiftObjectPointer, onChange: () -> Unit): skip.ui.View? override fun Swift_projection(options: Int): () -> Any = Swift_projectionImpl(options) private external fun Swift_projectionImpl(options: Int): () -> Any } """, swiftBridgeSupport: """ + import Observation import SkipFuseUI extension V: BridgedToKotlin, SkipUIBridging, SkipUI.View { nonisolated private static let Java_class = try! JClass(name: "V") @@ -8192,10 +8315,15 @@ final class BridgeToKotlinTests: XCTestCase { } } @_cdecl("Java_V_Swift_1composableBody") - public func V_Swift_composableBody(_ Java_env: JNIEnvPointer, _ Java_target: JavaObjectPointer, _ Swift_peer: SwiftObjectPointer) -> JavaObjectPointer? { + public func V_Swift_composableBody(_ Java_env: JNIEnvPointer, _ Java_target: JavaObjectPointer, _ Swift_peer: SwiftObjectPointer, _ onChange: JavaObjectPointer) -> JavaObjectPointer? { let peer_swift: SwiftValueTypeBox = Swift_peer.pointee()! + let onChange_swift = SwiftClosure0.closure(forJavaObject: onChange, options: [])! as @Sendable () -> Void return SkipBridge.assumeMainActorUnchecked { - let body = peer_swift.value.body + let body = withObservationTracking { + return peer_swift.value.body + } onChange: { + onChange_swift() + } return ((body as? SkipUIBridging)?.Java_view as? JConvertible)?.toJavaObject(options: []) } } @@ -8212,7 +8340,7 @@ final class BridgeToKotlinTests: XCTestCase { } } """, kotlin: """ - internal class V: skip.ui.View, skip.bridge.SwiftPeerBridged, skip.lib.SwiftProjecting { + internal class V: skip.ui.View, skip.ui.Renderable, skip.bridge.SwiftPeerBridged, skip.lib.SwiftProjecting { var Swift_peer: skip.bridge.SwiftObjectPointer = skip.bridge.SwiftObjectNil constructor(Swift_peer: skip.bridge.SwiftObjectPointer, marker: skip.bridge.SwiftPeerMarker?) { @@ -8244,15 +8372,26 @@ final class BridgeToKotlinTests: XCTestCase { private external fun Swift_syncState_value(Swift_peer: skip.bridge.SwiftObjectPointer, support: skip.ui.AppStorageSupport) override fun body(): skip.ui.View { - return skip.ui.ComposeBuilder { composectx: skip.ui.ComposeContext -> Swift_composableBody(Swift_peer)?.Compose(composectx) ?: skip.ui.ComposeResult.ok } + return skip.ui.ComposeBuilder { composectx: skip.ui.ComposeContext -> + Render(composectx) + skip.ui.ComposeResult.ok + } + } + + @androidx.compose.runtime.Composable + override fun Render(context: skip.ui.ComposeContext) { + val observationInvalidation = androidx.compose.runtime.remember { androidx.compose.runtime.mutableStateOf(0) } + observationInvalidation.value + Swift_composableBody(Swift_peer, onChange = { observationInvalidation.value += 1 })?.Compose(context) } - private external fun Swift_composableBody(Swift_peer: skip.bridge.SwiftObjectPointer): skip.ui.View? + private external fun Swift_composableBody(Swift_peer: skip.bridge.SwiftObjectPointer, onChange: () -> Unit): skip.ui.View? override fun Swift_projection(options: Int): () -> Any = Swift_projectionImpl(options) private external fun Swift_projectionImpl(options: Int): () -> Any } """, swiftBridgeSupport: """ + import Observation import SkipFuseUI extension V: BridgedToKotlin, SkipUIBridging, SkipUI.View { nonisolated private static let Java_class = try! JClass(name: "V") @@ -8305,10 +8444,15 @@ final class BridgeToKotlinTests: XCTestCase { } } @_cdecl("Java_V_Swift_1composableBody") - public func V_Swift_composableBody(_ Java_env: JNIEnvPointer, _ Java_target: JavaObjectPointer, _ Swift_peer: SwiftObjectPointer) -> JavaObjectPointer? { + public func V_Swift_composableBody(_ Java_env: JNIEnvPointer, _ Java_target: JavaObjectPointer, _ Swift_peer: SwiftObjectPointer, _ onChange: JavaObjectPointer) -> JavaObjectPointer? { let peer_swift: SwiftValueTypeBox = Swift_peer.pointee()! + let onChange_swift = SwiftClosure0.closure(forJavaObject: onChange, options: [])! as @Sendable () -> Void return SkipBridge.assumeMainActorUnchecked { - let body = peer_swift.value.body + let body = withObservationTracking { + return peer_swift.value.body + } onChange: { + onChange_swift() + } return ((body as? SkipUIBridging)?.Java_view as? JConvertible)?.toJavaObject(options: []) } } @@ -8369,14 +8513,18 @@ final class BridgeToKotlinTests: XCTestCase { private external fun Swift_syncState_count(Swift_peer: skip.bridge.SwiftObjectPointer, support: skip.ui.StateSupport) override fun body(content: skip.ui.View): skip.ui.View { - return skip.ui.ComposeBuilder { composectx: skip.ui.ComposeContext -> Swift_composableBody(Swift_peer, content)?.Compose(composectx) ?: skip.ui.ComposeResult.ok } + return skip.ui.ComposeBuilder { composectx: skip.ui.ComposeContext -> + val observationInvalidation = androidx.compose.runtime.remember { androidx.compose.runtime.mutableStateOf(0) } + observationInvalidation.value + Swift_composableBody(Swift_peer, content, onChange = { observationInvalidation.value += 1 })?.Compose(composectx) ?: skip.ui.ComposeResult.ok + } } - private external fun Swift_composableBody(Swift_peer: skip.bridge.SwiftObjectPointer, content: skip.ui.View): skip.ui.View? + private external fun Swift_composableBody(Swift_peer: skip.bridge.SwiftObjectPointer, content: skip.ui.View, onChange: () -> Unit): skip.ui.View? override fun Swift_projection(options: Int): () -> Any = Swift_projectionImpl(options) private external fun Swift_projectionImpl(options: Int): () -> Any } - internal class V: skip.ui.View, skip.bridge.SwiftPeerBridged, skip.lib.SwiftProjecting { + internal class V: skip.ui.View, skip.ui.Renderable, skip.bridge.SwiftPeerBridged, skip.lib.SwiftProjecting { var Swift_peer: skip.bridge.SwiftObjectPointer = skip.bridge.SwiftObjectNil constructor(Swift_peer: skip.bridge.SwiftObjectPointer, marker: skip.bridge.SwiftPeerMarker?) { @@ -8399,15 +8547,26 @@ final class BridgeToKotlinTests: XCTestCase { override fun hashCode(): Int = Swift_peer.hashCode() override fun body(): skip.ui.View { - return skip.ui.ComposeBuilder { composectx: skip.ui.ComposeContext -> Swift_composableBody(Swift_peer)?.Compose(composectx) ?: skip.ui.ComposeResult.ok } + return skip.ui.ComposeBuilder { composectx: skip.ui.ComposeContext -> + Render(composectx) + skip.ui.ComposeResult.ok + } + } + + @androidx.compose.runtime.Composable + override fun Render(context: skip.ui.ComposeContext) { + val observationInvalidation = androidx.compose.runtime.remember { androidx.compose.runtime.mutableStateOf(0) } + observationInvalidation.value + Swift_composableBody(Swift_peer, onChange = { observationInvalidation.value += 1 })?.Compose(context) } - private external fun Swift_composableBody(Swift_peer: skip.bridge.SwiftObjectPointer): skip.ui.View? + private external fun Swift_composableBody(Swift_peer: skip.bridge.SwiftObjectPointer, onChange: () -> Unit): skip.ui.View? override fun Swift_projection(options: Int): () -> Any = Swift_projectionImpl(options) private external fun Swift_projectionImpl(options: Int): () -> Any } """, swiftBridgeSupport: """ + import Observation import SkipFuseUI extension VM: BridgedToKotlin, SkipUI.ViewModifier { nonisolated private static let Java_class = try! JClass(name: "VM") @@ -8477,11 +8636,16 @@ final class BridgeToKotlinTests: XCTestCase { } } @_cdecl("Java_VM_Swift_1composableBody") - public func VM_Swift_composableBody(_ Java_env: JNIEnvPointer, _ Java_target: JavaObjectPointer, _ Swift_peer: SwiftObjectPointer, _ content: JavaObjectPointer) -> JavaObjectPointer? { + public func VM_Swift_composableBody(_ Java_env: JNIEnvPointer, _ Java_target: JavaObjectPointer, _ Swift_peer: SwiftObjectPointer, _ content: JavaObjectPointer, _ onChange: JavaObjectPointer) -> JavaObjectPointer? { let peer_swift: SwiftValueTypeBox = Swift_peer.pointee()! let content_swift = JavaBackedView(content)! + let onChange_swift = SwiftClosure0.closure(forJavaObject: onChange, options: [])! as @Sendable () -> Void return SkipBridge.assumeMainActorUnchecked { - let body = peer_swift.value.body(content: content_swift) + let body = withObservationTracking { + return peer_swift.value.body(content: content_swift) + } onChange: { + onChange_swift() + } return ((body as? SkipUIBridging)?.Java_view as? JConvertible)?.toJavaObject(options: []) } } @@ -8496,10 +8660,15 @@ final class BridgeToKotlinTests: XCTestCase { return SwiftClosure0.javaObject(for: factory, options: [])! } @_cdecl("Java_V_Swift_1composableBody") - public func V_Swift_composableBody(_ Java_env: JNIEnvPointer, _ Java_target: JavaObjectPointer, _ Swift_peer: SwiftObjectPointer) -> JavaObjectPointer? { + public func V_Swift_composableBody(_ Java_env: JNIEnvPointer, _ Java_target: JavaObjectPointer, _ Swift_peer: SwiftObjectPointer, _ onChange: JavaObjectPointer) -> JavaObjectPointer? { let peer_swift: SwiftValueTypeBox = Swift_peer.pointee()! + let onChange_swift = SwiftClosure0.closure(forJavaObject: onChange, options: [])! as @Sendable () -> Void return SkipBridge.assumeMainActorUnchecked { - let body = peer_swift.value.body + let body = withObservationTracking { + return peer_swift.value.body + } onChange: { + onChange_swift() + } return ((body as? SkipUIBridging)?.Java_view as? JConvertible)?.toJavaObject(options: []) } } @@ -8548,15 +8717,20 @@ final class BridgeToKotlinTests: XCTestCase { private external fun Swift_syncState_count(Swift_peer: skip.bridge.SwiftObjectPointer, support: skip.ui.StateSupport) override fun body(): skip.ui.View { - return skip.ui.ComposeBuilder { composectx: skip.ui.ComposeContext -> Swift_composableBody(Swift_peer)?.Compose(composectx) ?: skip.ui.ComposeResult.ok } + return skip.ui.ComposeBuilder { composectx: skip.ui.ComposeContext -> + val observationInvalidation = androidx.compose.runtime.remember { androidx.compose.runtime.mutableStateOf(0) } + observationInvalidation.value + Swift_composableBody(Swift_peer, onChange = { observationInvalidation.value += 1 })?.Compose(composectx) ?: skip.ui.ComposeResult.ok + } } - private external fun Swift_composableBody(Swift_peer: skip.bridge.SwiftObjectPointer): skip.ui.View? + private external fun Swift_composableBody(Swift_peer: skip.bridge.SwiftObjectPointer, onChange: () -> Unit): skip.ui.View? override fun Swift_projection(options: Int): () -> Any = Swift_projectionImpl(options) private external fun Swift_projectionImpl(options: Int): () -> Any } """, swiftBridgeSupport: """ + import Observation import SkipFuseUI extension T: BridgedToKotlin, SkipUIBridging, SkipUI.ToolbarContent { nonisolated private static let Java_class = try! JClass(name: "T") @@ -8609,10 +8783,15 @@ final class BridgeToKotlinTests: XCTestCase { } } @_cdecl("Java_T_Swift_1composableBody") - public func T_Swift_composableBody(_ Java_env: JNIEnvPointer, _ Java_target: JavaObjectPointer, _ Swift_peer: SwiftObjectPointer) -> JavaObjectPointer? { + public func T_Swift_composableBody(_ Java_env: JNIEnvPointer, _ Java_target: JavaObjectPointer, _ Swift_peer: SwiftObjectPointer, _ onChange: JavaObjectPointer) -> JavaObjectPointer? { let peer_swift: SwiftValueTypeBox = Swift_peer.pointee()! + let onChange_swift = SwiftClosure0.closure(forJavaObject: onChange, options: [])! as @Sendable () -> Void return SkipBridge.assumeMainActorUnchecked { - let body = peer_swift.value.body + let body = withObservationTracking { + return peer_swift.value.body + } onChange: { + onChange_swift() + } return ((body as? SkipUIBridging)?.Java_view as? JConvertible)?.toJavaObject(options: []) } } @@ -8826,7 +9005,7 @@ final class BridgeToKotlinTests: XCTestCase { """, kotlin: """ import androidx.compose.runtime.Composable - internal class V: skip.ui.View, skip.bridge.SwiftPeerBridged, skip.lib.SwiftProjecting { + internal class V: skip.ui.View, skip.ui.Renderable, skip.bridge.SwiftPeerBridged, skip.lib.SwiftProjecting { var Swift_peer: skip.bridge.SwiftObjectPointer = skip.bridge.SwiftObjectNil constructor(Swift_peer: skip.bridge.SwiftObjectPointer, marker: skip.bridge.SwiftPeerMarker?) { @@ -8849,9 +9028,19 @@ final class BridgeToKotlinTests: XCTestCase { override fun hashCode(): Int = Swift_peer.hashCode() override fun body(): skip.ui.View { - return skip.ui.ComposeBuilder { composectx: skip.ui.ComposeContext -> Swift_composableBody(Swift_peer)?.Compose(composectx) ?: skip.ui.ComposeResult.ok } + return skip.ui.ComposeBuilder { composectx: skip.ui.ComposeContext -> + Render(composectx) + skip.ui.ComposeResult.ok + } + } + + @androidx.compose.runtime.Composable + override fun Render(context: skip.ui.ComposeContext) { + val observationInvalidation = androidx.compose.runtime.remember { androidx.compose.runtime.mutableStateOf(0) } + observationInvalidation.value + Swift_composableBody(Swift_peer, onChange = { observationInvalidation.value += 1 })?.Compose(context) } - private external fun Swift_composableBody(Swift_peer: skip.bridge.SwiftObjectPointer): skip.ui.View? + private external fun Swift_composableBody(Swift_peer: skip.bridge.SwiftObjectPointer, onChange: () -> Unit): skip.ui.View? override fun Swift_projection(options: Int): () -> Any = Swift_projectionImpl(options) private external fun Swift_projectionImpl(options: Int): () -> Any @@ -8870,6 +9059,7 @@ final class BridgeToKotlinTests: XCTestCase { private external fun Swift_projectionImpl(options: Int): () -> Any } """, swiftBridgeSupport: """ + import Observation import SkipFuseUI extension V: BridgedToKotlin, SkipUIBridging, SkipUI.View { nonisolated private static let Java_class = try! JClass(name: "V") @@ -8899,10 +9089,15 @@ final class BridgeToKotlinTests: XCTestCase { return SwiftClosure0.javaObject(for: factory, options: [])! } @_cdecl("Java_V_Swift_1composableBody") - public func V_Swift_composableBody(_ Java_env: JNIEnvPointer, _ Java_target: JavaObjectPointer, _ Swift_peer: SwiftObjectPointer) -> JavaObjectPointer? { + public func V_Swift_composableBody(_ Java_env: JNIEnvPointer, _ Java_target: JavaObjectPointer, _ Swift_peer: SwiftObjectPointer, _ onChange: JavaObjectPointer) -> JavaObjectPointer? { let peer_swift: SwiftValueTypeBox = Swift_peer.pointee()! + let onChange_swift = SwiftClosure0.closure(forJavaObject: onChange, options: [])! as @Sendable () -> Void return SkipBridge.assumeMainActorUnchecked { - let body = peer_swift.value.body + let body = withObservationTracking { + return peer_swift.value.body + } onChange: { + onChange_swift() + } return ((body as? SkipUIBridging)?.Java_view as? JConvertible)?.toJavaObject(options: []) } } @@ -8977,7 +9172,7 @@ final class BridgeToKotlinTests: XCTestCase { import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue - internal class V: skip.ui.View, skip.bridge.SwiftPeerBridged, skip.lib.SwiftProjecting { + internal class V: skip.ui.View, skip.ui.Renderable, skip.bridge.SwiftPeerBridged, skip.lib.SwiftProjecting { var Swift_peer: skip.bridge.SwiftObjectPointer = skip.bridge.SwiftObjectNil constructor(Swift_peer: skip.bridge.SwiftObjectPointer, marker: skip.bridge.SwiftPeerMarker?) { @@ -9000,9 +9195,19 @@ final class BridgeToKotlinTests: XCTestCase { override fun hashCode(): Int = Swift_peer.hashCode() override fun body(): skip.ui.View { - return skip.ui.ComposeBuilder { composectx: skip.ui.ComposeContext -> Swift_composableBody(Swift_peer)?.Compose(composectx) ?: skip.ui.ComposeResult.ok } + return skip.ui.ComposeBuilder { composectx: skip.ui.ComposeContext -> + Render(composectx) + skip.ui.ComposeResult.ok + } } - private external fun Swift_composableBody(Swift_peer: skip.bridge.SwiftObjectPointer): skip.ui.View? + + @androidx.compose.runtime.Composable + override fun Render(context: skip.ui.ComposeContext) { + val observationInvalidation = androidx.compose.runtime.remember { androidx.compose.runtime.mutableStateOf(0) } + observationInvalidation.value + Swift_composableBody(Swift_peer, onChange = { observationInvalidation.value += 1 })?.Compose(context) + } + private external fun Swift_composableBody(Swift_peer: skip.bridge.SwiftObjectPointer, onChange: () -> Unit): skip.ui.View? override fun Swift_projection(options: Int): () -> Any = Swift_projectionImpl(options) private external fun Swift_projectionImpl(options: Int): () -> Any @@ -9037,6 +9242,7 @@ final class BridgeToKotlinTests: XCTestCase { } """, swiftBridgeSupport: """ + import Observation import SkipFuseUI extension V: BridgedToKotlin, SkipUIBridging, SkipUI.View { nonisolated private static let Java_class = try! JClass(name: "V") @@ -9066,10 +9272,15 @@ final class BridgeToKotlinTests: XCTestCase { return SwiftClosure0.javaObject(for: factory, options: [])! } @_cdecl("Java_V_Swift_1composableBody") - public func V_Swift_composableBody(_ Java_env: JNIEnvPointer, _ Java_target: JavaObjectPointer, _ Swift_peer: SwiftObjectPointer) -> JavaObjectPointer? { + public func V_Swift_composableBody(_ Java_env: JNIEnvPointer, _ Java_target: JavaObjectPointer, _ Swift_peer: SwiftObjectPointer, _ onChange: JavaObjectPointer) -> JavaObjectPointer? { let peer_swift: SwiftValueTypeBox = Swift_peer.pointee()! + let onChange_swift = SwiftClosure0.closure(forJavaObject: onChange, options: [])! as @Sendable () -> Void return SkipBridge.assumeMainActorUnchecked { - let body = peer_swift.value.body + let body = withObservationTracking { + return peer_swift.value.body + } onChange: { + onChange_swift() + } return ((body as? SkipUIBridging)?.Java_view as? JConvertible)?.toJavaObject(options: []) } }