Minimal Skip Fuse app reproducing a crash in skip-firebase on Android.
The generic Encodable Firestore write methods crash the process on Android the moment
they are called:
CollectionReference.addDocument(from:)DocumentReference.setData(from:)/setData(from:mergeFields:)WriteBatch.setData(from:forDocument:)/setData(from:forDocument:mergeFields:)
The abort is:
SkipBridge/BridgedTypes.swift:198: Fatal error: Unable to bridge Swift instance
ReproItem(id: ..., title: "batch", amount: 42.0, done: false) of type: ReproItem;
this is usually due to missing // SKIP @bridge or // SKIP @bridgeMembers on the Swift type
i.e. when one of these generic methods is called from native Swift, the raw generic
value (T) is marshalled across the JNI bridge (SwiftJNI.jniContext →
SkipBridge.AnyBridging.toJavaObject) instead of being encoded to [String: Any] on the
native side first. A plain Codable model is not a @bridge type, so toJavaObject
hits _assertionFailure and the process dies with SIGTRAP.
The equivalent primitive [String: Any] write methods work fine on both platforms —
that is the control case in this repro, and the workaround currently in use downstream.
The Decodable read counterpart, DocumentSnapshot.decoded(), had a related Android
bridging problem (#81) that was fixed in #82 by marking it @inline(__always) (skipstone
then emits a Kotlin inline fun <reified T> and the generic value never crosses the
bridge). The generic Encodable write methods never received that treatment.
iOS is unaffected (native Firebase); the crash is Android-only.
- skip-firebase:
main(9a3f608at time of filing) — i.e. the tree that already contains the #82decoded()fix - Skip: 1.9.2
- skip-fuse-ui: 1.0.0
- App mode: Skip Fuse (native Swift compiled for Android),
.swiftLanguageMode(.v5) - Firebase iOS 12.14.0 / Firebase Android 34.x (whatever the skip-firebase modules pull in)
This repro configures Firebase entirely in code with throwaway, well-formed
FirebaseOptions (Sources/Repro/ReproApp.swift) — there is no
GoogleService-Info.plist and no google-services.json, and the
com.google.gms.google-services Gradle plugin is not applied. The crash happens in
the bridge during local encoding, before any network round-trip, so dummy options are
enough to reproduce it. The WriteBatch.setData(from:forDocument:) case is synchronous
and is never committed, so it touches the network not at all.
An Android emulator must be running. Then:
skip launch(or open Project.xcworkspace in Xcode and run the "Repro App" target, which deploys to
the running emulator). Watch logs with adb logcat.
Tap each row in the app:
| Case | iOS | Android |
|---|---|---|
WriteBatch.setData(from:forDocument:) |
OK | CRASH |
DocumentReference.setData(from:) |
OK | CRASH |
CollectionReference.addDocument(from:) |
OK | CRASH |
WriteBatch.setData([String: Any]:) (control) |
OK | OK |
All four cases complete without crashing (as they do on iOS).
The generic-Encodable cases abort the process. Verified on an Android emulator
(API 36, arm64) against skip-firebase main (9a3f608) by tapping
WriteBatch.setData(from:forDocument:). The primitive-dictionary control case keeps the
app running. Full trace in CRASH_LOG.txt; top frames:
F SwiftRuntime: SkipBridge/BridgedTypes.swift:198: Fatal error: Unable to bridge Swift
instance ReproItem(...) of type: ReproItem; this is usually due to missing
// SKIP @bridge or // SKIP @bridgeMembers on the Swift type
F libc : Fatal signal 5 (SIGTRAP), code 1 (TRAP_BRKPT) ... >>> com.example.firebasedecodedrepro
#00 libswiftCore.so _assertionFailure(...)
#01 SkipBridge.AnyBridging.toJavaObject(_:options:)
#02 SkipFirebaseFirestore.WriteBatch.setData(from:forDocument:) {closure}
#04 SwiftJNI.jniContext(...)
#05 SkipFirebaseFirestore.WriteBatch.setData(from:forDocument:)
#06 Repro.ReproStore.runBatchEncodable()
The WriteBatch case is synchronous and never committed, so this is purely the
encode/bridge call — no network, no real project.
The generic write methods in SkipFirebaseFirestore.swift are plain generic functions
that encode the value and forward to the [String: Any] overload:
public func setData<T: Encodable>(from value: T, forDocument document: DocumentReference) throws -> WriteBatch {
let data = try FirestoreEncoder().encode(value)
return setData(data, forDocument: document)
}On Android, calling this from native Swift marshals the generic value itself across
the JNI boundary (frames #02/#04/#05 above) before the body runs, and
AnyBridging.toJavaObject cannot bridge an arbitrary Codable Swift instance — so it
asserts. The intended FirestoreEncoder().encode(value) → [String: Any] conversion
never gets the chance to run on the native side.
decoded() had a comparable Android bridging problem and was fixed in #82 by marking it
@inline(__always):
@inline(__always)
public func decoded<T: Decodable>() throws -> T { ... }Applying the same @inline(__always) treatment to the generic Encodable write methods
(addDocument(from:), setData(from:...), and the WriteBatch setData(from:...)
variants) should inline them at the call site so the value is encoded to [String: Any]
in native Swift and only the (bridgeable) dictionary crosses into the bridged
[String: Any] overload — matching the #82 approach.
Sources/Repro/ReproApp.swift— dummy-options Firebase configurationSources/Repro/ViewModel.swift—ReproStore, runs the four casesSources/Repro/ContentView.swift— one tappable row per case with its result