Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ See the `Package.swift` files in the
| Module | iOS API Coverage | Highlights | Notable Gaps |
|---|:---:|---|---|
| `SkipFirebaseCore` | ~80% | `FirebaseApp.configure` (default + named + custom `FirebaseOptions`), app lifecycle, `Timestamp`, deep Kotlin↔Swift value conversion helpers | Custom `FirebaseLogger`, advanced data-collection toggles |
| `SkipFirebaseFirestore` | ~80% | Collections, documents, batched writes, queries, `Filter.and`/`.or` composition, `FieldPath`, aggregate queries (`count`/`average`/`sum`), snapshot listeners, `FieldValue` sentinels, `LoadBundleTaskProgress`, errors mapped to `NSError` with `FirestoreErrorDomain`/`FirestoreErrorCode` | `runTransaction` (`Transaction` is currently a passthrough wrapper), `Codable` document encoding/decoding, `GeoPoint`, `FirestoreSettings`/cache configuration, `InputStream`-based bundle loading |
| `SkipFirebaseFirestore` | ~90% | Collections, documents, batched writes, queries, `Filter.and`/`.or` composition, `FieldPath`, aggregate queries (`count`/`average`/`sum`), snapshot listeners, `FieldValue` sentinels, `LoadBundleTaskProgress`, `GeoPoint`, `FirestoreSettings` with persistent/memory cache configuration, `DocumentSnapshot.metadata`/`reference`/`get(FieldPath)`, completion-based `addDocument`, `getDocument(source:)`, `setData(mergeFields:)`, `Codable` encoding/decoding via `FirestoreEncoder`/`FirestoreDecoder`, errors mapped to `NSError` with `FirestoreErrorDomain`/`FirestoreErrorCode` | `runTransaction` (`Transaction` is currently a passthrough wrapper), `InputStream`-based bundle loading, `Codable` decoding uses `snapshot.decoded()` on Android (not `data(as:)` due to a Skip transpiler limitation with `T.Type` parameters) |
| `SkipFirebaseAuth` | ~60% | Email/password sign-in, anonymous sign-in, email-link sign-in, interactive OAuth provider sign-in (Activity-based), state-change listener, ID-token retrieval, profile changes, account linking, reauthentication, `fetchSignInMethods`, `ActionCodeSettings`, partial `AuthErrorCode` mapping | Phone auth (`PhoneAuthProvider`, SMS verification), multi-factor (`MultiFactor`/`MultiFactorResolver`), `applyActionCode`/`checkActionCode`/`confirmPasswordReset`/`verifyPasswordResetCode`, custom-token sign-in, `GameCenterAuthProvider`, language/tenant configuration, `updateEmail`/`updatePassword` |
| `SkipFirebaseStorage` | ~80% | Bucket and reference resolution, upload (`putFile`/`putData` in both callback and `async` forms), download (`getData`/`write(toFile:)`), `StorageMetadata` read/write, `downloadURL`, `delete`, pause/resume/cancel on uploads, `list`/`listAll` with pagination (`pageToken`), live `Progress` snapshot observers on uploads and file downloads | Full `StorageError` code mapping, `putStream`/`putString` |
| `SkipFirebaseMessaging` | ~70% | FCM token retrieval + auto-refresh, topic subscribe/unsubscribe, `MessagingDelegate`, `MessagingService` integrating with iOS-style `UNUserNotificationCenterDelegate`, intent routing, localized `loc_key`/`loc_args` title/body, `RemoteMessage` → `UNNotification` translation | Per-sender FCM token APIs and notification-service-extension helpers (`populateNotificationContent`, `exportDeliveryMetricsToBigQuery`) are stubbed out as `@available(*, unavailable)` |
Expand All @@ -41,7 +41,7 @@ See the `Package.swift` files in the

### What is working well

- **Firestore** is the most complete module: all common CRUD, queries (including `Filter` combinators and `FieldPath` predicates), snapshot listeners (with `MetadataChanges` support), batched writes, aggregate queries, and `FieldValue` sentinels round-trip correctly between Swift and the Firestore Android SDK. Errors are mapped to `NSError` with `FirestoreErrorDomain`/`FirestoreErrorCode` so error handling looks the same on both platforms.
- **Firestore** is the most complete module: all common CRUD, queries (including `Filter` combinators and `FieldPath` predicates), snapshot listeners (with `MetadataChanges` support), batched writes, aggregate queries, `FieldValue` sentinels, `GeoPoint`, and `FirestoreSettings` (persistent and memory cache) round-trip correctly between Swift and the Firestore Android SDK. `Codable` models can be encoded with `setData(from:)` and decoded with `snapshot.decoded()` on Android (note: use `snapshot.data(as:)` from `FirebaseFirestoreSwift` on iOS). Errors are mapped to `NSError` with `FirestoreErrorDomain`/`FirestoreErrorCode` so error handling looks the same on both platforms.
- **Auth** covers the most common sign-in flows (email/password, anonymous, interactive OAuth via the system browser, email-link) and exposes a familiar `User` API with profile changes, account linking, reauthentication, ID-token retrieval, and a state-change listener.
- **Messaging** handles the full FCM lifecycle: token retrieval and auto-refresh, topic subscribe/unsubscribe, `RemoteMessage` → `UNNotification` translation (including localized `loc_key`/`loc_args` substitution from the app's `Localizable.strings`), and a `MessagingService` that integrates with iOS-style `UNUserNotificationCenterDelegate`.
- **Storage** supports both data-based and file-based uploads/downloads in callback and `async` forms, plus full `StorageMetadata` read/write.
Expand All @@ -52,6 +52,7 @@ See the `Package.swift` files in the
- **Realtime Database** (`SkipFirebaseDatabase`) is currently only a stub. `DatabaseReference`, `observe`/`observeSingleEvent`, queries, and writes have not been bridged. Apps that rely on the Realtime Database should use Firestore instead or contribute the missing wrappers.
- **Phone-number authentication and multi-factor flows** in `SkipFirebaseAuth` are not bridged. Apps requiring SMS verification or MFA need to drop into `#if SKIP` blocks and call the Android Firebase SDK directly.
- **`runTransaction`** in Firestore is not implemented — the `Transaction` class is currently a passthrough wrapper with no operations. Multi-document atomic reads-then-writes need to be expressed as `WriteBatch` commits or as Kotlin-side code today.
- **`Codable` decoding API differs by platform** — on Android use `snapshot.decoded()` with explicit type annotation (`let m: MyModel = try snapshot.decoded()`); on iOS use `FirebaseFirestoreSwift`'s `snapshot.data(as: MyModel.self)`. The `setData(from:)` encoding API works identically on both platforms.
- **`SkipFirebaseFunctions`** does not support streaming RPCs (`stream`). The Firebase Android SDK has no SSE/streaming equivalent to the iOS `AsyncThrowingStream`-based `stream` API — Android Functions calls are always one-shot request/response. Any code that iterates over `.stream(...)` on iOS will not compile on Android. The `Callable<Request, Response>` Codable convenience wrapper is also not bridged; use `call(_ data: Any?)` directly and decode the result manually on Android.
- **`SkipFirebaseRemoteConfig`** does not yet expose `addOnConfigUpdateListener` (real-time config updates) or custom signals.
- **`SkipFirebaseAppCheck`** ships only the `Debug` provider factory; custom provider implementations are not yet bridgeable.
Expand Down
59 changes: 58 additions & 1 deletion Sources/SkipFirebaseCore/SkipFirebaseCore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ public final class FirebaseOptions {
// https://firebase.google.com/docs/reference/swift/firebasefirestore/api/reference/Classes/Timestamp
// https://firebase.google.com/docs/reference/android/com/google/firebase/Timestamp

public class Timestamp: Hashable, KotlinConverting<com.google.firebase.Timestamp> {
public final class Timestamp: Hashable, Codable, KotlinConverting<com.google.firebase.Timestamp> {
public let timestamp: com.google.firebase.Timestamp

public init(timestamp: com.google.firebase.Timestamp) {
Expand Down Expand Up @@ -193,8 +193,34 @@ public class Timestamp: Hashable, KotlinConverting<com.google.firebase.Timestamp
public var nanoseconds: Int32 {
timestamp.nanoseconds
}

private enum CodingKeys: String, CodingKey {
case seconds = "__fts__"
case nanoseconds = "__ftn__"
}

public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(seconds, forKey: .seconds)
try container.encode(nanoseconds, forKey: .nanoseconds)
}

public init(from decoder: Decoder) throws {
// Support decoding from a plain Double (seconds since epoch) produced by FirestoreDecoder's prepareForJSON pass
if let svc = try? decoder.singleValueContainer(), let interval = try? svc.decode(Double.self) {
let s = Int64(interval)
let n = Int32((interval - Double(s)) * 1_000_000_000)
self.timestamp = com.google.firebase.Timestamp(s, n)
return
}
let container = try decoder.container(keyedBy: CodingKeys.self)
let s = try container.decode(Int64.self, forKey: .seconds)
let n = try container.decode(Int32.self, forKey: .nanoseconds)
self.timestamp = com.google.firebase.Timestamp(s, n)
}
}


// MARK: - Kotlin to Swift type conversion helpers

public func deepSwift(value: Any?) -> Any? {
Expand Down Expand Up @@ -236,3 +262,34 @@ public func deepSwift(collection: kotlin.collections.Collection<Any?>) -> Array<

#endif
#endif

#if SKIP_BRIDGE
// The generated Timestamp bridge class declares Codable but cannot auto-synthesize it
// (JObject peer is not Codable, and encode/decode methods are not JNI-bridgeable).
// This extension satisfies the Codable conformance using the JNI-bridged properties.
extension Timestamp {
private enum CodingKeys: String, CodingKey {
case seconds = "__fts__"
case nanoseconds = "__ftn__"
}

public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(seconds, forKey: .seconds)
try container.encode(nanoseconds, forKey: .nanoseconds)
}

public convenience init(from decoder: Decoder) throws {
if let svc = try? decoder.singleValueContainer(), let interval = try? svc.decode(Double.self) {
let s = Int64(interval)
let n = Int32((interval - Double(s)) * 1_000_000_000)
self.init(seconds: s, nanoseconds: n)
return
}
let container = try decoder.container(keyedBy: CodingKeys.self)
let s = try container.decode(Int64.self, forKey: .seconds)
let n = try container.decode(Int32.self, forKey: .nanoseconds)
self.init(seconds: s, nanoseconds: n)
}
}
#endif
Loading