Skip to content

Commit c58d706

Browse files
committed
Merge remote-tracking branch 'origin/main' into close-await-no-shutdownhook
2 parents 3130602 + c33414f commit c58d706

File tree

22 files changed

+649
-46
lines changed

22 files changed

+649
-46
lines changed

android/build.gradle

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,4 +78,8 @@ dependencies {
7878

7979
apply from: rootProject.file('gradle/artifacts-android.gradle')
8080
apply from: rootProject.file('gradle/mvn-publish.gradle')
81-
apply from: rootProject.file('gradle/codecov.gradle')
81+
apply from: rootProject.file('gradle/codecov.gradle')
82+
83+
tasks.named("signReleasePublication") {
84+
dependsOn("bundleReleaseAar")
85+
}

android/src/main/java/com/segment/analytics/kotlin/android/AndroidAnalytics.kt

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,18 @@ package com.segment.analytics.kotlin.android
33
import android.content.Context
44
import android.content.Intent
55
import android.util.Log
6+
import androidx.lifecycle.DefaultLifecycleObserver
7+
import androidx.lifecycle.LifecycleOwner
8+
import androidx.lifecycle.ProcessLifecycleOwner
69
import com.segment.analytics.kotlin.android.plugins.AndroidContextPlugin
710
import com.segment.analytics.kotlin.android.plugins.AndroidLifecyclePlugin
811
import com.segment.analytics.kotlin.android.utilities.DeepLinkUtils
912
import com.segment.analytics.kotlin.core.Analytics
1013
import com.segment.analytics.kotlin.core.Configuration
14+
import com.segment.analytics.kotlin.core.checkSettings
1115
import com.segment.analytics.kotlin.core.platform.plugins.logger.*
16+
import kotlinx.coroutines.Dispatchers
17+
import kotlinx.coroutines.launch
1218

1319
// A set of functions tailored to the Android implementation of analytics
1420

@@ -67,6 +73,25 @@ public fun Analytics(
6773
private fun Analytics.startup() {
6874
add(AndroidContextPlugin())
6975
add(AndroidLifecyclePlugin())
76+
registerLifecycle()
77+
}
78+
79+
private fun Analytics.registerLifecycle() {
80+
analyticsScope.launch(Dispatchers.Main) {
81+
ProcessLifecycleOwner.get().lifecycle.addObserver(object : DefaultLifecycleObserver {
82+
var lastCheckSettings = java.lang.System.currentTimeMillis()
83+
val CHECK_SETTINGS_INTERVAL = 10 * 1000L
84+
85+
override fun onStart(owner: LifecycleOwner) {
86+
analyticsScope.launch(analyticsDispatcher) {
87+
if (java.lang.System.currentTimeMillis() - lastCheckSettings > CHECK_SETTINGS_INTERVAL) {
88+
checkSettings()
89+
lastCheckSettings = java.lang.System.currentTimeMillis()
90+
}
91+
}
92+
}
93+
})
94+
}
7095
}
7196

7297
/**

android/src/test/java/com/segment/analytics/kotlin/android/StorageTests.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ class StorageTests {
158158
fun `system reset action removes system`() = runTest {
159159
val action = object : Action<System> {
160160
override fun reduce(state: System): System {
161-
return System(state.configuration, null, state.running, state.initializedPlugins, state.enabled)
161+
return System(state.configuration, null, state.running, state.initializedPlugins, state.waitingPlugins, state.enabled)
162162
}
163163
}
164164
store.dispatch(action, System::class)

core/build.gradle

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,8 @@ dependencies {
3838

3939
apply from: rootProject.file('gradle/artifacts-core.gradle')
4040
apply from: rootProject.file('gradle/mvn-publish.gradle')
41-
apply from: rootProject.file('gradle/codecov.gradle')
41+
apply from: rootProject.file('gradle/codecov.gradle')
42+
43+
tasks.named("signReleasePublication") {
44+
dependsOn("jar")
45+
}

core/src/main/java/com/segment/analytics/kotlin/core/Analytics.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -511,13 +511,13 @@ open class Analytics protected constructor(
511511
fun process(event: BaseEvent, enrichment: EnrichmentClosure? = null) {
512512
if (!enabled) return
513513

514-
event.applyBaseData()
514+
event.applyBaseData(enrichment)
515515

516516
log("applying base attributes on ${Thread.currentThread().name}")
517517
analyticsScope.launch(analyticsDispatcher) {
518518
event.applyBaseEventData(store)
519519
log("processing event on ${Thread.currentThread().name}")
520-
timeline.process(event, enrichment)
520+
timeline.process(event)
521521
}
522522
}
523523

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package com.segment.analytics.kotlin.core
22

33
object Constants {
4-
const val LIBRARY_VERSION = "1.19.1"
4+
const val LIBRARY_VERSION = "1.20.0"
55
const val DEFAULT_API_HOST = "api.segment.io/v1"
66
const val DEFAULT_CDN_HOST = "cdn-settings.segment.com/v1"
77
}

core/src/main/java/com/segment/analytics/kotlin/core/Events.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ typealias AnalyticsContext = JsonObject
1212
typealias Integrations = JsonObject
1313
typealias Properties = JsonObject
1414
typealias Traits = JsonObject
15+
typealias EnrichmentClosure = (event: BaseEvent?) -> BaseEvent?
1516

1617
val emptyJsonObject = JsonObject(emptyMap())
1718
val emptyJsonArray = JsonArray(emptyList())
@@ -75,11 +76,14 @@ sealed class BaseEvent {
7576

7677
abstract var _metadata: DestinationMetadata
7778

79+
var enrichment: EnrichmentClosure? = null
80+
7881
companion object {
7982
internal const val ALL_INTEGRATIONS_KEY = "All"
8083
}
8184

82-
internal fun applyBaseData() {
85+
internal fun applyBaseData(enrichment: EnrichmentClosure?) {
86+
this.enrichment = enrichment
8387
this.timestamp = SegmentInstant.now()
8488
this.context = emptyJsonObject
8589
this.messageId = UUID.randomUUID().toString()
@@ -119,6 +123,7 @@ sealed class BaseEvent {
119123
integrations = original.integrations
120124
userId = original.userId
121125
_metadata = original._metadata
126+
enrichment = original.enrichment
122127
}
123128
@Suppress("UNCHECKED_CAST")
124129
return copy as T // This is ok because resultant type will be same as input type

core/src/main/java/com/segment/analytics/kotlin/core/Settings.kt

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -84,25 +84,27 @@ suspend fun Analytics.checkSettings() {
8484
val writeKey = configuration.writeKey
8585
val cdnHost = configuration.cdnHost
8686

87-
store.currentState(System::class) ?: return
88-
store.dispatch(System.ToggleRunningAction(running = false), System::class)
87+
pauseEventProcessing()
8988

90-
withContext(networkIODispatcher) {
89+
val settingsObj = withContext(networkIODispatcher) {
9190
log("Fetching settings on ${Thread.currentThread().name}")
92-
val settingsObj: Settings? = fetchSettings(writeKey, cdnHost)
93-
94-
withContext(analyticsDispatcher) {
91+
return@withContext fetchSettings(writeKey, cdnHost)
92+
}
9593

96-
settingsObj?.let {
97-
log("Dispatching update settings on ${Thread.currentThread().name}")
98-
store.dispatch(System.UpdateSettingsAction(settingsObj), System::class)
99-
update(settingsObj)
100-
}
94+
settingsObj?.let {
95+
log("Dispatching update settings on ${Thread.currentThread().name}")
96+
store.dispatch(System.UpdateSettingsAction(settingsObj), System::class)
97+
}
10198

102-
// we're good to go back to a running state.
103-
store.dispatch(System.ToggleRunningAction(running = true), System::class)
99+
store.currentState(System::class)?.let { system ->
100+
system.settings?.let { settings ->
101+
log("Propagating settings on ${Thread.currentThread().name}")
102+
update(settings)
104103
}
105104
}
105+
106+
// we're good to go back to a running state.
107+
resumeEventProcessing()
106108
}
107109

108110
internal fun Analytics.fetchSettings(

core/src/main/java/com/segment/analytics/kotlin/core/State.kt

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,10 @@ import java.util.*
1818
data class System(
1919
var configuration: Configuration = Configuration(""),
2020
var settings: Settings?,
21-
var running: Boolean,
22-
var initializedPlugins: Set<Int>,
23-
var enabled: Boolean
21+
var running: Boolean = false,
22+
var initializedPlugins: Set<Int> = emptySet(),
23+
var waitingPlugins: Set<Int> = emptySet(),
24+
var enabled: Boolean = true
2425
) : State {
2526

2627
companion object {
@@ -62,6 +63,7 @@ data class System(
6263
settings = settings,
6364
running = false,
6465
initializedPlugins = setOf(),
66+
waitingPlugins = setOf(),
6567
enabled = true
6668
)
6769
}
@@ -74,18 +76,37 @@ data class System(
7476
settings,
7577
state.running,
7678
state.initializedPlugins,
79+
state.waitingPlugins,
7780
state.enabled
7881
)
7982
}
8083
}
8184

8285
class ToggleRunningAction(var running: Boolean) : Action<System> {
8386
override fun reduce(state: System): System {
87+
if (running && state.waitingPlugins.isNotEmpty()) {
88+
running = false
89+
}
90+
8491
return System(
8592
state.configuration,
8693
state.settings,
8794
running,
8895
state.initializedPlugins,
96+
state.waitingPlugins,
97+
state.enabled
98+
)
99+
}
100+
}
101+
102+
class ForceRunningAction : Action<System> {
103+
override fun reduce(state: System): System {
104+
return System(
105+
state.configuration,
106+
state.settings,
107+
true,
108+
state.initializedPlugins,
109+
state.waitingPlugins,
89110
state.enabled
90111
)
91112
}
@@ -105,6 +126,7 @@ data class System(
105126
newSettings,
106127
state.running,
107128
state.initializedPlugins,
129+
state.waitingPlugins,
108130
state.enabled
109131
)
110132
}
@@ -120,6 +142,7 @@ data class System(
120142
state.settings,
121143
state.running,
122144
initializedPlugins,
145+
state.waitingPlugins,
123146
state.enabled
124147
)
125148
}
@@ -132,10 +155,39 @@ data class System(
132155
state.settings,
133156
state.running,
134157
state.initializedPlugins,
158+
state.waitingPlugins,
135159
enabled
136160
)
137161
}
138162
}
163+
164+
class AddWaitingPlugin(val plugin: Int): Action<System> {
165+
override fun reduce(state: System): System {
166+
val waitingPlugins = state.waitingPlugins + plugin
167+
return System(
168+
state.configuration,
169+
state.settings,
170+
state.running,
171+
state.initializedPlugins,
172+
waitingPlugins,
173+
state.enabled
174+
)
175+
}
176+
}
177+
178+
class RemoveWaitingPlugin(val plugin: Int): Action<System> {
179+
override fun reduce(state: System): System {
180+
val waitingPlugins = state.waitingPlugins - plugin
181+
return System(
182+
state.configuration,
183+
state.settings,
184+
state.running,
185+
state.initializedPlugins,
186+
waitingPlugins,
187+
state.enabled
188+
)
189+
}
190+
}
139191
}
140192

141193
/**
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package com.segment.analytics.kotlin.core
2+
3+
import com.segment.analytics.kotlin.core.platform.Plugin
4+
import kotlinx.coroutines.delay
5+
import kotlinx.coroutines.launch
6+
7+
/**
8+
* An interface that provides functionality of pausing and resuming event processing on Analytics.
9+
*
10+
* By default plugins that implement this interface pauses processing when it is added to
11+
* analytics (via `setup()`) and resumes after 30s.
12+
*
13+
* To customize pausing and resuming, override `setup()` and call `pause()/resumes()` as needed
14+
*/
15+
interface WaitingPlugin: Plugin {
16+
override fun setup(analytics: Analytics) {
17+
super.setup(analytics)
18+
pause()
19+
}
20+
21+
fun pause() {
22+
analytics.pauseEventProcessing(this)
23+
}
24+
25+
fun resume() {
26+
analytics.resumeEventProcessing(this)
27+
}
28+
}
29+
30+
fun Analytics.pauseEventProcessing(plugin: WaitingPlugin) = analyticsScope.launch(analyticsDispatcher) {
31+
store.dispatch(System.AddWaitingPlugin(plugin.hashCode()), System::class)
32+
pauseEventProcessing()
33+
}
34+
35+
36+
fun Analytics.resumeEventProcessing(plugin: WaitingPlugin) = analyticsScope.launch(analyticsDispatcher) {
37+
store.dispatch(System.RemoveWaitingPlugin(plugin.hashCode()), System::class)
38+
resumeEventProcessing()
39+
}
40+
41+
internal suspend fun Analytics.running(): Boolean {
42+
val system = store.currentState(System::class)
43+
return system?.running ?: false
44+
}
45+
46+
internal suspend fun Analytics.pauseEventProcessing(timeout: Long = 30_000) {
47+
if (!running()) return
48+
49+
store.dispatch(System.ToggleRunningAction(false), System::class)
50+
startProcessingAfterTimeout(timeout)
51+
}
52+
53+
internal suspend fun Analytics.resumeEventProcessing() {
54+
if (running()) return
55+
store.dispatch(System.ToggleRunningAction(true), System::class)
56+
}
57+
58+
internal fun Analytics.startProcessingAfterTimeout(timeout: Long) = analyticsScope.launch(analyticsDispatcher) {
59+
delay(timeout)
60+
store.dispatch(System.ForceRunningAction(), System::class)
61+
}
62+

0 commit comments

Comments
 (0)