Skip to content

Observable

Classes marked with both @jvm and @Observable get automatic observation support in the generated Kotlin code. For each observed property, swift4j generates a getXxxWithObservationTracking() method that lets you register a callback to be called when the property is about to change.

On Android, the generate-java-bridging plugin can additionally generate ViewModel wrappers that expose observed properties as StateFlow values — the standard Kotlin/Compose reactive primitive.

import Foundation
import Observation
import Swift4j
@jvm
@Observable
final class Counter {
var count: Int = 0
func increment() { count += 1 }
}

The generated Kotlin class includes a tracking accessor for each @Observable-tracked property:

val counter = Counter()
// Read the current value and register a change callback
val current = counter.getCountWithObservationTracking {
println("count is about to change")
}
println("Initial count: $current")
counter.increment()
// prints: count is about to change
println("New count: ${counter.count}")

The callback fires once, just before the property changes. Re-register inside the callback to keep observing.

Run the plugin with the --generate-android-viewmodels flag to generate ViewModel and ViewModelFactory subclasses for each @Observable type:

Terminal window
swift package plugin generate-java-bridging \
--product MyLibrary \
--generate-android-viewmodels

The generated ViewModel exposes each tracked property as a StateFlow:

import MyLibrary.viewmodel.CounterViewModel
import MyLibrary.viewmodel.CounterViewModelFactory
class MainActivity : ComponentActivity() {
private val counterVm: CounterViewModel by viewModels {
CounterViewModelFactory(Counter())
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
System.loadLibrary("MyLibrary")
setContent {
CounterScreen(counterVm)
}
}
}
@Composable
fun CounterScreen(viewModel: CounterViewModel) {
val count by viewModel.count.collectAsState()
Column {
Text("Count: $count")
Button(onClick = { viewModel.updateCount(count + 1) }) {
Text("Increment")
}
}
}

The ViewModel uses StateFlow internally — property updates from Swift automatically trigger Compose recomposition.

Computed properties that depend on tracked stored properties are also exported with observation tracking:

@jvm
@Observable
final class Counter {
var count: Int = 0
var title: String { "Count: \(count)" }
}
val title = counter.getTitleWithObservationTracking {
println("title will change")
}
// Updating count also triggers the title observation callback
counter.count = 5