Skip to content

Commit 0e87958

Browse files
authored
Merge pull request #192 from shepeliev/remote_config
Implement Firebase remote config API
2 parents 83fe1f0 + e64b879 commit 0e87958

File tree

27 files changed

+983
-3
lines changed

27 files changed

+983
-3
lines changed

build.gradle.kts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ tasks {
4444
"firebase-common:updateVersion", "firebase-common:updateDependencyVersion",
4545
"firebase-database:updateVersion", "firebase-database:updateDependencyVersion",
4646
"firebase-firestore:updateVersion", "firebase-firestore:updateDependencyVersion",
47-
"firebase-functions:updateVersion", "firebase-functions:updateDependencyVersion"
47+
"firebase-functions:updateVersion", "firebase-functions:updateDependencyVersion",
48+
"firebase-config:updateVersion", "firebase-config:updateDependencyVersion"
4849
)
4950
}
5051
}

firebase-app/src/jsMain/kotlin/dev/gitlive/firebase/firebase.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ actual fun Firebase.apps(context: Any?) = firebase.apps.map { FirebaseApp(it) }
3434

3535
private fun FirebaseOptions.toJson() = json(
3636
"apiKey" to apiKey,
37-
"applicationId" to applicationId,
37+
"appId" to applicationId,
3838
"databaseURL" to (databaseUrl ?: undefined),
3939
"storageBucket" to (storageBucket ?: undefined),
4040
"projectId" to (projectId ?: undefined),

firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/externals.kt

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,4 +461,36 @@ external object firebase {
461461
}
462462
}
463463
}
464+
465+
fun remoteConfig(app: App? = definedExternally): remoteConfig.RemoteConfig
466+
467+
object remoteConfig {
468+
interface RemoteConfig {
469+
var defaultConfig: Any
470+
var fetchTimeMillis: Long
471+
var lastFetchStatus: String
472+
val settings: Settings
473+
fun activate(): Promise<Boolean>
474+
fun ensureInitialized(): Promise<Unit>
475+
fun fetch(): Promise<Unit>
476+
fun fetchAndActivate(): Promise<Boolean>
477+
fun getAll(): Json
478+
fun getBoolean(key: String): Boolean
479+
fun getNumber(key: String): Number
480+
fun getString(key: String): String?
481+
fun getValue(key: String): Value
482+
}
483+
484+
interface Settings {
485+
var fetchTimeoutMillis: Number
486+
var minimumFetchIntervalMillis: Number
487+
}
488+
489+
interface Value {
490+
fun asBoolean(): Boolean
491+
fun asNumber(): Number
492+
fun asString(): String?
493+
fun getSource(): String
494+
}
495+
}
464496
}

firebase-common/src/jsMain/kotlin/dev/gitlive/firebase/externals2.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ external object database
1818
@JsModule("firebase/firestore")
1919
external object firestore
2020

21+
@JsModule("firebase/remote-config")
22+
external object remoteConfig
23+
2124
typealias SnapshotCallback = (data: firebase.database.DataSnapshot, b: String?) -> Unit
2225

2326
operator fun firebase.functions.HttpsCallable.invoke() = asDynamic()() as Promise<firebase.functions.HttpsCallableResult>

firebase-config/build.gradle.kts

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
/*
2+
* Copyright (c) 2020 GitLive Ltd. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
6+
7+
version = project.property("firebase-config.version") as String
8+
9+
plugins {
10+
id("com.android.library")
11+
kotlin("multiplatform")
12+
//id("com.quittle.android-emulator") version "0.2.0"
13+
}
14+
15+
android {
16+
compileSdkVersion(property("targetSdkVersion") as Int)
17+
defaultConfig {
18+
minSdkVersion(property("minSdkVersion") as Int)
19+
targetSdkVersion(property("targetSdkVersion") as Int)
20+
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
21+
}
22+
sourceSets {
23+
getByName("main") {
24+
manifest.srcFile("src/androidMain/AndroidManifest.xml")
25+
}
26+
getByName("androidTest"){
27+
java.srcDir(file("src/androidAndroidTest/kotlin"))
28+
}
29+
}
30+
testOptions {
31+
unitTests.apply {
32+
isIncludeAndroidResources = true
33+
}
34+
}
35+
packagingOptions {
36+
pickFirst("META-INF/kotlinx-serialization-core.kotlin_module")
37+
pickFirst("META-INF/AL2.0")
38+
pickFirst("META-INF/LGPL2.1")
39+
}
40+
lintOptions {
41+
isAbortOnError = false
42+
}
43+
}
44+
45+
// Optional configuration
46+
//androidEmulator {
47+
// emulator {
48+
// name("givlive_emulator")
49+
// sdkVersion(28)
50+
// abi("x86_64")
51+
// includeGoogleApis(true) // Defaults to false
52+
//
53+
// }
54+
// headless(false)
55+
// logEmulatorOutput(false)
56+
//}
57+
58+
kotlin {
59+
60+
android {
61+
publishAllLibraryVariants()
62+
}
63+
64+
fun nativeTargetConfig(): KotlinNativeTarget.() -> Unit = {
65+
val nativeFrameworkPaths = listOf(
66+
"FirebaseCore",
67+
"FirebaseCoreDiagnostics",
68+
"FirebaseAnalytics",
69+
"GoogleAppMeasurement",
70+
"FirebaseInstallations",
71+
"GoogleDataTransport",
72+
"GoogleUtilities",
73+
"PromisesObjC",
74+
"nanopb"
75+
).map {
76+
val archVariant =
77+
if (konanTarget is org.jetbrains.kotlin.konan.target.KonanTarget.IOS_X64) "ios-arm64_i386_x86_64-simulator" else "ios-arm64_armv7"
78+
rootProject.project("firebase-app").projectDir.resolve("src/nativeInterop/cinterop/Carthage/Build/$it.xcframework/$archVariant")
79+
}.plus(
80+
listOf(
81+
"FirebaseABTesting",
82+
"FirebaseRemoteConfig"
83+
).map {
84+
val archVariant =
85+
if (konanTarget is org.jetbrains.kotlin.konan.target.KonanTarget.IOS_X64) "ios-arm64_i386_x86_64-simulator" else "ios-arm64_armv7"
86+
projectDir.resolve("src/nativeInterop/cinterop/Carthage/Build/$it.xcframework/$archVariant")
87+
}
88+
)
89+
90+
binaries {
91+
getTest("DEBUG").apply {
92+
linkerOpts(nativeFrameworkPaths.map { "-F$it" })
93+
linkerOpts("-ObjC")
94+
}
95+
}
96+
97+
compilations.getByName("main") {
98+
cinterops.create("FirebaseRemoteConfig") {
99+
compilerOpts(nativeFrameworkPaths.map { "-F$it" })
100+
extraOpts("-verbose")
101+
}
102+
}
103+
}
104+
105+
if (project.extra["ideaActive"] as Boolean) {
106+
iosX64("ios", nativeTargetConfig())
107+
} else {
108+
ios(configure = nativeTargetConfig())
109+
}
110+
111+
js {
112+
useCommonJs()
113+
browser {
114+
testTask {
115+
useKarma {
116+
useChromeHeadless()
117+
}
118+
}
119+
}
120+
}
121+
122+
sourceSets {
123+
all {
124+
languageSettings.apply {
125+
apiVersion = "1.4"
126+
languageVersion = "1.4"
127+
progressiveMode = true
128+
useExperimentalAnnotation("kotlinx.coroutines.ExperimentalCoroutinesApi")
129+
}
130+
}
131+
132+
val commonMain by getting {
133+
dependencies {
134+
api(project(":firebase-app"))
135+
implementation(project(":firebase-common"))
136+
}
137+
}
138+
139+
val androidMain by getting {
140+
dependencies {
141+
api("com.google.firebase:firebase-config-ktx:21.0.0")
142+
}
143+
}
144+
145+
val iosMain by getting
146+
147+
val jsMain by getting
148+
}
149+
}
150+
151+
signing {
152+
val signingKey: String? by project
153+
val signingPassword: String? by project
154+
useInMemoryPgpKeys(signingKey, signingPassword)
155+
sign(publishing.publications)
156+
}

firebase-config/package.json

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{
2+
"name": "@gitlive/firebase-config",
3+
"version": "1.0.0",
4+
"description": "Wrapper around firebase for usage in Kotlin Multiplatform projects",
5+
"main": "firebase-config.js",
6+
"scripts": {
7+
"test": "echo \"Error: no test specified\" && exit 1"
8+
},
9+
"repository": {
10+
"type": "git",
11+
"url": "git+https://github.com/GitLiveApp/firebase-kotlin-sdk.git"
12+
},
13+
"keywords": [
14+
"kotlin",
15+
"multiplatform",
16+
"kotlin-js",
17+
"firebase"
18+
],
19+
"author": "dev.gitlive",
20+
"license": "Apache-2.0",
21+
"bugs": {
22+
"url": "https://github.com/GitLiveApp/firebase-kotlin-sdk/issues"
23+
},
24+
"homepage": "https://github.com/GitLiveApp/firebase-kotlin-sdk",
25+
"dependencies": {
26+
"@gitlive/firebase-app": "1.3.1",
27+
"firebase": "8.5.0",
28+
"kotlin": "1.4.31",
29+
"kotlinx-coroutines-core": "1.4.3"
30+
}
31+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/*
2+
* Copyright (c) 2020 GitLive Ltd. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
@file:JvmName("tests")
6+
package dev.gitlive.firebase.remoteconfig
7+
8+
import androidx.test.platform.app.InstrumentationRegistry
9+
import kotlinx.coroutines.runBlocking
10+
11+
actual val context: Any = InstrumentationRegistry.getInstrumentation().targetContext
12+
13+
actual fun runTest(test: suspend () -> Unit) = runBlocking { test() }
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<manifest package="dev.gitlive.firebase.remoteconfig"/>
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
@file:JvmName("android")
2+
package dev.gitlive.firebase.remoteconfig
3+
4+
import com.google.firebase.remoteconfig.FirebaseRemoteConfigClientException
5+
import com.google.firebase.remoteconfig.FirebaseRemoteConfigFetchThrottledException
6+
import com.google.firebase.remoteconfig.FirebaseRemoteConfigServerException
7+
import com.google.firebase.remoteconfig.ktx.remoteConfig
8+
import com.google.firebase.remoteconfig.ktx.remoteConfigSettings
9+
import dev.gitlive.firebase.Firebase
10+
import dev.gitlive.firebase.FirebaseApp
11+
import kotlinx.coroutines.tasks.await
12+
import com.google.firebase.ktx.Firebase as AndroidFirebase
13+
import com.google.firebase.remoteconfig.FirebaseRemoteConfig as AndroidFirebaseRemoteConfig
14+
import com.google.firebase.remoteconfig.FirebaseRemoteConfigInfo as AndroidFirebaseRemoteConfigInfo
15+
import com.google.firebase.remoteconfig.FirebaseRemoteConfigSettings as AndroidFirebaseRemoteConfigSettings
16+
17+
actual val Firebase.remoteConfig: FirebaseRemoteConfig
18+
get() = FirebaseRemoteConfig(AndroidFirebase.remoteConfig)
19+
20+
actual fun Firebase.remoteConfig(app: FirebaseApp): FirebaseRemoteConfig =
21+
FirebaseRemoteConfig(AndroidFirebase.remoteConfig)
22+
23+
actual class FirebaseRemoteConfig internal constructor(val android: AndroidFirebaseRemoteConfig) {
24+
actual val all: Map<String, FirebaseRemoteConfigValue>
25+
get() = android.all.mapValues { FirebaseRemoteConfigValue(it.value) }
26+
27+
actual val info: FirebaseRemoteConfigInfo
28+
get() = android.info.asCommon()
29+
30+
actual suspend fun settings(init: FirebaseRemoteConfigSettings.() -> Unit) {
31+
val settings = FirebaseRemoteConfigSettings().apply(init)
32+
val androidSettings = remoteConfigSettings {
33+
minimumFetchIntervalInSeconds = settings.minimumFetchIntervalInSeconds
34+
fetchTimeoutInSeconds = settings.fetchTimeoutInSeconds
35+
}
36+
android.setConfigSettingsAsync(androidSettings).await()
37+
}
38+
39+
actual suspend fun setDefaults(vararg defaults: Pair<String, Any?>) {
40+
android.setDefaultsAsync(defaults.toMap()).await()
41+
}
42+
43+
actual suspend fun fetch(minimumFetchIntervalInSeconds: Long?) {
44+
minimumFetchIntervalInSeconds
45+
?.also { android.fetch(it).await() }
46+
?: run { android.fetch().await() }
47+
}
48+
49+
actual suspend fun activate(): Boolean = android.activate().await()
50+
actual suspend fun ensureInitialized() = android.ensureInitialized().await().let { }
51+
actual suspend fun fetchAndActivate(): Boolean = android.fetchAndActivate().await()
52+
actual fun getKeysByPrefix(prefix: String): Set<String> = android.getKeysByPrefix(prefix)
53+
actual fun getValue(key: String) = FirebaseRemoteConfigValue(android.getValue(key))
54+
actual suspend fun reset() = android.reset().await().let { }
55+
56+
private fun AndroidFirebaseRemoteConfigSettings.asCommon(): FirebaseRemoteConfigSettings {
57+
return FirebaseRemoteConfigSettings(
58+
fetchTimeoutInSeconds = fetchTimeoutInSeconds,
59+
minimumFetchIntervalInSeconds = minimumFetchIntervalInSeconds,
60+
)
61+
}
62+
63+
private fun AndroidFirebaseRemoteConfigInfo.asCommon(): FirebaseRemoteConfigInfo {
64+
val lastFetchStatus = when (lastFetchStatus) {
65+
AndroidFirebaseRemoteConfig.LAST_FETCH_STATUS_SUCCESS -> FetchStatus.Success
66+
AndroidFirebaseRemoteConfig.LAST_FETCH_STATUS_NO_FETCH_YET -> FetchStatus.NoFetchYet
67+
AndroidFirebaseRemoteConfig.LAST_FETCH_STATUS_FAILURE -> FetchStatus.Failure
68+
AndroidFirebaseRemoteConfig.LAST_FETCH_STATUS_THROTTLED -> FetchStatus.Throttled
69+
else -> error("Unknown last fetch status value: $lastFetchStatus")
70+
}
71+
72+
return FirebaseRemoteConfigInfo(
73+
configSettings = configSettings.asCommon(),
74+
fetchTimeMillis = fetchTimeMillis,
75+
lastFetchStatus = lastFetchStatus
76+
)
77+
}
78+
}
79+
80+
actual typealias FirebaseRemoteConfigException = com.google.firebase.remoteconfig.FirebaseRemoteConfigException
81+
actual typealias FirebaseRemoteConfigClientException = FirebaseRemoteConfigClientException
82+
actual typealias FirebaseRemoteConfigFetchThrottledException = FirebaseRemoteConfigFetchThrottledException
83+
actual typealias FirebaseRemoteConfigServerException = FirebaseRemoteConfigServerException
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package dev.gitlive.firebase.remoteconfig
2+
3+
import com.google.firebase.remoteconfig.FirebaseRemoteConfig
4+
import com.google.firebase.remoteconfig.FirebaseRemoteConfigValue as AndroidFirebaseRemoteConfigValue
5+
6+
actual class FirebaseRemoteConfigValue internal constructor(
7+
private val android: AndroidFirebaseRemoteConfigValue
8+
) {
9+
actual fun asBoolean(): Boolean = android.asBoolean()
10+
actual fun asByteArray(): ByteArray = android.asByteArray()
11+
actual fun asDouble(): Double = android.asDouble()
12+
actual fun asLong(): Long = android.asLong()
13+
actual fun asString(): String = android.asString()
14+
actual fun getSource(): ValueSource = when (android.source) {
15+
FirebaseRemoteConfig.VALUE_SOURCE_STATIC -> ValueSource.Static
16+
FirebaseRemoteConfig.VALUE_SOURCE_DEFAULT -> ValueSource.Default
17+
FirebaseRemoteConfig.VALUE_SOURCE_REMOTE -> ValueSource.Remote
18+
else -> error("Unknown value source:${android.source}")
19+
}
20+
}

0 commit comments

Comments
 (0)