Generic Encodable Firestore write methods crash on Android (addDocument(from:), setData(from:), WriteBatch.setData(from:forDocument:))
The generic Encodable Firestore write APIs crash the process on Android the moment
they're called. iOS is unaffected (native Firebase). Affected:
CollectionReference.addDocument(from:)DocumentReference.setData(from:)/setData(from:mergeFields:)WriteBatch.setData(from:forDocument:)/setData(from:forDocument:mergeFields:)
The Decodable read counterpart DocumentSnapshot.decoded() had a related Android
bridging problem fixed in #82 (#81) via @inline(__always). The generic Encodable
write methods never received that treatment.
- skip-firebase:
main(9a3f608) - Skip: 1.9.2, skip-fuse-ui: 1.0.0
- App mode: Skip Fuse (native Swift for Android),
.swiftLanguageMode(.v5) - Device: Android emulator, API 36, arm64
- Firebase Android 34.x / iOS 12.14.0 (as pulled in by the SkipFirebase modules)
Minimal Skip Fuse app: https://github.com/loopless/skip-firebase-encodable-write-repro
It configures Firebase entirely in code with dummy FirebaseOptions — no
GoogleService-Info.plist, no google-services.json, no com.google.gms.google-services
plugin — because the crash happens during local encoding/bridging, before any network.
Build & run:
skip app launch --androidTap the WriteBatch.setData(from:forDocument:) row (synchronous, never committed →
zero network). The minimal model:
struct ReproItem: Codable, Identifiable {
var id: String = UUID().uuidString
var title: String = "batch"
var amount: Double = 42
var done: Bool = false
}
let batch = Firestore.firestore().batch()
_ = try batch.setData(from: ReproItem(), forDocument: db.collection("repro").document())The value is encoded to [String: Any] and staged/written, as it is on iOS (no crash).
F SwiftRuntime: 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
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 (app) ReproStore.runBatchEncodable()
Full trace: https://github.com/loopless/skip-firebase-encodable-write-repro/blob/main/CRASH_LOG.txt
The primitive [String: Any] write
(batch.setData(["title": ..., "amount": 42.0], forDocument:)) does not crash — that's
the control row in the repro, and the workaround we currently use.
The generic write methods encode then forward to the [String: Any] overload, e.g.:
public func setData<T: Encodable>(from value: T, forDocument document: DocumentReference) throws -> WriteBatch {
let data = try FirestoreEncoder().encode(value) // expected to run on the native side
return setData(data, forDocument: document)
}On Android the raw generic value is marshalled across the JNI boundary (frames
#02/#04/#05) before the body runs, and AnyBridging.toJavaObject can't bridge an arbitrary
Codable Swift instance, so it asserts — the FirestoreEncoder().encode(value) →
dictionary conversion never gets to run natively.
Apply the same @inline(__always) used for decoded() in #82 to the generic Encodable
write methods (addDocument(from:), setData(from:...), and the WriteBatch
setData(from:...) variants), so the value is encoded to [String: Any] in native Swift
and only the (bridgeable) dictionary crosses into the bridged [String: Any] overload.