{"id":28435173,"url":"https://github.com/skiptools/skip-firebase","last_synced_at":"2026-04-02T13:47:19.251Z","repository":{"id":205093503,"uuid":"713405907","full_name":"skiptools/skip-firebase","owner":"skiptools","description":"Firebase integration for Skip apps","archived":false,"fork":false,"pushed_at":"2026-03-26T03:24:19.000Z","size":353,"stargazers_count":13,"open_issues_count":6,"forks_count":19,"subscribers_count":3,"default_branch":"main","last_synced_at":"2026-03-26T17:51:27.703Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://skip.dev/docs/modules/skip-firebase/","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/skiptools.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.LGPL","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"custom":["https://skip.dev/sponsor"]}},"created_at":"2023-11-02T13:08:27.000Z","updated_at":"2026-03-26T01:44:44.000Z","dependencies_parsed_at":null,"dependency_job_id":"d1bd4e02-a92f-40d1-876b-607b560d576d","html_url":"https://github.com/skiptools/skip-firebase","commit_stats":null,"previous_names":["skiptools/skip-base"],"tags_count":46,"template":false,"template_full_name":null,"purl":"pkg:github/skiptools/skip-firebase","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/skiptools%2Fskip-firebase","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/skiptools%2Fskip-firebase/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/skiptools%2Fskip-firebase/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/skiptools%2Fskip-firebase/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/skiptools","download_url":"https://codeload.github.com/skiptools/skip-firebase/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/skiptools%2Fskip-firebase/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31307204,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-02T12:59:32.332Z","status":"ssl_error","status_checked_at":"2026-04-02T12:54:48.875Z","response_time":89,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-06-05T20:06:50.569Z","updated_at":"2026-04-02T13:47:19.241Z","avatar_url":"https://github.com/skiptools.png","language":"Swift","funding_links":["https://skip.dev/sponsor"],"categories":[],"sub_categories":[],"readme":"# SkipFirebase\n\nThis package provides Firebase support for [Skip](https://skip.dev) Swift projects.\nThe Swift side uses the official Firebase iOS SDK directly,\nwith the various `SkipFirebase*` modules passing the transpiled calls\nthrough to the Firebase Android SDK.\n\nFor an example of using Firebase in a [Skip Fuse](https://skip.dev/docs/modes/#fuse) app, see the\n[FiresideFuse Sample](https://github.com/skiptools/skipapp-fireside-fuse/). For a [Skip Lite](https://skip.dev/docs/modes/#lite) app, see the [Fireside Sample](https://github.com/skiptools/skipapp-fireside/).\n\n## Package\n\nThe modules in the SkipFirebase framework project mirror the division of the SwiftPM\nmodules in the Firebase iOS SDK (at [https://github.com/firebase/firebase-ios-sdk.git](https://github.com/firebase/firebase-ios-sdk.git)),\nwhich is also mirrored in the division of the Firebase Kotlin Android gradle modules (at [https://github.com/firebase/firebase-android-sdk.git](https://github.com/firebase/firebase-android-sdk.git)).\n\nSee the `Package.swift` files in the\n[FiresideFuse](https://github.com/skiptools/skipapp-fireside-fuse/) and [Fireside](https://github.com/skiptools/skipapp-fireside/) apps for examples of integrating Firebase dependencies.\n\n\u003c!--\nAn example of a Skip Lite app projects using the `Firestore` API at the model layer and the `Messaging` API at the app layer can be seen from the command:\n\n```console\nskip init --show-tree --icon-color='1abc9c' --no-zero --appid=skip.fireside.App --version 0.0.1 skipapp-fireside FireSide:skip-ui/SkipUI:skip-firebase/SkipFirebaseMessaging FireSideModel:skip-foundation/SkipFoundation:skip-model/SkipModel:skip-firebase/SkipFirebaseFirestore:skip-firebase/SkipFirebaseAuth\n```\n\nThis will create an SwiftPM project in `skipapp-fireside/Package.swift` like:\n\n```swift\n// swift-tools-version: 5.9\nimport PackageDescription\n\nlet package = Package(\n    name: \"skipapp-fireside\",\n    defaultLocalization: \"en\",\n    platforms: [.iOS(.v16), .macOS(.v13), .tvOS(.v16), .watchOS(.v9), .macCatalyst(.v16)],\n    products: [\n        .library(name: \"FireSideApp\", type: .dynamic, targets: [\"FireSide\"]),\n        .library(name: \"FireSideModel\", targets: [\"FireSideModel\"]),\n    ],\n    dependencies: [\n        .package(url: \"https://source.skip.dev/skip.git\", from: \"1.0.0\"),\n        .package(url: \"https://source.skip.dev/skip-ui.git\", from: \"1.0.0\"),\n        .package(url: \"https://source.skip.dev/skip-foundation.git\", from: \"1.0.0\"),\n        .package(url: \"https://source.skip.dev/skip-model.git\", from: \"1.0.0\"),\n        .package(url: \"https://source.skip.dev/skip-firebase.git\", \"0.0.0\"..\u003c\"2.0.0\")\n    ],\n    targets: [\n        .target(name: \"FireSide\", dependencies: [\n            \"FireSideModel\", \n            .product(name: \"SkipUI\", package: \"skip-ui\"),\n            .product(name: \"SkipFirebaseMessaging\", package: \"skip-firebase\")\n        ], resources: [.process(\"Resources\")], plugins: [.plugin(name: \"skipstone\", package: \"skip\")]),\n        .testTarget(name: \"FireSideTests\", dependencies: [\n            \"FireSide\", \n            .product(name: \"SkipTest\", package: \"skip\")\n        ], resources: [.process(\"Resources\")], plugins: [.plugin(name: \"skipstone\", package: \"skip\")]),\n        \n        .target(name: \"FireSideModel\", dependencies: [\n            .product(name: \"SkipFoundation\", package: \"skip-foundation\"), \n            .product(name: \"SkipModel\", package: \"skip-model\"), \n            .product(name: \"SkipFirebaseFirestore\", package: \"skip-firebase\"),  \n            .product(name: \"SkipFirebaseAuth\", package: \"skip-firebase\")\n        ], resources: [.process(\"Resources\")], plugins: [.plugin(name: \"skipstone\", package: \"skip\")]),\n        .testTarget(name: \"FireSideModelTests\", dependencies: [\n            \"FireSideModel\", \n            .product(name: \"SkipTest\", package: \"skip\")\n        ], resources: [.process(\"Resources\")], plugins: [.plugin(name: \"skipstone\", package: \"skip\")]),\n    ]\n)\n```\n--\u003e\n\n## Setup\n\nFor a Skip app, the simplest way to setup Firebase support is to\ncreate a Firebase project at [https://console.firebase.google.com/project](https://console.firebase.google.com/project).\nFollow the Firebase setup instructions to obtain the \n`GoogleService-Info.plist` and `google-services.json` files and\nadd them to the iOS and Android sides of the project:\n\n- The `GoogleService-Info.plist` file should be placed in the `Darwin/` folder of the Skip project\n- The `google-services.json` file should be placed in the `Android/app/` folder of the Skip project\n\nIn addition, the `com.google.gms.google-services` plugin will need to be added to the \nAndroid app's `Android/app/build.gradle.kts` file in order to process the `google-services.json`\nfile for the app, like so:\n\n```kotlin\nplugins {\n    alias(libs.plugins.kotlin.android)\n    alias(libs.plugins.android.application)\n    id(\"skip-build-plugin\")\n    id(\"com.google.gms.google-services\") version \"4.4.4\" apply true\n    id(\"com.google.firebase.crashlytics\") version \"3.0.2\" apply true # (if using Crashlytics)\n}\n```\n\nFor concrete examples, see the [FireSideFuse Sample](https://github.com/skiptools/skipapp-fireside-fuse/) project.\n{: class=\"callout info\"}\n\nOnce Firebase has been added to your project, you need to configure the `FirebaseApp` on app startup. This is typically done in the `onInit()` callback of the `*AppDelegate` in your `*App.swift` file. Here is a snippet from the FireSideFuse sample app:\n\n```swift\n#if os(Android)\nimport SkipFirebaseCore\n#else\nimport FirebaseCore\n#endif\n\n...\n\n/* SKIP @bridge */public final class FireSideFuseAppDelegate : Sendable {\n    /* SKIP @bridge */public static let shared = FireSideFuseAppDelegate()\n\n    ...\n\n    /* SKIP @bridge */public func onInit() {\n        logger.debug(\"onInit\")\n\n        FirebaseApp.configure()\n        ...\n    }\n\n    ...\n}\n```\n\nAfter configuring the `FirebaseApp`, you will be able to access the singleton type for each of the\nimported Firebase modules. For example, the following actor uses the `Firestore` singleton:\n\n```swift\n// Sources/FireSideFuse/FireSideFuseApp.swift\n\n#if os(Android)\nimport SkipFirebaseFirestore\n#else\nimport FirebaseFirestore\n#endif\n\n...\n\npublic actor Model {\n    /// The shared model singleton\n    public static let shared = Model()\n\n    private let firestore: Firestore\n\n    private init() {\n        self.firestore = Firestore.firestore()\n    }\n    \n    public func queryData() async throws -\u003e [DataModel] { ... }\n    public func saveData(model: DataModel) async throws { ... }\n\n    ...\n}\n```\n\n## Messaging\n\nAfter [setting up](#setup) your app to use Firebase, enabling push notifications via Firebase Cloud Messaging (FCM) requires a number of additional steps.\n\n1. Follow Firebase's [instructions](https://firebase.google.com/docs/cloud-messaging/ios/client) for creating and uploading your Apple Push Notification Service (APNS) key.\n1. Use Xcode to [add the Push capability](https://developer.apple.com/documentation/xcode/adding-capabilities-to-your-app/) to your iOS app.\n1. Add Skip's Firebase messaging service and default messaging channel to `Android/app/src/main/AndroidManifest.xml`:\n\n    ```xml\n    ...\n    \u003capplication ...\u003e\n        ...\n        \u003cservice\n            android:name=\"skip.firebase.messaging.MessagingService\"\n            android:exported=\"false\"\u003e\n            \u003cintent-filter\u003e\n                \u003caction android:name=\"com.google.firebase.MESSAGING_EVENT\" /\u003e\n            \u003c/intent-filter\u003e\n        \u003c/service\u003e\n        \u003cmeta-data\n            android:name=\"com.google.firebase.messaging.default_notification_channel_id\"\n            android:value=\"tools.skip.firebase.messaging\" /\u003e\n    \u003c/application\u003e\n    ```\n\n1. Consider increasing the `minSdk` version of your Android app. Prior to SDK 33, Android does not provide any control over asking the user for push notification permissions. Rather, the system will prompt the user for permission only after receiving a notification and opening the app. Increasing your `minSdk` will allow you to decide when to request notification permissions. To do so, edit your `Android/app/build.gradle.kts` file and change the `minSdk` value to 33.\n1. Define a delegate to receive notification callbacks. In keeping with Skip's philosophy of *transparent adoption*, both the iOS and Android sides of your app will receive callbacks via iOS's standard `UNUserNotificationCenterDelegate` API, as well as the Firebase iOS SDK's `MessagingDelegate`. Here are example [Skip Fuse](https://skip.dev/docs/modes/#fuse) delegate implementations that works across both platforms:\n\n```swift\nimport SwiftFuseUI\n#if os(Android)\nimport SkipFirebaseMessaging\n#else\nimport FirebaseMessaging\n#endif\n\nfinal class NotificationDelegate : NSObject, UNUserNotificationCenterDelegate, Sendable {\n    public func requestPermission() {\n        let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]\n        Task { @MainActor in\n            do {\n                if try await UNUserNotificationCenter.current().requestAuthorization(options: authOptions) {\n                    logger.info(\"notification permission granted\")\n                } else {\n                    logger.info(\"notification permission denied\")\n                }\n            } catch {\n                logger.error(\"notification permission error: \\(error)\")\n            }\n        }\n    }\n\n    public func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification) async -\u003e UNNotificationPresentationOptions {\n        let content = notification.request.content\n        logger.info(\"willPresentNotification: \\(content.title): \\(content.body) \\(content.userInfo)\")\n        return [.banner, .sound]\n    }\n\n    public func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse) async {\n        let content = response.notification.request.content\n        logger.info(\"didReceiveNotification: \\(content.title): \\(content.body) \\(content.userInfo)\")\n        #if os(Android) || !os(macOS)\n        // Example of using a deep_link key passed in the notification to route to the app's `onOpenURL` handler\n        if let deepLink = response.notification.request.content.userInfo[\"deep_link\"] as? String, let url = URL(string: deepLink) {\n            Task { @MainActor in\n                await UIApplication.shared.open(url)\n            }\n        }\n        #endif\n    }\n}\n\n// Your Firebase MessageDelegate must bridge because we use the Firebase Kotlin API on Android.\n/* SKIP @bridge */final class MessageDelegate : NSObject, MessagingDelegate, Sendable {\n    /* SKIP @bridge */public func messaging(_ messaging: Messaging, didReceiveRegistrationToken token: String?) {\n        logger.info(\"didReceiveRegistrationToken: \\(token ?? \"nil\")\")\n    }\n}\n```\n\n1. Wire everything up. This includes assigning your shared delegate, registering for remote notifications, and other necessary steps. Below we build on our [previous Firebase setup code](#setup) to perform these actions. This is taken from our FireSideFuse sample app:\n\n```swift\n// Sources/FireSideFuse/FireSideFuseApp.swift\n\n#if os(Android)\nimport SkipFirebaseCore\n#else\nimport FirebaseCore\n#endif\n\n...\n\n/* SKIP @bridge */public final class FireSideFuseAppDelegate : Sendable {\n    /* SKIP @bridge */public static let shared = FireSideFuseAppDelegate()\n\n    private let notificationDelegate = NotificationDelegate()\n    private let messageDelegate = MessageDelegate()\n\n    private init() {\n    }\n\n    /* SKIP @bridge */public func onInit() {\n        logger.debug(\"onInit\")\n\n        // Configure Firebase and notifications\n        FirebaseApp.configure()\n        Messaging.messaging().delegate = messageDelegate\n        UNUserNotificationCenter.current().delegate = notificationDelegate\n    }\n\n    /* SKIP @bridge */public func onLaunch() {\n        logger.debug(\"onLaunch\")\n        // Ask for permissions at a time appropriate for your app\n        notificationDelegate.requestPermission()\n    }\n\n    ...\n}\n```\n\n```swift\n// Darwin/Sources/Main.swift\n\n...\n\nclass AppMainDelegate: NSObject, AppMainDelegateBase {\n    ...\n\n    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -\u003e Bool {\n        AppDelegate.shared.onLaunch()\n        application.registerForRemoteNotifications() // \u003c-- Insert\n        return true\n    }\n\n    ...\n}\n```\n\n```kotlin\n// Android/app/src/main/kotlin/.../Main.kt\n\n...\n\nopen class MainActivity: AppCompatActivity {\n    ...\n\n    override fun onCreate(savedInstanceState: android.os.Bundle?) {\n        ...\n\n        setContent {\n            ...\n        }\n\n        skip.firebase.messaging.Messaging.messaging().onActivityCreated(this) // \u003c-- Insert\n        FireSideFuseAppDelegate.shared.onLaunch()\n\n        ...\n    }\n\n    ...\n}\n```\n\n1. See Firebase's [iOS instructions](https://firebase.google.com/docs/cloud-messaging/ios/client) and [Android instructions](https://firebase.google.com/docs/cloud-messaging/android/client) for additional details and options, including how to send test messages to your apps!\n\nThe [FiresideFuse](https://github.com/skiptools/skipapp-fireside-fuse/) and [Fireside](https://github.com/skiptools/skipapp-fireside/) projects are great references for seeing complete, working Skip Fuse and Skip Lite apps using Firebase push notifications.\n{: class=\"callout info\"}\n\n## Error handling\n\n### Firestore\n\nThe Firestore API converts `com.google.firebase.firestore.FirebaseFirestoreException` to NSError so you can handle errors the same way on both platforms:\n\n```swift\ndo {\n    try await Firestore.firestore().collection(\"foo\").document(\"bar\").updateData(...)\n} catch let error as NSError {\n    if error.domain == FirestoreErrorDomain \u0026\u0026 error.code == FirestoreErrorCode.notFound.rawValue {\n        ...\n    }\n}\n```\n\n### Catching other errors\n\nOther parts of this library have not been updated to this unified error handling. Instead, you can access the underlying Kotlin exceptions in SKIP blocks according to the documentation:\n\n- FirebaseAuth: https://firebase.google.com/docs/reference/android/com/google/firebase/auth/FirebaseAuth\n- FirebaseMessaging.MessagingService.onSendError: https://firebase.google.com/docs/reference/android/com/google/firebase/messaging/SendException\n- FirebaseStorage: https://firebase.google.com/docs/reference/android/com/google/firebase/storage/StorageException\n\n```swift\ndo {\n    try await Storage.storage().reference().child(\"nonexistent\").delete()\n} catch {\n    #if !SKIP\n    let error = error as NSError\n    let errorCode = error.domain == StorageError.errorDomain ? error.code : nil\n    #else\n    let exception = (error as Exception).cause as? com.google.firebase.storage.StorageException\n    let errorCode = exception?.code.value()\n    #endif\n    if errorCode == -13010 { \n        // Object not found\n    }\n}\n```\n\n## Testing\n\nFor unit testing, where there isn't a standard place to store the\n`GoogleService-Info.plist` and `google-services.json` configuration files,\nyou can create an configure the app using the `SkipFirebaseCore.FirebaseApp`\nAPI manually from the information provided from the Firebase console, like so:\n\n```swift\nimport SkipFirebaseCore\nimport SkipFirebaseAuth\nimport SkipFirebaseStorage\nimport SkipFirebaseDatabase\nimport SkipFirebaseAppCheck\nimport SkipFirebaseFunctions\nimport SkipFirebaseFirestore\nimport SkipFirebaseMessaging\nimport SkipFirebaseCrashlytics\nimport SkipFirebaseRemoteConfig\nimport SkipFirebaseInstallations\n\nlet appName = \"myapp\"\nlet options = FirebaseOptions(googleAppID: \"1:GCM:ios:HASH\", gcmSenderID: \"GCM\")\noptions.projectID = \"some-firebase-projectid\"\noptions.storageBucket = \"some-firebase-demo.appspot.com\"\noptions.apiKey = \"some-api-key\"\n\nFirebaseApp.configure(name: appName, options: options)\nguard let app = FirebaseApp.app(name: appName) else {\n    fatalError(\"Cannot load Firebase config\")\n}\n\n// customize the app here\napp.isDataCollectionDefaultEnabled = false\n\n// use the app to create and test services\nlet auth = Auth.auth(app: app)\nlet storage = Storage.storage(app: app)\nlet database = Database.database(app: app)\nlet appcheck = AppCheck.appCheck(app: app)\nlet functions = Functions.functions(app: app)\nlet firestore = Firestore.firestore(app: app)\nlet crashlytics = Crashlytics.crashlytics(app: app)\nlet remoteconfig = RemoteConfig.remoteConfig(app: app)\nlet installations = Installations.installations(app: app)\n```\n\n## Common Errors \n\n```plaintext\nError in adb logcat: FirebaseApp: Default FirebaseApp failed to initialize because no default options were found.\nThis usually means that com.google.gms:google-services was not applied to your gradle project.\n```\n\nThe app's `com.google.gms:google-services` plugin must be applied to the `build.gradle.kts` file for the app's target.\n\n## License\n\nThis software is licensed under the \n[Mozilla Public License 2.0](https://www.mozilla.org/MPL/).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fskiptools%2Fskip-firebase","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fskiptools%2Fskip-firebase","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fskiptools%2Fskip-firebase/lists"}