Available libraries
The following libraries are available for the various Firebase products.
Is the Firebase library or API you need missing? Create an issue to request additional API coverage or be awesome and submit a PR
The following libraries are available for the various Firebase products.
Is the Firebase library or API you need missing? Create an issue to request additional API coverage or be awesome and submit a PR
The Firebase Kotlin SDK provides a common API to access Firebase for projects targeting iOS, Android, JVM and JS meaning you can use Firebase directly in your common code. Under the hood, the SDK achieves this by binding to the respective official Firebase SDK for each supported platform.
It uses the Firebase Java SDK to support the JVM target. The library requires additional initialization compared to the official Firebase SDKs.
Firebase.initialize is the one entry point for every platform, but what it does, and whether you need to call it
at all, depends on how the underlying Firebase SDK is initialised:
| Platform | context argument |
Without options |
With options |
|---|---|---|---|
| Android | required, an android.content.Context |
reads the resources generated by the Google Services Gradle plugin from google-services.json |
uses the given options |
| iOS | ignored | reads GoogleService-Info.plist (FirebaseApp.configure()) |
uses the given options |
| JS | ignored | not supported, throws UnsupportedOperationException |
uses the given options (authDomain is JS only) |
| JVM | required, an android.app.Application from the Firebase Java SDK |
not supported | uses the given options |
On Android you usually need no call: with the Google Services Gradle plugin the official SDK initialises the default app itself
at startup (through a ContentProvider merged into your manifest) and Firebase.app is ready to use. On iOS the default app has
to be configured once at launch, either from Swift with FirebaseApp.configure() or from Kotlin as shown below, before any Firebase
API is used. On JS and the JVM the options always have to be provided in code.
The recommended pattern is a common function that takes the options and an optional platform context, called from each platform's entry point:
// commonMain
fun initializeFirebase(context: Any? = null): FirebaseApp = Firebase.apps(context).firstOrNull() ?: Firebase.initialize(
context,
FirebaseOptions(
applicationId = "1:846484016111:web:abc123",
apiKey = "AIza...",
projectId = "fir-kotlin-sdk",
databaseUrl = "https://fir-kotlin-sdk.firebaseio.com",
storageBucket = "fir-kotlin-sdk.appspot.com",
gcmSenderId = "846484016111",
authDomain = "fir-kotlin-sdk.firebaseapp.com", // JS only, ignored elsewhere
),
)
// androidMain, e.g. in Application.onCreate; only needed without the Google Services plugin
initializeFirebase(applicationContext)
// iosMain, e.g. from the app delegate or the SwiftUI App init; or pass no options to read GoogleService-Info.plist
initializeFirebase()
// jsMain, before any other Firebase call
initializeFirebase()
On the JVM the Firebase Java SDK needs its platform hooks installed
before the app is created, and the context is the SDK's Application stand-in:
// jvmMain
FirebasePlatform.initializeFirebasePlatform(object : FirebasePlatform() {
val storage = mutableMapOf<String, String>()
override fun store(key: String, value: String) = storage.set(key, value)
override fun retrieve(key: String) = storage[key]
override fun clear(key: String) { storage.remove(key) }
override fun log(msg: String) = println(msg)
})
initializeFirebase(Application())
Firebase.initialize(context, options, name) initialises an additional named app and Firebase.app(name) returns it;
Firebase.apps(context) lists the initialised apps and FirebaseApp.delete() removes one. The default app is named
FirebaseApp.DEFAULT_APP_NAME on every platform, even though the underlying SDKs use different names for it.
Code written against the Android SDK can keep using com.google.firebase.Firebase.initialize(context, options): the same
three overloads exist there with the context typed as Any?, following the rules above per platform.
Alongside the Kotlin-first dev.gitlive.firebase API, the SDK ships the Firebase Android SDK API itself under its original
com.google.firebase packages, as expect declarations implemented on every platform. Code written against the Android SDK
(including com.google.android.gms.tasks.Task and its listeners) therefore compiles unchanged, without even changing imports,
in common code targeting Android, iOS, JVM and JS:
import com.google.firebase.installations.FirebaseInstallations
import kotlinx.coroutines.tasks.await
FirebaseInstallations.getInstance().getId()
.addOnSuccessListener { id -> println("Installation id: $id") }
.addOnFailureListener { e -> println("Failed: ${e.message}") }
val token = FirebaseInstallations.getInstance().getToken(false).await().token
So far this layer covers firebase-app (FirebaseApp, FirebaseOptions, Timestamp, exceptions, Task, Task.await()) and
firebase-installations; the other modules are migrated one by one. For a step-by-step walkthrough of moving Android SDK code
into a shared module, including the compiler-guided replacement of the Android-only members, see
Migrating from the Firebase Android SDK.
In some cases you might want to access the underlying official Firebase SDK in platform specific code, for example when the common API is missing the functionality you need. For this purpose each class in the SDK has android, ios and js extension properties that hold the equivalent object of the underlying official Firebase SDK. For JVM, as the firebase-java-sdk is a direct port of the Firebase Android SDK, is it also accessed via the android property.
These properties are only accessible from the equivalent target's source set. For example to disable persistence in Cloud Firestore on Android you can write the following in your Android specific code (e.g. androidMain or androidTest):
Firebase.firestore.android.firestoreSettings = FirebaseFirestoreSettings.Builder(Firebase.firestore.android.firestoreSettings)
.setPersistenceEnabled(false)
.build()
On android, some modules (config) require you to enable Core library desugaring if you have a minSDK lower than API 26.
On iOS the official Firebase iOS SDK is not linked as a transitive dependency. Therefore, any project using this SDK needs to link the actual Firestore SDK as well. This can be done through your preferred installation method (Cocoapods/SPM).
Similarly, tests require linking as well. Make sure to add the required frameworks to the search path of your test targets. This can be done by specifying a cocoapods block in your build.gradle:
cocoapods {
pod("FirebaseCore") // Repeat for Firebase pods required by your project, e.g FirebaseFirestore for the `firebase-firestore` module.
}
Unlike the Kotlin Extensions for the Firebase Android SDK this project does not extend a Java based SDK so we get the full power of Kotlin including coroutines and serialization!
Asynchronous operations that return a single or no value are represented by suspending functions in the SDK instead of callbacks, listeners or OS specific types such as Task, for example:
suspend fun signInWithCustomToken(token: String): AuthResult
It is important to remember that unlike a callback based API, waiting for suspending functions to complete is implicit and so if you don't want to wait for the result you can launch a new coroutine:
//TODO don't use GlobalScope
GlobalScope.launch {
Firebase.auth.signOut()
}
Asynchronous streams of values are represented by Flows in the SDK instead of repeatedly invoked callbacks or listeners, for example:
val snapshots: Flow<DocumentSnapshot>
The flows are cold, which means a new listener is added every time a terminal operator is applied to the resulting flow. A buffer with the default size is used to buffer values received from the listener, use the buffer operator on the flow to specify a user-defined value and to control what happens when data is produced faster than consumed, i.e. to control the back-pressure behavior. Often you are only interested in the latest value received, in this case you can use the conflate operator to disable buffering.
The listener is removed once the flow completes or is cancelled.
The official Firebase SDKs use different platform-specific ways to support writing data with and without custom classes in Cloud Firestore, Realtime Database and Functions.
The Firebase Kotlin SDK uses Kotlin serialization to read and write custom classes to Firebase. To use Kotlin serialization in your project add the plugin to your gradle file:
plugins {
kotlin("multiplatform") version "1.9.20" // or kotlin("jvm") or any other kotlin plugin
kotlin("plugin.serialization") version "1.9.20"
}
Then mark your custom classes @Serializable:
@Serializable
data class City(val name: String)
Instances of these classes can now be passed along with their serializer to the SDK:
db.collection("cities").document("LA").set(City.serializer(), city) { encodeDefaults = true }
The buildSettings closure is optional and allows for configuring serialization behaviour.
Setting the encodeDefaults parameter is optional and defaults to true, set this to false to omit writing optional properties if they are equal to theirs default values.
Using @EncodeDefault on properties is a recommended way to locally override the behavior set with encodeDefaults.
You can also omit the serializer if it can be inferred using serializer<KType>().
To support contextual serialization or open polymorphism the serializersModule can be overridden in the buildSettings closure:
@Serializable
abstract class AbstractCity {
abstract val name: String
}
@Serializable
@SerialName("capital")
data class Capital(override val name: String, val isSeatOfGovernment: Boolean) : AbstractCity()
val module = SerializersModule {
polymorphic(AbstractCity::class, AbstractCity.serializer()) {
subclass(Capital::class, Capital.serializer())
}
}
val city = Capital("London", true)
db.collection("cities").document("UK").set(AbstractCity.serializer(), city) {
encodeDefaults = true
serializersModule = module
}
val storedCity = db.collection("cities").document("UK").get().data(AbstractCity.serializer()) {
serializersModule = module
}
kotlin
@Serializable
data class Post(
val timestamp: ServerValue = ServerValue.TIMESTAMP,
)kotlin
@Serializable
data class Post(
// `Timestamp.ServerTimestamp` is a `BaseTimestamp`, not a `Timestamp`, so declare the field as
// `BaseTimestamp` for it to hold either the sentinel or a concrete `Timestamp` read back from
// the server.
val timestamp: BaseTimestamp = Timestamp.ServerTimestamp,
// or
val alternativeTimestamp: FieldValue = FieldValue.serverTimestamp,
// or
@Serializable(with = DoubleAsTimestampSerializer::class)
val doubleTimestamp: Double = DoubleAsTimestampSerializer.SERVER_TIMESTAMP,
)firebase-firestorekotlin
@Serializable
data class PointOfInterest(
val reference: DocumentReference,
val location: GeoPoint
)
val document = PointOfInterest(
reference = Firebase.firestore.collection("foo").document("bar"),
location = GeoPoint(51.939, 4.506)
)This sdk will handle polymorphic serialization automatically if you have a sealed class and its children marked as Serializable. It will include a type property that will be used to discriminate which child class is the serialized.
You can change this type property by using the @FirebaseClassDiscrminator annotation in the parent sealed class:
@Serializable
@FirebaseClassDiscriminator("class")
sealed class Parent {
@Serializable
@SerialName("child")
data class Child(
val property: Boolean
) : Parent
}
In combination with a SerialName specified for the child class, you have full control over the serialized data. In this case it will be:
{
"class": "child",
"property": true
}
updateupdateFieldsdocumentRef.updateFields {
// Root level encode settings
encodeDefaults = false
serializersModule = module
"field" to "value"
// Set the value of otherField to "1" using a custom Serializer
"otherField".to(IntAsStringSerializer(), 1)
// Overwrite build settings. All fields added within this block will have these build settings applied
withEncodeSettings {
encodeDefaults = true
serializersModule = otherModule
"city" to abstractCity
}
}
Similarly, the Query methods startAt/startAfter/endAt/endBefore have an alternative method in startAtFieldValues/startAfterFieldValues/endAtFieldValues/endBeforeFieldValues
query.orderBy("field", "otherField", "city").startAtFieldValues { // similar syntax for startAfter/endAt/endBefore
// Root level encode settings
encodeDefaults = false
serializersModule = module
add("Value")
// Starts at "1" for the otherField value
add(1, IntAsStringSerializer())
// Overwrite build settings. All field values added within this block will have these build settings applied
withEncodeSettings {
encodeDefaults = true
serializersModule = otherModule
add(abstractCity)
}
}
To reduce boilerplate, default arguments are used in the places where the Firebase Android SDK employs the builder pattern:
UserProfileChangeRequest profileUpdates = new UserProfileChangeRequest.Builder()
.setDisplayName("Jane Q. User")
.setPhotoUri(Uri.parse("https://example.com/jane-q-user/profile.jpg"))
.build()
user.updateProfile(profileUpdates)
.addOnCompleteListener(new OnCompleteListener<Void>() {
@Override
public void onComplete(@NonNull Task<Void> task) {
if (task.isSuccessful()) {
Log.d(TAG, "User profile updated.")
}
}
})
//...becomes...
user.updateProfile(displayName = "Jane Q. User", photoURL = "https://example.com/jane-q-user/profile.jpg")
To improve readability and reduce boilerplate for functions such as the Cloud Firestore query operators are built with infix notation:
citiesRef.whereEqualTo("state", "CA")
citiesRef.whereArrayContains("regions", "west_coast")
citiesRef.where(Filter.and(
Filter.equalTo("state", "CA"),
Filter.or(
Filter.equalTo("capital", true),
Filter.greaterThanOrEqualTo("population", 1000000)
)
))
//...becomes...
citiesRef.where { "state" equalTo "CA" }
citiesRef.where { "regions" contains "west_coast" }
citiesRef.where {
all(
"state" equalTo "CA",
any(
"capital" equalTo true,
"population" greaterThanOrEqualTo 1000000
)
)
}
In cases where it makes sense, such as Firebase Functions HTTPS Callable, operator overloading is used:
val addMessage = functions.getHttpsCallable("addMessage")
//In the official android Firebase SDK this would be addMessage.call(...)
addMessage(mapOf("text" to text, "push" to true))