|
| 1 | +# GitHub Copilot Instructions — firebase-kotlin-sdk |
| 2 | + |
| 3 | +## Project Overview |
| 4 | + |
| 5 | +This is a **Kotlin-first, multiplatform SDK for Firebase**. It wraps the official Firebase platform SDKs (Android, iOS, JS, JVM) behind a unified Kotlin common API, enabling Firebase to be used directly from shared Kotlin Multiplatform (KMP) source sets targeting **Android**, **iOS**, **Desktop (JVM)**, and **Web (JS)**. |
| 6 | + |
| 7 | +All modules are published under the `dev.gitlive` group ID (e.g. `dev.gitlive:firebase-firestore`). |
| 8 | + |
| 9 | +--- |
| 10 | + |
| 11 | +## Architecture |
| 12 | + |
| 13 | +### Module Structure |
| 14 | + |
| 15 | +Each Firebase product is a separate Gradle module (e.g. `firebase-auth`, `firebase-firestore`, `firebase-database`). Inside each module the source is split by KMP targets: |
| 16 | + |
| 17 | +``` |
| 18 | +src/ |
| 19 | + commonMain/ ← shared public API (Kotlin) |
| 20 | + androidMain/ ← wraps Firebase Android SDK (and is also used as the physical dir for `jvmMain` in some modules, e.g. firebase-firestore) |
| 21 | + appleMain/ ← shared Apple targets (iOS/tvOS/macOS) wrapping Firebase iOS SDK via Kotlin/Native |
| 22 | + jsMain/ ← wraps Firebase JS SDK |
| 23 | + jvmMain/ ← JVM desktop/server target (may map to src/androidMain/kotlin in some modules) |
| 24 | + commonTest/ ← shared tests |
| 25 | + androidTest/ |
| 26 | + appleTest/ |
| 27 | + jsTest/ |
| 28 | + jvmTest/ |
| 29 | +``` |
| 30 | + |
| 31 | +The `commonMain` source set defines the **public API**. Platform-specific source sets contain the `actual` implementations that delegate to the respective native SDK. |
| 32 | + |
| 33 | +### Shared modules |
| 34 | + |
| 35 | +- `firebase-app` — `FirebaseApp` and `Firebase` object |
| 36 | +- `firebase-common` — shared types, serialization helpers, `FirebaseEncoder`/`FirebaseDecoder` |
| 37 | +- `firebase-common-internal` — internal utilities not part of the public API |
| 38 | + |
| 39 | +--- |
| 40 | + |
| 41 | +## Kotlin-First Design Principles |
| 42 | + |
| 43 | +These principles **must** be followed in all new and modified code. |
| 44 | + |
| 45 | +### 1. Suspend functions instead of callbacks or Tasks |
| 46 | + |
| 47 | +Async operations that return a single value use `suspend fun`. Never use callbacks, `Task`, `Promise`, or listener patterns in `commonMain`. |
| 48 | + |
| 49 | +```kotlin |
| 50 | +// ✅ Correct |
| 51 | +suspend fun signInWithEmailAndPassword(email: String, password: String): AuthResult |
| 52 | + |
| 53 | +// ❌ Wrong |
| 54 | +fun signInWithEmailAndPassword(email: String, password: String, callback: (AuthResult) -> Unit) |
| 55 | +``` |
| 56 | + |
| 57 | +### 2. `Flow` instead of listeners |
| 58 | + |
| 59 | +Streams of values use `kotlinx.coroutines.flow.Flow`. The flow should be cold — a new listener is registered on collection and removed on cancellation/completion. |
| 60 | + |
| 61 | +```kotlin |
| 62 | +// ✅ Correct |
| 63 | +val snapshots: Flow<DocumentSnapshot> |
| 64 | + |
| 65 | +// ❌ Wrong |
| 66 | +fun addSnapshotListener(listener: (DocumentSnapshot) -> Unit): ListenerRegistration |
| 67 | +``` |
| 68 | + |
| 69 | +### 3. Default arguments instead of the Builder pattern |
| 70 | + |
| 71 | +Prefer Kotlin default arguments over builder classes. When the upstream Android SDK uses a Builder, provide a Kotlin-idiomatic overload with default arguments **in addition to** accepting the built object (for API compatibility). |
| 72 | + |
| 73 | +```kotlin |
| 74 | +// ✅ Correct |
| 75 | +suspend fun updateProfile(displayName: String? = null, photoURL: String? = null) |
| 76 | + |
| 77 | +// ❌ Avoid as the sole API |
| 78 | +suspend fun updateProfile(request: UserProfileChangeRequest) |
| 79 | +``` |
| 80 | + |
| 81 | +### 4. Infix notation for query operators |
| 82 | + |
| 83 | +Firestore and Database query operators use infix functions inside a `where { }` builder lambda. |
| 84 | + |
| 85 | +```kotlin |
| 86 | +citiesRef.where { "state" equalTo "CA" } |
| 87 | +citiesRef.where { "regions" contains "west_coast" } |
| 88 | +citiesRef.where { |
| 89 | + all( |
| 90 | + "state" equalTo "CA", |
| 91 | + any("capital" equalTo true, "population" greaterThanOrEqualTo 1_000_000) |
| 92 | + ) |
| 93 | +} |
| 94 | +``` |
| 95 | + |
| 96 | +### 5. Operator overloading where natural |
| 97 | + |
| 98 | +Use operator overloading where semantics are obvious (e.g. callable HTTP Functions via `invoke`). |
| 99 | + |
| 100 | +--- |
| 101 | + |
| 102 | +## Serialization |
| 103 | + |
| 104 | +The SDK uses **`kotlinx.serialization`** throughout. Never use platform-specific serialization mechanisms in `commonMain`. |
| 105 | + |
| 106 | +- Custom classes passed to/from Firebase must be annotated with `@Serializable`. |
| 107 | +- Always accept an explicit `SerializationStrategy`/`DeserializationStrategy` parameter alongside a reified/inferred overload. |
| 108 | +- `encodeDefaults` defaults to `true`; allow it to be overridden via a `buildSettings` lambda. |
| 109 | +- Support `serializersModule` for contextual and polymorphic serialization. |
| 110 | +- Use `@FirebaseClassDiscriminator` (defined in `firebase-common`) on sealed classes to control the type discriminator field name. |
| 111 | +- Special sentinel values (`ServerValue.TIMESTAMP`, `Timestamp.ServerTimestamp`, `FieldValue.serverTimestamp`) must remain serializable. |
| 112 | +- For Firestore update operations, provide an `updateFields` builder that allows per-field serializer overrides. |
| 113 | + |
| 114 | +--- |
| 115 | + |
| 116 | +## API Compatibility Goal |
| 117 | + |
| 118 | +The target is **near binary compatibility** with the [Firebase Android SDK Kotlin API](https://firebase.google.com/docs/reference/kotlin/packages): |
| 119 | + |
| 120 | +- Match class names, function names, and parameter names from the Android SDK. |
| 121 | +- Package imports should be the **only change** needed when porting Android code: `com.google.firebase` → `dev.gitlive.firebase`. |
| 122 | +- When an Android SDK API is Java-first (uses builders, callbacks, etc.), provide **both** the Android-compatible form *and* a Kotlin-idiomatic overload. |
| 123 | +- When the Android SDK API is already Kotlin-first, simply match it. |
| 124 | + |
| 125 | +--- |
| 126 | + |
| 127 | +## Accessing the Underlying Platform SDK |
| 128 | + |
| 129 | +Each wrapper class exposes the underlying native SDK object via extension properties: |
| 130 | + |
| 131 | +- `.android` — Firebase Android SDK object (also used for JVM via `firebase-java-sdk`) |
| 132 | +- `.ios` — Firebase iOS SDK object (Kotlin/Native) |
| 133 | +- `.js` — Firebase JS SDK object |
| 134 | + |
| 135 | +These are only accessible from the respective platform source sets. Do **not** use them in `commonMain`. |
| 136 | + |
| 137 | +--- |
| 138 | + |
| 139 | +## Platform-Specific Notes |
| 140 | + |
| 141 | +### Android |
| 142 | +- Some modules (e.g. `firebase-config`) require **Core library desugaring** for `minSdk < 26`. |
| 143 | +- Access the underlying Android object via the `.android` extension property in `androidMain`. |
| 144 | + |
| 145 | +### iOS |
| 146 | +- The Firebase iOS SDK is **not** a transitive dependency — consuming projects must link it via CocoaPods or SPM. |
| 147 | +- Tests need the relevant Firebase pods in the `cocoapods` block of `build.gradle.kts`. |
| 148 | + |
| 149 | +### JVM / Desktop |
| 150 | +- Uses [`firebase-java-sdk`](https://github.com/GitLiveApp/firebase-java-sdk), which mirrors the Android SDK API. |
| 151 | +- Requires additional initialization compared to mobile targets (see `firebase-java-sdk` docs). |
| 152 | +- Accessed via the `.android` extension property (same as Android). |
| 153 | + |
| 154 | +--- |
| 155 | + |
| 156 | +## Documentation |
| 157 | + |
| 158 | +- Every **public** class, function, and property must have **KDoc**. |
| 159 | +- Use [`@param`](https://kotlinlang.org/docs/kotlin-doc.html#param-name), [`@return`](https://kotlinlang.org/docs/kotlin-doc.html#return), and [`@throws`](https://kotlinlang.org/docs/kotlin-doc.html#throws-exception) tags where applicable. |
| 160 | +- Published docs live at [gitliveapp.github.io/firebase-kotlin-sdk](https://gitliveapp.github.io/firebase-kotlin-sdk/). |
| 161 | + |
| 162 | +--- |
| 163 | + |
| 164 | +## Code Style |
| 165 | + |
| 166 | +- Follow the **IntelliJ Kotlin code style**. |
| 167 | +- Run `./gradlew formatKotlin` to auto-format before committing. |
| 168 | +- Run `./gradlew lintKotlin` to validate style. |
| 169 | +- No `GlobalScope` usage (except in comments/examples showing what *not* to do). |
| 170 | + |
| 171 | +--- |
| 172 | + |
| 173 | +## Binary API Validation |
| 174 | + |
| 175 | +This library tracks its public binary API. After any public API change: |
| 176 | + |
| 177 | +```bash |
| 178 | +./gradlew apiDump |
| 179 | +``` |
| 180 | + |
| 181 | +Commit the updated `.api` files alongside your code changes. |
| 182 | + |
| 183 | +--- |
| 184 | + |
| 185 | +## Testing |
| 186 | + |
| 187 | +Tests live in `commonTest` (shared) and platform-specific test source sets. A Firebase emulator must be running for integration tests: |
| 188 | + |
| 189 | +```bash |
| 190 | +# Inside the /test directory |
| 191 | +firebase emulators:start |
| 192 | +``` |
| 193 | + |
| 194 | +Run tests per platform: |
| 195 | + |
| 196 | +```bash |
| 197 | +./gradlew connectedAndroidTest # requires running emulator |
| 198 | +./gradlew iosSimulatorArm64Test # Apple Silicon |
| 199 | +./gradlew iosX64Test # Intel Mac |
| 200 | +./gradlew jsNodeTest |
| 201 | +./gradlew jvmTest |
| 202 | +``` |
| 203 | + |
| 204 | +--- |
| 205 | + |
| 206 | +## What Copilot Should Avoid |
| 207 | + |
| 208 | +- Do **not** generate Java-style callback APIs in `commonMain`. |
| 209 | +- Do **not** use `runBlocking` in production SDK code. |
| 210 | +- Do **not** add platform-specific imports to `commonMain` source files. |
| 211 | +- Do **not** use `GlobalScope` — callers manage coroutine scope. |
| 212 | +- Do **not** skip KDoc on public API members. |
| 213 | +- Do **not** use `lateinit var` for public API properties; prefer `val` or nullable types with proper initialization. |
| 214 | +- Do **not** hardcode serializers when a reified/inferred overload is possible. |
0 commit comments