Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: refactor context provider #191

Merged
merged 8 commits into from
Jan 30, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 60 additions & 47 deletions Confidence/api/Confidence.api
Original file line number Diff line number Diff line change
@@ -1,26 +1,3 @@
public final class com/spotify/confidence/AndroidLifecycleEventProducer : android/app/Application$ActivityLifecycleCallbacks, androidx/lifecycle/DefaultLifecycleObserver, com/spotify/confidence/EventProducer {
public static final field Companion Lcom/spotify/confidence/AndroidLifecycleEventProducer$Companion;
public fun <init> (Landroid/app/Application;Z)V
public fun contextChanges ()Lkotlinx/coroutines/flow/Flow;
public fun events ()Lkotlinx/coroutines/flow/Flow;
public fun onActivityCreated (Landroid/app/Activity;Landroid/os/Bundle;)V
public fun onActivityDestroyed (Landroid/app/Activity;)V
public fun onActivityPaused (Landroid/app/Activity;)V
public fun onActivityResumed (Landroid/app/Activity;)V
public fun onActivitySaveInstanceState (Landroid/app/Activity;Landroid/os/Bundle;)V
public fun onActivityStarted (Landroid/app/Activity;)V
public fun onActivityStopped (Landroid/app/Activity;)V
public fun onCreate (Landroidx/lifecycle/LifecycleOwner;)V
public fun stop ()V
}

public final class com/spotify/confidence/AndroidLifecycleEventProducer$Companion {
}

public final class com/spotify/confidence/AndroidLifecycleEventProducerKt {
public static final fun getReferrer (Landroid/app/Activity;)Landroid/net/Uri;
}

public final class com/spotify/confidence/BuildConfig {
public static final field BUILD_TYPE Ljava/lang/String;
public static final field DEBUG Z
Expand All @@ -46,7 +23,7 @@ public final class com/spotify/confidence/Confidence : com/spotify/confidence/Co
public final fun putContext (Ljava/util/Map;Ljava/util/List;)V
public fun removeContext (Ljava/lang/String;)V
public fun stop ()V
public fun track (Lcom/spotify/confidence/EventProducer;)V
public fun track (Lcom/spotify/confidence/Producer;)V
public fun track (Ljava/lang/String;Ljava/util/Map;)V
public synthetic fun withContext (Ljava/util/Map;)Lcom/spotify/confidence/Contextual;
public fun withContext (Ljava/util/Map;)Lcom/spotify/confidence/EventSender;
Expand All @@ -56,6 +33,28 @@ public abstract interface class com/spotify/confidence/ConfidenceContextProvider
public abstract fun getContext ()Ljava/util/Map;
}

public final class com/spotify/confidence/ConfidenceDeviceInfoContextProducer : com/spotify/confidence/Producer {
public static final field APP_BUILD_CONTEXT_KEY Ljava/lang/String;
public static final field APP_NAMESPACE_CONTEXT_KEY Ljava/lang/String;
public static final field APP_VERSION_CONTEXT_KEY Ljava/lang/String;
public static final field Companion Lcom/spotify/confidence/ConfidenceDeviceInfoContextProducer$Companion;
public static final field DEVICE_BRAND_CONTEXT_KEY Ljava/lang/String;
public static final field DEVICE_MANUFACTURER_CONTEXT_KEY Ljava/lang/String;
public static final field DEVICE_MODEL_CONTEXT_KEY Ljava/lang/String;
public static final field DEVICE_TYPE_CONTEXT_KEY Ljava/lang/String;
public static final field LOCALE_CONTEXT_KEY Ljava/lang/String;
public static final field OS_NAME_CONTEXT_KEY Ljava/lang/String;
public static final field OS_VERSION_CONTEXT_KEY Ljava/lang/String;
public static final field PREFERRED_LANGUAGES_CONTEXT_KEY Ljava/lang/String;
public fun <init> (Landroid/content/Context;ZZZZ)V
public synthetic fun <init> (Landroid/content/Context;ZZZZILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun stop ()V
public fun updates ()Lkotlinx/coroutines/flow/Flow;
}

public final class com/spotify/confidence/ConfidenceDeviceInfoContextProducer$Companion {
}

public final class com/spotify/confidence/ConfidenceError {
public fun <init> ()V
}
Expand Down Expand Up @@ -286,6 +285,7 @@ public final class com/spotify/confidence/ConfidenceValue$List$Companion {
public final class com/spotify/confidence/ConfidenceValue$Null : com/spotify/confidence/ConfidenceValue {
public static final field INSTANCE Lcom/spotify/confidence/ConfidenceValue$Null;
public final fun serializer ()Lkotlinx/serialization/KSerializer;
public fun toString ()Ljava/lang/String;
}

public final class com/spotify/confidence/ConfidenceValue$String : com/spotify/confidence/ConfidenceValue {
Expand Down Expand Up @@ -406,32 +406,10 @@ public final class com/spotify/confidence/Evaluation {
public fun toString ()Ljava/lang/String;
}

public final class com/spotify/confidence/Event {
public fun <init> (Ljava/lang/String;Ljava/util/Map;Z)V
public synthetic fun <init> (Ljava/lang/String;Ljava/util/Map;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Ljava/lang/String;
public final fun component2 ()Ljava/util/Map;
public final fun component3 ()Z
public final fun copy (Ljava/lang/String;Ljava/util/Map;Z)Lcom/spotify/confidence/Event;
public static synthetic fun copy$default (Lcom/spotify/confidence/Event;Ljava/lang/String;Ljava/util/Map;ZILjava/lang/Object;)Lcom/spotify/confidence/Event;
public fun equals (Ljava/lang/Object;)Z
public final fun getData ()Ljava/util/Map;
public final fun getName ()Ljava/lang/String;
public final fun getShouldFlush ()Z
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}

public abstract interface class com/spotify/confidence/EventProducer {
public abstract fun contextChanges ()Lkotlinx/coroutines/flow/Flow;
public abstract fun events ()Lkotlinx/coroutines/flow/Flow;
public abstract fun stop ()V
}

public abstract interface class com/spotify/confidence/EventSender : com/spotify/confidence/Contextual {
public abstract fun flush ()V
public abstract fun stop ()V
public abstract fun track (Lcom/spotify/confidence/EventProducer;)V
public abstract fun track (Lcom/spotify/confidence/Producer;)V
public abstract fun track (Ljava/lang/String;Ljava/util/Map;)V
public abstract fun withContext (Ljava/util/Map;)Lcom/spotify/confidence/EventSender;
}
Expand Down Expand Up @@ -490,6 +468,11 @@ public final class com/spotify/confidence/LoggingLevel : java/lang/Enum {
public static fun values ()[Lcom/spotify/confidence/LoggingLevel;
}

public abstract interface class com/spotify/confidence/Producer {
public abstract fun stop ()V
public abstract fun updates ()Lkotlinx/coroutines/flow/Flow;
}

public abstract interface class com/spotify/confidence/ProviderCache {
public abstract fun get ()Lcom/spotify/confidence/FlagResolution;
public abstract fun refresh (Lcom/spotify/confidence/FlagResolution;)V
Expand Down Expand Up @@ -536,6 +519,36 @@ public final class com/spotify/confidence/Result$Success : com/spotify/confidenc
public fun toString ()Ljava/lang/String;
}

public abstract interface class com/spotify/confidence/Update {
}

public final class com/spotify/confidence/Update$ContextUpdate : com/spotify/confidence/Update {
public fun <init> (Ljava/util/Map;)V
public final fun component1 ()Ljava/util/Map;
public final fun copy (Ljava/util/Map;)Lcom/spotify/confidence/Update$ContextUpdate;
public static synthetic fun copy$default (Lcom/spotify/confidence/Update$ContextUpdate;Ljava/util/Map;ILjava/lang/Object;)Lcom/spotify/confidence/Update$ContextUpdate;
public fun equals (Ljava/lang/Object;)Z
public final fun getContext ()Ljava/util/Map;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}

public final class com/spotify/confidence/Update$Event : com/spotify/confidence/Update {
public fun <init> (Ljava/lang/String;Ljava/util/Map;Z)V
public synthetic fun <init> (Ljava/lang/String;Ljava/util/Map;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Ljava/lang/String;
public final fun component2 ()Ljava/util/Map;
public final fun component3 ()Z
public final fun copy (Ljava/lang/String;Ljava/util/Map;Z)Lcom/spotify/confidence/Update$Event;
public static synthetic fun copy$default (Lcom/spotify/confidence/Update$Event;Ljava/lang/String;Ljava/util/Map;ZILjava/lang/Object;)Lcom/spotify/confidence/Update$Event;
public fun equals (Ljava/lang/Object;)Z
public final fun getData ()Ljava/util/Map;
public final fun getName ()Ljava/lang/String;
public final fun getShouldFlush ()Z
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}

public final class com/spotify/confidence/apply/ApplyInstance {
public static final field Companion Lcom/spotify/confidence/apply/ApplyInstance$Companion;
public synthetic fun <init> (ILjava/util/Date;Lcom/spotify/confidence/apply/EventStatus;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V
Expand Down
1 change: 0 additions & 1 deletion Confidence/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ dependencies {
implementation(
"org.jetbrains.kotlinx:kotlinx-serialization-json:${Versions.kotlinxSerialization}"
)
implementation("androidx.lifecycle:lifecycle-process:2.6.1")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:${Versions.coroutines}")
testImplementation("junit:junit:${Versions.junit}")
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:${Versions.coroutines}")
Expand Down
36 changes: 17 additions & 19 deletions Confidence/src/main/java/com/spotify/confidence/Confidence.kt
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class Confidence internal constructor(
private var currentFetchJob: Job? = null

private val coroutineScope = CoroutineScope(SupervisorJob() + dispatcher)
private val eventProducers: MutableList<EventProducer> = mutableListOf()
private val producers: MutableList<Producer> = mutableListOf()

private val flagApplier = FlagApplierWithRetries(
client = flagApplierClient,
Expand Down Expand Up @@ -276,31 +276,29 @@ class Confidence internal constructor(
activate()
}

override fun track(eventProducer: EventProducer) {
override fun track(producer: Producer) {
coroutineScope.launch {
eventProducer
.events()
.collect { event ->
eventSenderEngine.emit(
event.name,
event.data,
getContext()
)
if (event.shouldFlush) {
eventSenderEngine.flush()
producer.updates().collect { update ->
when (update) {
is Update.Event -> {
eventSenderEngine.emit(
update.name,
update.data,
getContext()
)
if (update.shouldFlush) {
eventSenderEngine.flush()
}
}
is Update.ContextUpdate -> putContext(update.context)
}
}
producers.add(producer)
}

coroutineScope.launch {
eventProducer.contextChanges()
.collect(this@Confidence::putContext)
}
eventProducers.add(eventProducer)
}

override fun stop() {
for (producer in eventProducers) {
for (producer in producers) {
producer.stop()
}
if (parent == null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
package com.spotify.confidence

import android.content.Context
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.os.Build
import android.util.Log
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
import java.util.Locale

/**
* Helper class to produce device information context for the Confidence context.
*
* @param applicationContext the application context.
* @param withAppInfo whether to include app information in the context.
* @param withDeviceInfo whether to include device information in the context.
* @param withOsInfo whether to include OS information in the context.
* @param withLocale whether to include locale information in the context.
*
* The values appended to the Context come primarily from the Android Build class and the application context.
*
* AppInfo contains:
* - version: the version name of the app.
* - build: the version code of the app.
* - namespace: the package name of the app.
*
* DeviceInfo contains:
* - manufacturer: the manufacturer of the device.
* - brand: the brand of the device.
* - model: the model of the device.
* - type: the type of the device.
*
* OsInfo contains:
* - name: the name of the OS.
* - version: the version of the OS.
*
* Locale contains:
* - locale: the locale of the device.
* - preferred_languages: the preferred languages of the device.
*
* The context is only updated when the producer is initialized and then static.
*
*/
class ConfidenceDeviceInfoContextProducer(
applicationContext: Context,
withAppInfo: Boolean = false,
withDeviceInfo: Boolean = false,
withOsInfo: Boolean = false,
withLocale: Boolean = false
) : Producer {
private val staticContext: ConfidenceFieldsType
private val packageInfo: PackageInfo? = try {
@Suppress("DEPRECATION")
applicationContext.packageManager.getPackageInfo(applicationContext.packageName, 0)
} catch (e: PackageManager.NameNotFoundException) {
Log.w(DebugLogger.TAG, "Failed to get package info", e)
null
}

init {
val context = mutableMapOf<String, ConfidenceValue>()
if (withAppInfo) {
val currentVersion = ConfidenceValue.String(packageInfo?.versionName ?: "")
val currentBuild = ConfidenceValue.String(packageInfo?.getVersionCodeAsString() ?: "")
val bundleId = ConfidenceValue.String(applicationContext.packageName)
context["app"] = ConfidenceValue.Struct(
mapOf(
APP_VERSION_CONTEXT_KEY to currentVersion,
APP_BUILD_CONTEXT_KEY to currentBuild,
APP_NAMESPACE_CONTEXT_KEY to bundleId
)
)
}

if (withDeviceInfo) {
context["device"] = ConfidenceValue.Struct(
mapOf(
DEVICE_MANUFACTURER_CONTEXT_KEY to ConfidenceValue.String(Build.MANUFACTURER),
DEVICE_BRAND_CONTEXT_KEY to ConfidenceValue.String(Build.BRAND),
DEVICE_MODEL_CONTEXT_KEY to ConfidenceValue.String(Build.MODEL),
DEVICE_TYPE_CONTEXT_KEY to ConfidenceValue.String("android")
)
)
}

if (withOsInfo) {
context["os"] = ConfidenceValue.Struct(
mapOf(
OS_NAME_CONTEXT_KEY to ConfidenceValue.String("android"),
OS_VERSION_CONTEXT_KEY to ConfidenceValue.Double(Build.VERSION.SDK_INT.toDouble())
)
)
}

if (withLocale) {
val preferredLang = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
val locales = applicationContext.resources.configuration.locales
(0 until locales.size()).map { locales.get(it).toString() }
} else {
listOf(Locale.getDefault().toString())
}
val localeIdentifier = Locale.getDefault().toString()
val localeInfo = mapOf(
LOCALE_CONTEXT_KEY to ConfidenceValue.String(localeIdentifier),
PREFERRED_LANGUAGES_CONTEXT_KEY to ConfidenceValue.List(preferredLang.map(ConfidenceValue::String))
)
// these are on the top level
context += localeInfo
}
staticContext = context
}

override fun updates(): Flow<Update> = flowOf(Update.ContextUpdate(staticContext))

override fun stop() {}

companion object {
const val APP_VERSION_CONTEXT_KEY = "version"
const val APP_BUILD_CONTEXT_KEY = "build"
const val APP_NAMESPACE_CONTEXT_KEY = "namespace"
const val DEVICE_MANUFACTURER_CONTEXT_KEY = "manufacturer"
const val DEVICE_BRAND_CONTEXT_KEY = "brand"
const val DEVICE_MODEL_CONTEXT_KEY = "model"
const val DEVICE_TYPE_CONTEXT_KEY = "type"
const val OS_NAME_CONTEXT_KEY = "name"
const val OS_VERSION_CONTEXT_KEY = "version"
const val LOCALE_CONTEXT_KEY = "locale"
const val PREFERRED_LANGUAGES_CONTEXT_KEY = "preferred_languages"
}
}

private fun PackageInfo.getVersionCodeAsString(): String =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
this.longVersionCode.toString()
} else {
@Suppress("DEPRECATION")
this.versionCode.toString()
}
Loading
Loading