Skip to content

Commit 0f1f984

Browse files
Merge branch 'main' into renovate/jacoco-0.x
2 parents d2ed958 + 8be3ca2 commit 0f1f984

File tree

14 files changed

+190
-71
lines changed

14 files changed

+190
-71
lines changed

unleashandroidsdk/src/main/java/io/getunleash/android/DefaultUnleash.kt

Lines changed: 48 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package io.getunleash.android
22

33
import android.content.Context
4-
import android.util.Log
54
import androidx.lifecycle.Lifecycle
65
import androidx.lifecycle.LifecycleOwner
76
import androidx.lifecycle.ProcessLifecycleOwner
@@ -31,6 +30,7 @@ import io.getunleash.android.metrics.NoOpMetrics
3130
import io.getunleash.android.polling.UnleashFetcher
3231
import io.getunleash.android.tasks.DataJob
3332
import io.getunleash.android.tasks.LifecycleAwareTaskManager
33+
import io.getunleash.android.util.UnleashLogger
3434
import kotlinx.collections.immutable.toImmutableList
3535
import kotlinx.coroutines.CoroutineExceptionHandler
3636
import kotlinx.coroutines.CoroutineScope
@@ -56,7 +56,7 @@ import java.util.concurrent.TimeoutException
5656
import java.util.concurrent.atomic.AtomicBoolean
5757

5858
val unleashExceptionHandler = CoroutineExceptionHandler { _, exception ->
59-
Log.e("UnleashHandler", "Caught unhandled exception: ${exception.message}", exception)
59+
UnleashLogger.e("UnleashHandler", "Caught unhandled exception: ${exception.message}", exception)
6060
}
6161

6262
private val job = SupervisorJob()
@@ -145,13 +145,13 @@ class DefaultUnleash(
145145
}
146146
}
147147

148-
fun start(
149-
eventListeners: List<UnleashListener> = emptyList(),
150-
bootstrapFile: File? = null,
151-
bootstrap: List<Toggle> = emptyList()
148+
override fun start(
149+
eventListeners: List<UnleashListener>,
150+
bootstrapFile: File?,
151+
bootstrap: List<Toggle>
152152
) {
153153
if (!started.compareAndSet(false, true)) {
154-
Log.w(TAG, "Unleash already started, ignoring start call")
154+
UnleashLogger.w(TAG, "Unleash already started, ignoring start call")
155155
return
156156
}
157157
initialListeners.forEach { addUnleashEventListener(it) }
@@ -170,14 +170,14 @@ class DefaultUnleash(
170170
cache.subscribeTo(fetcher.getFeaturesReceivedFlow())
171171
lifecycle.addObserver(taskManager)
172172
if (bootstrapFile != null && bootstrapFile.exists()) {
173-
Log.i(TAG, "Using provided bootstrap file")
173+
UnleashLogger.i(TAG, "Using provided bootstrap file")
174174
Parser.proxyResponseAdapter.fromJson(bootstrapFile.readText())?.let { state ->
175175
val toggles = state.toggles.groupBy { it.name }
176176
.mapValues { (_, v) -> v.first() }
177177
cache.write(UnleashState(unleashContextState.value, toggles))
178178
}
179179
} else if (bootstrap.isNotEmpty()) {
180-
Log.i(TAG, "Using provided bootstrap toggles")
180+
UnleashLogger.i(TAG, "Using provided bootstrap toggles")
181181
cache.write(UnleashState(unleashContextState.value, bootstrap.associateBy { it.name }))
182182
}
183183
}
@@ -213,20 +213,35 @@ class DefaultUnleash(
213213
// subscribe to feature updates from upstream
214214
localBackup.subscribeTo(fetcher.getFeaturesReceivedFlow())
215215
unleashContextState.asStateFlow().takeWhile { !ready.get() }.collect { ctx ->
216-
Log.d(TAG, "Loading state from backup for $ctx")
216+
UnleashLogger.d(TAG, "Loading state from backup for $ctx")
217217
localBackup.loadFromDisc(unleashContextState.value)?.let { state ->
218218
if (!ready.get()) {
219-
Log.i(TAG, "Loaded state from backup for $ctx")
219+
UnleashLogger.i(TAG, "Loaded state from backup for $ctx")
220220
cache.write(state)
221221
} else {
222-
Log.d(TAG, "Ignoring backup, Unleash is already ready")
222+
UnleashLogger.d(TAG, "Ignoring backup, Unleash is already ready")
223223
}
224224
}
225225
}
226226
}
227227
}
228228
}
229229

230+
override fun isEnabled(toggleName: String): Boolean {
231+
val toggle = cache.get(toggleName)
232+
val enabled = toggle?.enabled ?: false
233+
val impressionData = unleashConfig.forceImpressionData || toggle?.impressionData ?: false
234+
if (impressionData) {
235+
emit(ImpressionEvent(toggleName, enabled, unleashContextState.value))
236+
}
237+
metrics.count(toggleName, enabled)
238+
return enabled
239+
}
240+
241+
@Deprecated(
242+
"Use isEnabled(toggleName: String) instead. See https://github.com/Unleash/unleash-android-sdk/issues/141",
243+
replaceWith = ReplaceWith("isEnabled(toggleName)")
244+
)
230245
override fun isEnabled(toggleName: String, defaultValue: Boolean): Boolean {
231246
val toggle = cache.get(toggleName)
232247
val enabled = toggle?.enabled ?: defaultValue
@@ -238,6 +253,22 @@ class DefaultUnleash(
238253
return enabled
239254
}
240255

256+
override fun getVariant(toggleName: String): Variant {
257+
val toggle = cache.get(toggleName)
258+
val enabled = isEnabled(toggleName)
259+
val variant = if (enabled) (toggle?.variant ?: disabledVariant) else disabledVariant
260+
val impressionData = toggle?.impressionData ?: unleashConfig.forceImpressionData
261+
if (impressionData) {
262+
emit(ImpressionEvent(toggleName, enabled, unleashContextState.value, variant.name))
263+
}
264+
metrics.countVariant(toggleName, variant)
265+
return variant
266+
}
267+
268+
@Deprecated(
269+
"Use getVariant(toggleName: String) instead. See https://github.com/Unleash/unleash-android-sdk/issues/141",
270+
replaceWith = ReplaceWith("getVariant(toggleName)")
271+
)
241272
override fun getVariant(toggleName: String, defaultValue: Variant): Variant {
242273
val toggle = cache.get(toggleName)
243274
val enabled = isEnabled(toggleName)
@@ -336,7 +367,7 @@ class DefaultUnleash(
336367
if (listener is UnleashReadyListener) {
337368
val job = coroutineScope.launch {
338369
readyOnFeaturesReceived()
339-
Log.d(TAG, "Notifying UnleashReadyListener")
370+
UnleashLogger.d(TAG, "Notifying UnleashReadyListener")
340371
listener.onReady()
341372
}
342373
registerListenerJob(listener, job)
@@ -391,9 +422,9 @@ class DefaultUnleash(
391422
val first = cache.getUpdatesFlow().first { it ->
392423
it.toggles.isNotEmpty()
393424
}
394-
Log.d(TAG, "Received first cache update: $first")
425+
UnleashLogger.d(TAG, "Received first cache update: $first")
395426
if (ready.compareAndSet(false, true)) {
396-
Log.d(TAG, "Unleash state changed to ready")
427+
UnleashLogger.d(TAG, "Unleash state changed to ready")
397428
}
398429
}
399430

@@ -408,9 +439,9 @@ class DefaultUnleash(
408439

409440
private fun getLifecycle(androidContext: Context) =
410441
if (androidContext is LifecycleOwner) {
411-
Log.d("Unleash", "Using lifecycle from Android context")
442+
UnleashLogger.d("Unleash", "Using lifecycle from Android context")
412443
androidContext.lifecycle
413444
} else {
414-
Log.d("Unleash", "Using lifecycle from ProcessLifecycleOwner")
445+
UnleashLogger.d("Unleash", "Using lifecycle from ProcessLifecycleOwner")
415446
ProcessLifecycleOwner.get().lifecycle
416447
}

unleashandroidsdk/src/main/java/io/getunleash/android/Unleash.kt

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,41 @@
11
package io.getunleash.android
22

3+
import io.getunleash.android.data.Toggle
34
import io.getunleash.android.data.UnleashContext
45
import io.getunleash.android.data.Variant
56
import io.getunleash.android.events.UnleashListener
67
import java.io.Closeable
8+
import java.io.File
79

810
val disabledVariant = Variant("disabled")
911

1012
interface Unleash: Closeable {
1113
/**
1214
* Check if a toggle is enabled or disabled
15+
* @deprecated use [isEnabled(toggleName: String)] instead. Using default values can lead to unexpected behavior.
16+
* @see https://github.com/Unleash/unleash-android-sdk/issues/141
1317
*/
14-
fun isEnabled(toggleName: String, defaultValue: Boolean = false): Boolean
18+
@Deprecated("Use isEnabled(toggleName: String) instead. See https://github.com/Unleash/unleash-android-sdk/issues/141", ReplaceWith("isEnabled(toggleName)"))
19+
fun isEnabled(toggleName: String, defaultValue: Boolean): Boolean
20+
21+
/**
22+
* Check if a toggle is enabled or disabled
23+
*/
24+
fun isEnabled(toggleName: String): Boolean
1525

1626
/**
1727
* Get the variant for a toggle
28+
* @deprecated use [getVariant(toggleName: String)] instead. Using default values can lead to unexpected behavior.
29+
* @see https://github.com/Unleash/unleash-android-sdk/issues/141
1830
*/
31+
@Deprecated("Use getVariant(toggleName: String) instead. See https://github.com/Unleash/unleash-android-sdk/issues/141", ReplaceWith("getVariant(toggleName)"))
1932
fun getVariant(toggleName: String, defaultValue: Variant = disabledVariant): Variant
2033

34+
/**
35+
* Get the variant for a toggle
36+
*/
37+
fun getVariant(toggleName: String): Variant
38+
2139
/**
2240
* Set context and trigger a fetch of the latest toggles immediately and block until the fetch is complete or failed.
2341
*/
@@ -70,4 +88,13 @@ interface Unleash: Closeable {
7088
* once the initial fetch of toggles has been completed or failed.
7189
*/
7290
fun isReady(): Boolean
91+
92+
/**
93+
* Starts Unleash manually
94+
*/
95+
fun start(
96+
eventListeners: List<UnleashListener> = emptyList(),
97+
bootstrapFile: File? = null,
98+
bootstrap: List<Toggle> = emptyList()
99+
)
73100
}

unleashandroidsdk/src/main/java/io/getunleash/android/backup/LocalBackup.kt

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
package io.getunleash.android.backup
22

3-
import android.util.Log
43
import com.squareup.moshi.JsonAdapter
54
import io.getunleash.android.data.Parser.moshi
65
import io.getunleash.android.data.Toggle
76
import io.getunleash.android.data.UnleashContext
87
import io.getunleash.android.data.UnleashState
98
import io.getunleash.android.unleashScope
9+
import io.getunleash.android.util.UnleashLogger
1010
import kotlinx.coroutines.Dispatchers
1111
import kotlinx.coroutines.flow.Flow
1212
import kotlinx.coroutines.launch
@@ -39,7 +39,7 @@ open class LocalBackup(
3939
lastContext = it.context
4040
writeToDisc(it)
4141
} else {
42-
Log.d(TAG, "Context unchanged, not writing to disc")
42+
UnleashLogger.d(TAG, "Context unchanged, not writing to disc")
4343
}
4444
}
4545
}
@@ -58,9 +58,9 @@ open class LocalBackup(
5858
)
5959
).toByteArray(Charsets.UTF_8)
6060
)
61-
Log.d(TAG, "Written state to ${contextBackup.absolutePath}")
61+
UnleashLogger.d(TAG, "Written state to ${contextBackup.absolutePath}")
6262
} catch (e: Exception) {
63-
Log.i(TAG, "Error writing to disc", e)
63+
UnleashLogger.i(TAG, "Error writing to disc", e)
6464
}
6565
}
6666

@@ -71,15 +71,15 @@ open class LocalBackup(
7171
val backupState =
7272
backupAdapter.fromJson(stateBackup.readText(Charsets.UTF_8)) ?: return null
7373
if (backupState.contextId != id(context)) {
74-
Log.i(TAG, "Context id mismatch, ignoring backup for context id ${backupState.contextId}")
74+
UnleashLogger.i(TAG, "Context id mismatch, ignoring backup for context id ${backupState.contextId}")
7575
return null
7676
}
7777
return UnleashState(context, backupState.toggles)
7878
} else {
79-
Log.d(TAG, "No backup found at ${stateBackup.absolutePath}")
79+
UnleashLogger.d(TAG, "No backup found at ${stateBackup.absolutePath}")
8080
}
8181
} catch (e: Exception) {
82-
Log.w(TAG, "Error loading from disc ${stateBackup.absolutePath}", e)
82+
UnleashLogger.w(TAG, "Error loading from disc ${stateBackup.absolutePath}", e)
8383
}
8484
return null
8585
}

unleashandroidsdk/src/main/java/io/getunleash/android/cache/CacheDirectoryProvider.kt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
package io.getunleash.android.cache
22

33
import android.content.Context
4-
import android.util.Log
54
import io.getunleash.android.backup.LocalStorageConfig
5+
import io.getunleash.android.util.UnleashLogger
66
import java.io.File
77

88
class CacheDirectoryProvider(
@@ -18,7 +18,7 @@ class CacheDirectoryProvider(
1818
val tempStorageDir: File = config.dir?.let { File(it) } ?: context.cacheDir
1919
val tempDir = File(tempStorageDir, tempDirName)
2020
if (!createDirectoryIfNotExists(tempDir)) {
21-
Log.w(TAG, "Failed to create directory ${tempDir.absolutePath}")
21+
UnleashLogger.w(TAG, "Failed to create directory ${tempDir.absolutePath}")
2222
} else {
2323
if (deleteOnShutdown) addShutdownHook(tempDir)
2424
}
@@ -27,14 +27,14 @@ class CacheDirectoryProvider(
2727

2828
private fun createDirectoryIfNotExists(file: File): Boolean {
2929
if (file.exists()) {
30-
Log.d(TAG, "Directory ${file.absolutePath} already exists")
30+
UnleashLogger.d(TAG, "Directory ${file.absolutePath} already exists")
3131
return true
3232
}
3333
if (file.mkdirs()) {
34-
Log.d(TAG, "Created directory ${file.absolutePath}")
34+
UnleashLogger.d(TAG, "Created directory ${file.absolutePath}")
3535
return true
3636
}
37-
Log.w(TAG, "Failed to create directory ${file.absolutePath}")
37+
UnleashLogger.w(TAG, "Failed to create directory ${file.absolutePath}")
3838
return false
3939
}
4040

unleashandroidsdk/src/main/java/io/getunleash/android/cache/ObservableCache.kt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
package io.getunleash.android.cache
22

3-
import android.util.Log
43
import io.getunleash.android.data.Toggle
54
import io.getunleash.android.data.UnleashState
65
import io.getunleash.android.unleashScope
6+
import io.getunleash.android.util.UnleashLogger
77
import kotlinx.coroutines.CoroutineScope
88
import kotlinx.coroutines.Dispatchers
99
import kotlinx.coroutines.channels.BufferOverflow
@@ -32,19 +32,19 @@ class ObservableCache(private val cache: ToggleCache, private val coroutineScope
3232

3333
override fun write(state: UnleashState) {
3434
cache.write(state)
35-
Log.d(TAG, "Done writing cache with ${newStateEventFlow.subscriptionCount.value} subscribers")
35+
UnleashLogger.d(TAG, "Done writing cache with ${newStateEventFlow.subscriptionCount.value} subscribers")
3636
coroutineScope.launch {
37-
Log.d(TAG, "Emitting new state with ${state.toggles.size} toggles")
37+
UnleashLogger.d(TAG, "Emitting new state with ${state.toggles.size} toggles")
3838
newStateEventFlow.emit(state)
3939
}
4040
}
4141

4242
override fun subscribeTo(featuresReceived: Flow<UnleashState>) {
43-
Log.d(TAG, "Subscribing to observable cache")
43+
UnleashLogger.d(TAG, "Subscribing to observable cache")
4444
coroutineScope.launch {
4545
featuresReceived.collect { state ->
4646
withContext(Dispatchers.IO) {
47-
Log.d(TAG, "Storing new state with ${state.toggles.size} toggles for $state.context")
47+
UnleashLogger.d(TAG, "Storing new state with ${state.toggles.size} toggles for $state.context")
4848
write(state)
4949
}
5050
}

unleashandroidsdk/src/main/java/io/getunleash/android/http/NetworkStatusHelper.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import android.net.Network
66
import android.net.NetworkCapabilities
77
import android.net.NetworkRequest
88
import android.os.Build
9-
import android.util.Log
9+
import io.getunleash.android.util.UnleashLogger
1010

1111
interface NetworkListener {
1212
fun onAvailable()
@@ -77,7 +77,7 @@ class NetworkStatusHelper(private val context: Context) {
7777
private fun getConnectivityManager(): ConnectivityManager? {
7878
val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE)
7979
if (connectivityManager !is ConnectivityManager) {
80-
Log.w(TAG, "Failed to get ConnectivityManager assuming network is available")
80+
UnleashLogger.w(TAG, "Failed to get ConnectivityManager assuming network is available")
8181
return null
8282
}
8383
return connectivityManager

unleashandroidsdk/src/main/java/io/getunleash/android/http/Throttler.kt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package io.getunleash.android.http
22

3-
import android.util.Log
3+
import io.getunleash.android.util.UnleashLogger
44
import java.net.HttpURLConnection
55
import java.util.concurrent.atomic.AtomicLong
66
import kotlin.math.max
@@ -75,23 +75,23 @@ class Throttler(
7575
|| responseCode == HttpURLConnection.HTTP_FORBIDDEN
7676
) {
7777
maximizeSkips()
78-
Log.e(TAG,
78+
UnleashLogger.e(TAG,
7979
"Client was not authorized to talk to the Unleash API at $target. Backing off to $maxSkips times our poll interval (of $intervalLengthInSeconds seconds) to avoid overloading server",
8080
)
8181
}
8282
if (responseCode == HttpURLConnection.HTTP_NOT_FOUND) {
8383
maximizeSkips()
84-
Log.e(TAG,
84+
UnleashLogger.e(TAG,
8585
"Server said that the endpoint at $target does not exist. Backing off to $maxSkips times our poll interval (of $intervalLengthInSeconds seconds) to avoid overloading server",
8686
)
8787
} else if (responseCode == 429) {
8888
increaseSkipCount()
89-
Log.i(TAG,
89+
UnleashLogger.i(TAG,
9090
"RATE LIMITED for the ${failures.get()}. time. Further backing off. Current backoff at ${skips.get()} times our interval (of $intervalLengthInSeconds seconds)",
9191
)
9292
} else if (responseCode >= HttpURLConnection.HTTP_INTERNAL_ERROR) {
9393
increaseSkipCount()
94-
Log.i(TAG,
94+
UnleashLogger.i(TAG,
9595
"Server failed with a $responseCode status code. Backing off. Current backoff at ${skips.get()} times our poll interval (of $intervalLengthInSeconds seconds)",
9696
)
9797
}

0 commit comments

Comments
 (0)