Skip to content

Commit

Permalink
Merge pull request #192 from shepeliev/remote_config
Browse files Browse the repository at this point in the history
Implement Firebase remote config API
  • Loading branch information
Reedyuk authored Jul 6, 2021
2 parents 83fe1f0 + e64b879 commit 0e87958
Show file tree
Hide file tree
Showing 27 changed files with 983 additions and 3 deletions.
3 changes: 2 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ tasks {
"firebase-common:updateVersion", "firebase-common:updateDependencyVersion",
"firebase-database:updateVersion", "firebase-database:updateDependencyVersion",
"firebase-firestore:updateVersion", "firebase-firestore:updateDependencyVersion",
"firebase-functions:updateVersion", "firebase-functions:updateDependencyVersion"
"firebase-functions:updateVersion", "firebase-functions:updateDependencyVersion",
"firebase-config:updateVersion", "firebase-config:updateDependencyVersion"
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ actual fun Firebase.apps(context: Any?) = firebase.apps.map { FirebaseApp(it) }

private fun FirebaseOptions.toJson() = json(
"apiKey" to apiKey,
"applicationId" to applicationId,
"appId" to applicationId,
"databaseURL" to (databaseUrl ?: undefined),
"storageBucket" to (storageBucket ?: undefined),
"projectId" to (projectId ?: undefined),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -461,4 +461,36 @@ external object firebase {
}
}
}

fun remoteConfig(app: App? = definedExternally): remoteConfig.RemoteConfig

object remoteConfig {
interface RemoteConfig {
var defaultConfig: Any
var fetchTimeMillis: Long
var lastFetchStatus: String
val settings: Settings
fun activate(): Promise<Boolean>
fun ensureInitialized(): Promise<Unit>
fun fetch(): Promise<Unit>
fun fetchAndActivate(): Promise<Boolean>
fun getAll(): Json
fun getBoolean(key: String): Boolean
fun getNumber(key: String): Number
fun getString(key: String): String?
fun getValue(key: String): Value
}

interface Settings {
var fetchTimeoutMillis: Number
var minimumFetchIntervalMillis: Number
}

interface Value {
fun asBoolean(): Boolean
fun asNumber(): Number
fun asString(): String?
fun getSource(): String
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ external object database
@JsModule("firebase/firestore")
external object firestore

@JsModule("firebase/remote-config")
external object remoteConfig

typealias SnapshotCallback = (data: firebase.database.DataSnapshot, b: String?) -> Unit

operator fun firebase.functions.HttpsCallable.invoke() = asDynamic()() as Promise<firebase.functions.HttpsCallableResult>
Expand Down
156 changes: 156 additions & 0 deletions firebase-config/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
/*
* Copyright (c) 2020 GitLive Ltd. Use of this source code is governed by the Apache 2.0 license.
*/

import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget

version = project.property("firebase-config.version") as String

plugins {
id("com.android.library")
kotlin("multiplatform")
//id("com.quittle.android-emulator") version "0.2.0"
}

android {
compileSdkVersion(property("targetSdkVersion") as Int)
defaultConfig {
minSdkVersion(property("minSdkVersion") as Int)
targetSdkVersion(property("targetSdkVersion") as Int)
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
sourceSets {
getByName("main") {
manifest.srcFile("src/androidMain/AndroidManifest.xml")
}
getByName("androidTest"){
java.srcDir(file("src/androidAndroidTest/kotlin"))
}
}
testOptions {
unitTests.apply {
isIncludeAndroidResources = true
}
}
packagingOptions {
pickFirst("META-INF/kotlinx-serialization-core.kotlin_module")
pickFirst("META-INF/AL2.0")
pickFirst("META-INF/LGPL2.1")
}
lintOptions {
isAbortOnError = false
}
}

// Optional configuration
//androidEmulator {
// emulator {
// name("givlive_emulator")
// sdkVersion(28)
// abi("x86_64")
// includeGoogleApis(true) // Defaults to false
//
// }
// headless(false)
// logEmulatorOutput(false)
//}

kotlin {

android {
publishAllLibraryVariants()
}

fun nativeTargetConfig(): KotlinNativeTarget.() -> Unit = {
val nativeFrameworkPaths = listOf(
"FirebaseCore",
"FirebaseCoreDiagnostics",
"FirebaseAnalytics",
"GoogleAppMeasurement",
"FirebaseInstallations",
"GoogleDataTransport",
"GoogleUtilities",
"PromisesObjC",
"nanopb"
).map {
val archVariant =
if (konanTarget is org.jetbrains.kotlin.konan.target.KonanTarget.IOS_X64) "ios-arm64_i386_x86_64-simulator" else "ios-arm64_armv7"
rootProject.project("firebase-app").projectDir.resolve("src/nativeInterop/cinterop/Carthage/Build/$it.xcframework/$archVariant")
}.plus(
listOf(
"FirebaseABTesting",
"FirebaseRemoteConfig"
).map {
val archVariant =
if (konanTarget is org.jetbrains.kotlin.konan.target.KonanTarget.IOS_X64) "ios-arm64_i386_x86_64-simulator" else "ios-arm64_armv7"
projectDir.resolve("src/nativeInterop/cinterop/Carthage/Build/$it.xcframework/$archVariant")
}
)

binaries {
getTest("DEBUG").apply {
linkerOpts(nativeFrameworkPaths.map { "-F$it" })
linkerOpts("-ObjC")
}
}

compilations.getByName("main") {
cinterops.create("FirebaseRemoteConfig") {
compilerOpts(nativeFrameworkPaths.map { "-F$it" })
extraOpts("-verbose")
}
}
}

if (project.extra["ideaActive"] as Boolean) {
iosX64("ios", nativeTargetConfig())
} else {
ios(configure = nativeTargetConfig())
}

js {
useCommonJs()
browser {
testTask {
useKarma {
useChromeHeadless()
}
}
}
}

sourceSets {
all {
languageSettings.apply {
apiVersion = "1.4"
languageVersion = "1.4"
progressiveMode = true
useExperimentalAnnotation("kotlinx.coroutines.ExperimentalCoroutinesApi")
}
}

val commonMain by getting {
dependencies {
api(project(":firebase-app"))
implementation(project(":firebase-common"))
}
}

val androidMain by getting {
dependencies {
api("com.google.firebase:firebase-config-ktx:21.0.0")
}
}

val iosMain by getting

val jsMain by getting
}
}

signing {
val signingKey: String? by project
val signingPassword: String? by project
useInMemoryPgpKeys(signingKey, signingPassword)
sign(publishing.publications)
}
31 changes: 31 additions & 0 deletions firebase-config/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"name": "@gitlive/firebase-config",
"version": "1.0.0",
"description": "Wrapper around firebase for usage in Kotlin Multiplatform projects",
"main": "firebase-config.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/GitLiveApp/firebase-kotlin-sdk.git"
},
"keywords": [
"kotlin",
"multiplatform",
"kotlin-js",
"firebase"
],
"author": "dev.gitlive",
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/GitLiveApp/firebase-kotlin-sdk/issues"
},
"homepage": "https://github.com/GitLiveApp/firebase-kotlin-sdk",
"dependencies": {
"@gitlive/firebase-app": "1.3.1",
"firebase": "8.5.0",
"kotlin": "1.4.31",
"kotlinx-coroutines-core": "1.4.3"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/*
* Copyright (c) 2020 GitLive Ltd. Use of this source code is governed by the Apache 2.0 license.
*/

@file:JvmName("tests")
package dev.gitlive.firebase.remoteconfig

import androidx.test.platform.app.InstrumentationRegistry
import kotlinx.coroutines.runBlocking

actual val context: Any = InstrumentationRegistry.getInstrumentation().targetContext

actual fun runTest(test: suspend () -> Unit) = runBlocking { test() }
1 change: 1 addition & 0 deletions firebase-config/src/androidMain/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<manifest package="dev.gitlive.firebase.remoteconfig"/>
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
@file:JvmName("android")
package dev.gitlive.firebase.remoteconfig

import com.google.firebase.remoteconfig.FirebaseRemoteConfigClientException
import com.google.firebase.remoteconfig.FirebaseRemoteConfigFetchThrottledException
import com.google.firebase.remoteconfig.FirebaseRemoteConfigServerException
import com.google.firebase.remoteconfig.ktx.remoteConfig
import com.google.firebase.remoteconfig.ktx.remoteConfigSettings
import dev.gitlive.firebase.Firebase
import dev.gitlive.firebase.FirebaseApp
import kotlinx.coroutines.tasks.await
import com.google.firebase.ktx.Firebase as AndroidFirebase
import com.google.firebase.remoteconfig.FirebaseRemoteConfig as AndroidFirebaseRemoteConfig
import com.google.firebase.remoteconfig.FirebaseRemoteConfigInfo as AndroidFirebaseRemoteConfigInfo
import com.google.firebase.remoteconfig.FirebaseRemoteConfigSettings as AndroidFirebaseRemoteConfigSettings

actual val Firebase.remoteConfig: FirebaseRemoteConfig
get() = FirebaseRemoteConfig(AndroidFirebase.remoteConfig)

actual fun Firebase.remoteConfig(app: FirebaseApp): FirebaseRemoteConfig =
FirebaseRemoteConfig(AndroidFirebase.remoteConfig)

actual class FirebaseRemoteConfig internal constructor(val android: AndroidFirebaseRemoteConfig) {
actual val all: Map<String, FirebaseRemoteConfigValue>
get() = android.all.mapValues { FirebaseRemoteConfigValue(it.value) }

actual val info: FirebaseRemoteConfigInfo
get() = android.info.asCommon()

actual suspend fun settings(init: FirebaseRemoteConfigSettings.() -> Unit) {
val settings = FirebaseRemoteConfigSettings().apply(init)
val androidSettings = remoteConfigSettings {
minimumFetchIntervalInSeconds = settings.minimumFetchIntervalInSeconds
fetchTimeoutInSeconds = settings.fetchTimeoutInSeconds
}
android.setConfigSettingsAsync(androidSettings).await()
}

actual suspend fun setDefaults(vararg defaults: Pair<String, Any?>) {
android.setDefaultsAsync(defaults.toMap()).await()
}

actual suspend fun fetch(minimumFetchIntervalInSeconds: Long?) {
minimumFetchIntervalInSeconds
?.also { android.fetch(it).await() }
?: run { android.fetch().await() }
}

actual suspend fun activate(): Boolean = android.activate().await()
actual suspend fun ensureInitialized() = android.ensureInitialized().await().let { }
actual suspend fun fetchAndActivate(): Boolean = android.fetchAndActivate().await()
actual fun getKeysByPrefix(prefix: String): Set<String> = android.getKeysByPrefix(prefix)
actual fun getValue(key: String) = FirebaseRemoteConfigValue(android.getValue(key))
actual suspend fun reset() = android.reset().await().let { }

private fun AndroidFirebaseRemoteConfigSettings.asCommon(): FirebaseRemoteConfigSettings {
return FirebaseRemoteConfigSettings(
fetchTimeoutInSeconds = fetchTimeoutInSeconds,
minimumFetchIntervalInSeconds = minimumFetchIntervalInSeconds,
)
}

private fun AndroidFirebaseRemoteConfigInfo.asCommon(): FirebaseRemoteConfigInfo {
val lastFetchStatus = when (lastFetchStatus) {
AndroidFirebaseRemoteConfig.LAST_FETCH_STATUS_SUCCESS -> FetchStatus.Success
AndroidFirebaseRemoteConfig.LAST_FETCH_STATUS_NO_FETCH_YET -> FetchStatus.NoFetchYet
AndroidFirebaseRemoteConfig.LAST_FETCH_STATUS_FAILURE -> FetchStatus.Failure
AndroidFirebaseRemoteConfig.LAST_FETCH_STATUS_THROTTLED -> FetchStatus.Throttled
else -> error("Unknown last fetch status value: $lastFetchStatus")
}

return FirebaseRemoteConfigInfo(
configSettings = configSettings.asCommon(),
fetchTimeMillis = fetchTimeMillis,
lastFetchStatus = lastFetchStatus
)
}
}

actual typealias FirebaseRemoteConfigException = com.google.firebase.remoteconfig.FirebaseRemoteConfigException
actual typealias FirebaseRemoteConfigClientException = FirebaseRemoteConfigClientException
actual typealias FirebaseRemoteConfigFetchThrottledException = FirebaseRemoteConfigFetchThrottledException
actual typealias FirebaseRemoteConfigServerException = FirebaseRemoteConfigServerException
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package dev.gitlive.firebase.remoteconfig

import com.google.firebase.remoteconfig.FirebaseRemoteConfig
import com.google.firebase.remoteconfig.FirebaseRemoteConfigValue as AndroidFirebaseRemoteConfigValue

actual class FirebaseRemoteConfigValue internal constructor(
private val android: AndroidFirebaseRemoteConfigValue
) {
actual fun asBoolean(): Boolean = android.asBoolean()
actual fun asByteArray(): ByteArray = android.asByteArray()
actual fun asDouble(): Double = android.asDouble()
actual fun asLong(): Long = android.asLong()
actual fun asString(): String = android.asString()
actual fun getSource(): ValueSource = when (android.source) {
FirebaseRemoteConfig.VALUE_SOURCE_STATIC -> ValueSource.Static
FirebaseRemoteConfig.VALUE_SOURCE_DEFAULT -> ValueSource.Default
FirebaseRemoteConfig.VALUE_SOURCE_REMOTE -> ValueSource.Remote
else -> error("Unknown value source:${android.source}")
}
}
Loading

0 comments on commit 0e87958

Please sign in to comment.