Skip to content

Latest commit

 

History

History
99 lines (74 loc) · 3.95 KB

File metadata and controls

99 lines (74 loc) · 3.95 KB

Generic Encodable Firestore write methods crash on Android (addDocument(from:), setData(from:), WriteBatch.setData(from:forDocument:))

Summary

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.

Environment

  • 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)

Reproduction

Minimal Skip Fuse app: https://github.com/loopless/skip-firebase-encodable-write-repro

It configures Firebase entirely in code with dummy FirebaseOptionsno 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 --android

Tap 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())

Expected

The value is encoded to [String: Any] and staged/written, as it is on iOS (no crash).

Actual (Android)

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.

Analysis

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.

Suggested fix

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.