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.
Basic observation
Section titled “Basic observation”import Foundationimport Observationimport Swift4j
@jvm@Observablefinal 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 callbackval current = counter.getCountWithObservationTracking { println("count is about to change")}println("Initial count: $current")
counter.increment()// prints: count is about to changeprintln("New count: ${counter.count}")The callback fires once, just before the property changes. Re-register inside the callback to keep observing.
Android ViewModel generation
Section titled “Android ViewModel generation”Run the plugin with the --generate-android-viewmodels flag to generate ViewModel and ViewModelFactory subclasses for each @Observable type:
swift package plugin generate-java-bridging \ --product MyLibrary \ --generate-android-viewmodelsThe generated ViewModel exposes each tracked property as a StateFlow:
import MyLibrary.viewmodel.CounterViewModelimport 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) } }}
@Composablefun 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
Section titled “Computed properties”Computed properties that depend on tracked stored properties are also exported with observation tracking:
@jvm@Observablefinal 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 callbackcounter.count = 5