Commit 21224c0
[bindgen] Add Kotlin Flow and Cancelable Support for Signals. (#8699)
Fixes https://mapbox.atlassian.net/browse/CORESDK-3520
This PR adds Kotlin Flow support for signals while maintaining and
improving callback-based APIs with `Cancelable` return values for
lifecycle management. Both Java and Kotlin generators now support the
enhanced signal API.
## API Changes
### Kotlin - Dual API (Flow + Callback)
**Flow-based (new):**
```kotlin
// Collect signals as Flow
service.foo.collect {
// Handle signal
}
// With parameters
service.bar.collect { barValue ->
println("Received: $barValue")
}
// Tuple signals (multiple parameters)
service.experimentalSignal.collect { (test, ing) ->
println("Received: $test, $ing")
}
```
**Callback-based (refined):**
```kotlin
val cancelable = service.subscribeFoo(object : FooCallback {
override fun onFoo() {
// Handle signal
}
})
cancelable.cancel() // Stop receiving signals
```
### Java - Callback API
**Callback-based (refined):**
```java
Cancelable cancelable = service.subscribeFoo(new SampleService.FooCallback() {
@OverRide
public void onFoo() {
// Handle signal
}
});
cancelable.cancel(); // Stop receiving signals
```
### Breaking Changes
**Method naming:**
- `setFooCallback()` → `subscribeFoo()` (returns `Cancelable`)
- `nativeSetFooCallback()` → `nativeSubscribeFoo()`
**Callback method naming fix:**
- `onOnIndoorUpdated()` → `onIndoorUpdated()` (fixed double-on prefix)
**Rationale:**
- "subscribe" correctly indicates multiple concurrent observers (vs
"set" implying single callback)
- Returns `Cancelable` for explicit lifecycle management
- Smart callback naming avoids double "on" prefix for signals starting
with "on"
## Implementation
### Core Components
**SignalPublisher.kt** (`src/support/java/src/main/kotlin/`)
- Bridges callback-based signals to Kotlin Flow
- Uses `callbackFlow` with automatic cleanup via `awaitClose`
- `.conflate()` strategy - keeps only latest value for state-based
signals
- Error handling during registration and cancellation
**Generator Updates:**
1. **kotlin.js** - Generate dual API:
- Flow properties (e.g., `val foo: Flow<Unit>`)
- Subscribe methods (e.g., `subscribeFoo()` returning `Cancelable`)
- Uses `SignalPublisher.create()` to bridge callback to Flow
2. **java.js** - Generate callback API:
- Subscribe methods (e.g., `subscribeFoo()` returning `Cancelable`)
- Callback interfaces with smart naming
3. **jni.js** - JNI layer enhancements:
- Native subscribe methods returning `Cancelable`
- Callback handling with proper lifecycle management
- Includes `<mapbox/common/cancelable.jni.hpp>`
4. **cpp_helpers.js** - Helper utilities:
- `signalToCallbackMethodName()` - Smart "on" prefix handling
- Avoids double prefix for signals starting with "on"
### Stub Infrastructure
**Purpose:** Allow bindgen feature tests to compile without depending on
full common SDK
**Java stubs:**
`src/support/mapbox/bindgen/java/stubs/com/mapbox/common/`
- `Cancelable.java` - Interface for lifecycle management
- `CancelableNative.java` - Native implementation wrapper
**JNI stubs:** `src/support/mapbox/bindgen/jni/stubs/mapbox/common/`
- `cancelable.hpp` - C++ interface
- `cancelable.jni.hpp` - JNI bridge header
- `cancelable.jni.cpp` - JNI marshaller implementation
**Path structure:** Production-compatible (`mapbox/common/`) so
generated code works with both stubs and production implementations
## Testing
### Kotlin Feature Tests
`make feature signals kotlin` - 1 scenario (1 passed), 14 steps (14
passed)
**Test coverage:**
- Test 1-8: Callback-based API (basic, multiple subscribers,
cancellation, re-subscription, threading)
- Test 9-13: Flow-based API (collection, parameters, tuples,
cancellation, multiple collectors)
### Java Feature Tests
`make feature signals java` - 1 scenario (1 passed), 14 steps (14
passed)
**Test coverage:**
- Test 1-8: Callback-based API (basic, multiple subscribers,
cancellation, re-subscription, threading, tuple signals)
### Performance Benchmarks
Fixed benchmark failures caused by `SignalPublisher.kt` requiring
`Cancelable` dependency
- Updated `benchmarks/performance/support.js` to copy stubs to support
module
### Android Integration Tests
Fixed build with centralized plugin version management
- Updated `test/android/settings.gradle.kts` with `pluginManagement`
- Support module uses `kotlin("jvm")` without version (inherited from
parent)
- Standalone build still works via
`src/support/java/settings.gradle.kts`
## Build Configuration Updates
### Support Module (`src/support/java/build.gradle.kts`)
```kotlin
// Added Kotlin stdlib and coroutines for Flow support
api("org.jetbrains.kotlin:kotlin-stdlib:1.7.20")
api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")
// Include stubs for compilation but exclude from JAR
sourceSets {
main {
java {
srcDir("../mapbox/bindgen/java/stubs")
}
}
}
tasks.jar {
exclude("com/mapbox/common/**") // Don't publish stubs
}
```
### Feature Test CMakeLists.txt
```cmake
# Both Kotlin and Java signal tests now include stubs
target_include_directories(signals PRIVATE
${CMAKE_CURRENT_LIST_DIR}/../../../../src/support/mapbox/bindgen/jni/stubs
)
target_sources(signals PRIVATE
${CMAKE_CURRENT_LIST_DIR}/../../../../src/support/mapbox/bindgen/jni/stubs/mapbox/common/cancelable.jni.cpp
)
```
## Dependencies for Integration
Projects integrating this change need to add coroutine dependencies:
**build.gradle.kts:**
```kotlin
api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4") // For Android
```
**Common SDK:**
- Add `SignalPublisher.kt` to java/kotlin source directory
cc @mapbox/core-sdk
cc @mapbox/maps-android
cc @mapbox/gl-native
cc @mapbox/sdk-platform
cc @mapbox/nav-core-sdk
---------
Co-authored-by: Aleksei Sapitskii <[email protected]>
GitOrigin-RevId: 3be10f266514eb7118efa6fedea320fb34d88cd41 parent 8077dee commit 21224c0
File tree
2 files changed
+13
-7
lines changed- plugin-indoorselector/src/main/java/com/mapbox/maps/plugin/indoorselector
2 files changed
+13
-7
lines changed| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
30 | 30 | | |
31 | 31 | | |
32 | 32 | | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
33 | 36 | | |
34 | 37 | | |
35 | 38 | | |
| |||
Lines changed: 10 additions & 7 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
| 8 | + | |
8 | 9 | | |
9 | 10 | | |
10 | 11 | | |
| |||
28 | 29 | | |
29 | 30 | | |
30 | 31 | | |
31 | | - | |
| 32 | + | |
32 | 33 | | |
33 | 34 | | |
34 | 35 | | |
| |||
40 | 41 | | |
41 | 42 | | |
42 | 43 | | |
43 | | - | |
| 44 | + | |
44 | 45 | | |
45 | 46 | | |
46 | 47 | | |
47 | | - | |
| 48 | + | |
48 | 49 | | |
49 | 50 | | |
50 | 51 | | |
51 | 52 | | |
52 | 53 | | |
| 54 | + | |
| 55 | + | |
53 | 56 | | |
54 | 57 | | |
55 | 58 | | |
| |||
72 | 75 | | |
73 | 76 | | |
74 | 77 | | |
| 78 | + | |
75 | 79 | | |
76 | 80 | | |
| 81 | + | |
77 | 82 | | |
78 | 83 | | |
79 | 84 | | |
| |||
113 | 118 | | |
114 | 119 | | |
115 | 120 | | |
| 121 | + | |
116 | 122 | | |
117 | 123 | | |
118 | 124 | | |
| |||
122 | 128 | | |
123 | 129 | | |
124 | 130 | | |
| 131 | + | |
125 | 132 | | |
126 | 133 | | |
127 | | - | |
128 | 134 | | |
129 | 135 | | |
130 | 136 | | |
131 | 137 | | |
132 | 138 | | |
133 | 139 | | |
134 | 140 | | |
135 | | - | |
136 | | - | |
137 | | - | |
138 | 141 | | |
139 | 142 | | |
140 | 143 | | |
| |||
0 commit comments