From 040fc724a3084984725c07075022d67e3c39017e Mon Sep 17 00:00:00 2001 From: Woodrow Melling Date: Thu, 21 May 2026 10:28:32 -0600 Subject: [PATCH 1/2] Adds brdiged SwiftUI body Observation via withObservationTracking --- .../Kotlin/KotlinBridgeToKotlinVisitor.swift | 37 ++- .../SkipSyntaxTests/BridgeToKotlinTests.swift | 251 ++++++++++++++---- 2 files changed, 223 insertions(+), 65 deletions(-) diff --git a/Sources/SkipSyntax/Kotlin/KotlinBridgeToKotlinVisitor.swift b/Sources/SkipSyntax/Kotlin/KotlinBridgeToKotlinVisitor.swift index 4c854378..417bfcde 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") @@ -1757,10 +1762,17 @@ 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 functionSourceCode = [ + "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", + "}", + ] let externalFunctionDeclaration = KotlinRawStatement(sourceCode: externalSourceCode) let functionDeclaration = KotlinFunctionDeclaration(name: "body") @@ -1770,7 +1782,7 @@ 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: functionSourceCode.map { KotlinRawStatement(sourceCode: $0) }) functionDeclaration.body?.disallowSingleStatementAppend = true functionDeclaration.parent = classDeclaration @@ -1789,25 +1801,32 @@ 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) diff --git a/Tests/SkipSyntaxTests/BridgeToKotlinTests.swift b/Tests/SkipSyntaxTests/BridgeToKotlinTests.swift index c5167ed1..220b493a 100644 --- a/Tests/SkipSyntaxTests/BridgeToKotlinTests.swift +++ b/Tests/SkipSyntaxTests/BridgeToKotlinTests.swift @@ -7319,9 +7319,13 @@ 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 -> + 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? val i: Int get() = Swift_i(Swift_peer) @@ -7339,6 +7343,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 +7388,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: []) } } @@ -7441,15 +7451,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 V: BridgedToKotlin, SkipUIBridging, SkipUI.View { nonisolated private static let Java_class = try! JClass(name: "V") @@ -7502,10 +7517,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: []) } } @@ -7585,15 +7605,20 @@ 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 -> + 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 V: BridgedToKotlin, SkipUIBridging, SkipUI.View { nonisolated private static var Java_class: JClass { try! JClass(name: "V") } @@ -7666,10 +7691,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: []) } } @@ -7718,15 +7748,20 @@ 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 -> + 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 V: BridgedToKotlin, SkipUIBridging, SkipUI.View { nonisolated private static let Java_class = try! JClass(name: "V") @@ -7779,10 +7814,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: []) } } @@ -7807,9 +7847,13 @@ final class BridgeToKotlinTests: XCTestCase { 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 -> + val observationInvalidation = androidx.compose.runtime.remember { androidx.compose.runtime.mutableStateOf(0) } + observationInvalidation.value + Swift_composableBody(name, onChange = { observationInvalidation.value += 1 })?.Compose(composectx) ?: skip.ui.ComposeResult.ok + } } - 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 +7863,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 +7898,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: []) } } @@ -7899,9 +7949,13 @@ 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 -> + 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 @@ -7912,6 +7966,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 +8021,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: []) } } @@ -8018,15 +8078,20 @@ 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 -> + 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 V: BridgedToKotlin, SkipUIBridging, SkipUI.View { nonisolated private static let Java_class = try! JClass(name: "V") @@ -8079,10 +8144,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: []) } } @@ -8131,15 +8201,20 @@ 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 -> + 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 V: BridgedToKotlin, SkipUIBridging, SkipUI.View { nonisolated private static let Java_class = try! JClass(name: "V") @@ -8192,10 +8267,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: []) } } @@ -8244,15 +8324,20 @@ 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 -> + 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 V: BridgedToKotlin, SkipUIBridging, SkipUI.View { nonisolated private static let Java_class = try! JClass(name: "V") @@ -8305,10 +8390,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,9 +8459,13 @@ 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 @@ -8399,15 +8493,20 @@ 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 -> + 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 VM: BridgedToKotlin, SkipUI.ViewModifier { nonisolated private static let Java_class = try! JClass(name: "VM") @@ -8477,11 +8576,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 +8600,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 +8657,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 +8723,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: []) } } @@ -8849,9 +8968,13 @@ 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 -> + 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 @@ -8870,6 +8993,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 +9023,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: []) } } @@ -9000,9 +9129,13 @@ 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 -> + 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 @@ -9037,6 +9170,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 +9200,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: []) } } From bbff9590bee771129ab8fdde1877866bca1c3313 Mon Sep 17 00:00:00 2001 From: Woodrow Melling Date: Thu, 21 May 2026 14:48:50 -0600 Subject: [PATCH 2/2] Render bridged SwiftUI views atomically --- .../Kotlin/KotlinBridgeToKotlinVisitor.swift | 54 +++++- .../SkipSyntaxTests/BridgeToKotlinTests.swift | 168 +++++++++++++----- 2 files changed, 165 insertions(+), 57 deletions(-) diff --git a/Sources/SkipSyntax/Kotlin/KotlinBridgeToKotlinVisitor.swift b/Sources/SkipSyntax/Kotlin/KotlinBridgeToKotlinVisitor.swift index 417bfcde..95b6592f 100644 --- a/Sources/SkipSyntax/Kotlin/KotlinBridgeToKotlinVisitor.swift +++ b/Sources/SkipSyntax/Kotlin/KotlinBridgeToKotlinVisitor.swift @@ -1052,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 @@ -1766,13 +1769,30 @@ final class KotlinBridgeToKotlinVisitor { 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 ->", - " 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", - "}", - ] + 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") @@ -1782,10 +1802,25 @@ final class KotlinBridgeToKotlinVisitor { functionDeclaration.returnType = .skipUIView functionDeclaration.modifiers = Modifiers(visibility: .public, isOverride: true) functionDeclaration.extras = .singleNewline - functionDeclaration.body = KotlinCodeBlock(statements: functionSourceCode.map { KotlinRawStatement(sourceCode: $0) }) + 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 { @@ -1831,7 +1866,8 @@ final class KotlinBridgeToKotlinVisitor { 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 220b493a..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?) { @@ -7320,11 +7320,17 @@ final class BridgeToKotlinTests: XCTestCase { override fun body(): skip.ui.View { 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 + 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, onChange: () -> Unit): skip.ui.View? val i: Int @@ -7419,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?) { @@ -7452,11 +7458,17 @@ final class BridgeToKotlinTests: XCTestCase { override fun body(): skip.ui.View { 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 + 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, onChange: () -> Unit): skip.ui.View? override fun Swift_projection(options: Int): () -> Any = Swift_projectionImpl(options) @@ -7573,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?) { @@ -7606,11 +7618,17 @@ final class BridgeToKotlinTests: XCTestCase { override fun body(): skip.ui.View { 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 + 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, onChange: () -> Unit): skip.ui.View? override fun Swift_projection(options: Int): () -> Any = Swift_projectionImpl(options) @@ -7716,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?) { @@ -7749,11 +7767,17 @@ final class BridgeToKotlinTests: XCTestCase { override fun body(): skip.ui.View { 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 + 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, onChange: () -> Unit): skip.ui.View? override fun Swift_projection(options: Int): () -> Any = Swift_projectionImpl(options) @@ -7842,17 +7866,23 @@ 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 -> - val observationInvalidation = androidx.compose.runtime.remember { androidx.compose.runtime.mutableStateOf(0) } - observationInvalidation.value - Swift_composableBody(name, onChange = { observationInvalidation.value += 1 })?.Compose(composectx) ?: skip.ui.ComposeResult.ok + 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, onChange: () -> Unit): skip.ui.View? override fun Swift_projection(options: Int): () -> Any = Swift_projectionImpl(options) @@ -7927,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() { } @@ -7950,11 +7980,17 @@ final class BridgeToKotlinTests: XCTestCase { override fun body(): skip.ui.View { 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 + 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, onChange: () -> Unit): skip.ui.View? override fun Swift_projection(options: Int): () -> Any = Swift_projectionImpl(options) @@ -8046,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?) { @@ -8079,11 +8115,17 @@ final class BridgeToKotlinTests: XCTestCase { override fun body(): skip.ui.View { 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 + 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, onChange: () -> Unit): skip.ui.View? override fun Swift_projection(options: Int): () -> Any = Swift_projectionImpl(options) @@ -8169,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?) { @@ -8202,11 +8244,17 @@ final class BridgeToKotlinTests: XCTestCase { override fun body(): skip.ui.View { 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 + 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, onChange: () -> Unit): skip.ui.View? override fun Swift_projection(options: Int): () -> Any = Swift_projectionImpl(options) @@ -8292,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?) { @@ -8325,11 +8373,17 @@ final class BridgeToKotlinTests: XCTestCase { override fun body(): skip.ui.View { 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 + 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, onChange: () -> Unit): skip.ui.View? override fun Swift_projection(options: Int): () -> Any = Swift_projectionImpl(options) @@ -8470,7 +8524,7 @@ final class BridgeToKotlinTests: XCTestCase { 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?) { @@ -8494,11 +8548,17 @@ final class BridgeToKotlinTests: XCTestCase { override fun body(): skip.ui.View { 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 + 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, onChange: () -> Unit): skip.ui.View? override fun Swift_projection(options: Int): () -> Any = Swift_projectionImpl(options) @@ -8945,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?) { @@ -8969,11 +9029,17 @@ final class BridgeToKotlinTests: XCTestCase { override fun body(): skip.ui.View { 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 + 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, onChange: () -> Unit): skip.ui.View? override fun Swift_projection(options: Int): () -> Any = Swift_projectionImpl(options) @@ -9106,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?) { @@ -9130,11 +9196,17 @@ final class BridgeToKotlinTests: XCTestCase { override fun body(): skip.ui.View { 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 + 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, onChange: () -> Unit): skip.ui.View? override fun Swift_projection(options: Int): () -> Any = Swift_projectionImpl(options)