Skip to content

Commit 5c2a122

Browse files
committed
cleanup
1 parent 6750d5a commit 5c2a122

File tree

9 files changed

+131
-104
lines changed

9 files changed

+131
-104
lines changed

core/src/main/kotlin/io/customer/sdk/core/pipeline/IdentifyContextProvider.kt

Lines changed: 0 additions & 20 deletions
This file was deleted.

core/src/main/kotlin/io/customer/sdk/core/pipeline/IdentifyContextRegistry.kt

Lines changed: 0 additions & 41 deletions
This file was deleted.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package io.customer.sdk.core.pipeline
2+
3+
import io.customer.base.internal.InternalCustomerIOApi
4+
5+
/**
6+
* Hook for modules that participate in the identify event lifecycle.
7+
*
8+
* [getIdentifyContext] returns context entries (String, Number, Boolean)
9+
* added to the identify event's context via `putInContext()`. Return an
10+
* empty map when there is nothing to contribute. These are context-level
11+
* enrichment data (e.g., location coordinates), NOT profile traits.
12+
*
13+
* [resetContext] is called synchronously during `analytics.reset()`
14+
* (clearIdentify flow). Implementations must clear any cached data
15+
* here to prevent stale context from enriching a subsequent identify.
16+
* Full cleanup (persistence, filters) can happen asynchronously via
17+
* EventBus ResetEvent.
18+
*
19+
* This is an internal SDK contract — not intended for use by host app developers.
20+
*/
21+
@InternalCustomerIOApi
22+
interface IdentifyHook {
23+
fun getIdentifyContext(): Map<String, Any>
24+
fun resetContext() {}
25+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package io.customer.sdk.core.pipeline
2+
3+
import io.customer.base.internal.InternalCustomerIOApi
4+
import io.customer.sdk.core.di.SDKComponent
5+
6+
/**
7+
* Thread-safe registry of [IdentifyHook] instances.
8+
*
9+
* Modules register hooks during initialization. The datapipelines module
10+
* queries all hooks when enriching identify event context and on reset.
11+
*
12+
* Cleared automatically when [SDKComponent.reset] clears singletons.
13+
*
14+
* This is an internal SDK contract — not intended for use by host app developers.
15+
*/
16+
@InternalCustomerIOApi
17+
class IdentifyHookRegistry {
18+
private val hooks = mutableListOf<IdentifyHook>()
19+
20+
@Synchronized
21+
fun register(hook: IdentifyHook) {
22+
if (hook !in hooks) {
23+
hooks.add(hook)
24+
}
25+
}
26+
27+
@Synchronized
28+
fun getAll(): List<IdentifyHook> = hooks.toList()
29+
30+
@Synchronized
31+
fun clear() {
32+
hooks.clear()
33+
}
34+
}
35+
36+
/**
37+
* Singleton accessor for [IdentifyHookRegistry] via [SDKComponent].
38+
*/
39+
@InternalCustomerIOApi
40+
val SDKComponent.identifyHookRegistry: IdentifyHookRegistry
41+
get() = singleton { IdentifyHookRegistry() }

datapipelines/src/main/kotlin/io/customer/datapipelines/plugins/IdentifyContextPlugin.kt

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,29 +6,50 @@ import com.segment.analytics.kotlin.core.IdentifyEvent
66
import com.segment.analytics.kotlin.core.platform.EventPlugin
77
import com.segment.analytics.kotlin.core.platform.Plugin
88
import com.segment.analytics.kotlin.core.utilities.putInContext
9-
import io.customer.sdk.core.pipeline.IdentifyContextRegistry
9+
import io.customer.sdk.core.pipeline.IdentifyHookRegistry
1010
import io.customer.sdk.core.util.Logger
1111
import kotlinx.serialization.json.JsonPrimitive
1212

1313
/**
14-
* Generic Segment enrichment plugin that queries all registered
15-
* [IdentifyContextProvider][io.customer.sdk.core.pipeline.IdentifyContextProvider]
16-
* instances and adds their entries to the identify event context.
14+
* Segment enrichment plugin that delegates to registered [IdentifyHook]
15+
* instances for both identify enrichment and reset lifecycle.
1716
*
18-
* This plugin has zero knowledge of specific modules — providers
17+
* On identify: queries all hooks for context entries and adds them
18+
* to the event context via `putInContext()`.
19+
*
20+
* On reset: propagates synchronously to all hooks so they clear
21+
* cached state before a subsequent identify() picks up stale values.
22+
*
23+
* This plugin has zero knowledge of specific modules — hooks
1924
* manage their own state and return primitive-valued maps.
2025
*/
2126
internal class IdentifyContextPlugin(
22-
private val registry: IdentifyContextRegistry,
27+
private val registry: IdentifyHookRegistry,
2328
private val logger: Logger
2429
) : EventPlugin {
2530
override val type: Plugin.Type = Plugin.Type.Enrichment
2631
override lateinit var analytics: Analytics
2732

33+
/**
34+
* Called synchronously by analytics.reset() during clearIdentify().
35+
* Propagates to all hooks so they clear cached data before a subsequent
36+
* identify() can pick up stale values.
37+
*/
38+
override fun reset() {
39+
super.reset()
40+
for (hook in registry.getAll()) {
41+
try {
42+
hook.resetContext()
43+
} catch (e: Exception) {
44+
logger.error("IdentifyHook reset failed: ${e.message}")
45+
}
46+
}
47+
}
48+
2849
override fun identify(payload: IdentifyEvent): BaseEvent {
29-
for (provider in registry.getAll()) {
50+
for (hook in registry.getAll()) {
3051
try {
31-
val context = provider.getIdentifyContext()
52+
val context = hook.getIdentifyContext()
3253
if (context.isEmpty()) continue
3354
for ((key, value) in context) {
3455
val jsonValue = when (value) {
@@ -43,7 +64,7 @@ internal class IdentifyContextPlugin(
4364
payload.putInContext(key, jsonValue)
4465
}
4566
} catch (e: Exception) {
46-
logger.error("IdentifyContextProvider failed: ${e.message}")
67+
logger.error("IdentifyHook failed: ${e.message}")
4768
}
4869
}
4970
return payload

datapipelines/src/main/kotlin/io/customer/sdk/CustomerIO.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ import io.customer.sdk.core.di.AndroidSDKComponent
3333
import io.customer.sdk.core.di.SDKComponent
3434
import io.customer.sdk.core.module.CustomerIOModule
3535
import io.customer.sdk.core.pipeline.DataPipeline
36-
import io.customer.sdk.core.pipeline.identifyContextRegistry
36+
import io.customer.sdk.core.pipeline.identifyHookRegistry
3737
import io.customer.sdk.core.util.CioLogLevel
3838
import io.customer.sdk.core.util.Logger
3939
import io.customer.sdk.data.model.CustomAttributes
@@ -130,7 +130,7 @@ class CustomerIO private constructor(
130130

131131
// Add plugin to filter events based on SDK configuration
132132
analytics.add(ScreenFilterPlugin(moduleConfig.screenViewUse))
133-
analytics.add(IdentifyContextPlugin(SDKComponent.identifyContextRegistry, logger))
133+
analytics.add(IdentifyContextPlugin(SDKComponent.identifyHookRegistry, logger))
134134
analytics.add(ApplicationLifecyclePlugin())
135135

136136
// Register this instance as DataPipeline so modules can send track events directly

location/src/main/kotlin/io/customer/location/LocationTracker.kt

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package io.customer.location
33
import io.customer.location.store.LocationPreferenceStore
44
import io.customer.location.sync.LocationSyncFilter
55
import io.customer.sdk.core.pipeline.DataPipeline
6-
import io.customer.sdk.core.pipeline.IdentifyContextProvider
6+
import io.customer.sdk.core.pipeline.IdentifyHook
77
import io.customer.sdk.core.util.Logger
88
import io.customer.sdk.util.EventNames
99

@@ -13,7 +13,7 @@ import io.customer.sdk.util.EventNames
1313
*
1414
* Location reaches the backend through two independent paths:
1515
*
16-
* 1. **Identify context enrichment** — implements [IdentifyContextProvider].
16+
* 1. **Identify context enrichment** — implements [IdentifyHook].
1717
* Every identify() call enriches the event context with the latest
1818
* location coordinates. This is unfiltered — a new user always gets
1919
* the device's current location on their profile immediately.
@@ -24,16 +24,17 @@ import io.customer.sdk.util.EventNames
2424
* user's activity timeline for journey/segment triggers.
2525
*
2626
* Profile switch handling is intentionally not tracked here.
27-
* On clearIdentify(), [onReset] clears all state (cache, persistence,
28-
* sync filter). On identify(), the new user's profile receives the
29-
* location via path 1 regardless of the sync filter's state.
27+
* On clearIdentify(), [resetContext] clears all state (cache, persistence,
28+
* sync filter) synchronously during analytics.reset(). On identify(), the
29+
* new user's profile receives the location via path 1 regardless of the
30+
* sync filter's state.
3031
*/
3132
internal class LocationTracker(
3233
private val dataPipeline: DataPipeline?,
3334
private val locationPreferenceStore: LocationPreferenceStore,
3435
private val locationSyncFilter: LocationSyncFilter,
3536
private val logger: Logger
36-
) : IdentifyContextProvider {
37+
) : IdentifyHook {
3738

3839
@Volatile
3940
private var lastLocation: LocationCoordinates? = null
@@ -46,6 +47,20 @@ internal class LocationTracker(
4647
)
4748
}
4849

50+
/**
51+
* Called synchronously by analytics.reset() during clearIdentify.
52+
* Clears all location state: in-memory cache, persisted coordinates,
53+
* and sync filter — similar to how device tokens and other per-user
54+
* state are cleared on reset. This runs before ResetEvent is published,
55+
* guaranteeing no stale data is available for a subsequent identify().
56+
*/
57+
override fun resetContext() {
58+
lastLocation = null
59+
locationPreferenceStore.clearCachedLocation()
60+
locationSyncFilter.clearSyncedData()
61+
logger.debug("Location state reset")
62+
}
63+
4964
/**
5065
* Reads persisted cached location from the preference store and sets the
5166
* in-memory cache so that identify events have location context
@@ -83,19 +98,6 @@ internal class LocationTracker(
8398
syncCachedLocationIfNeeded()
8499
}
85100

86-
/**
87-
* Clears all location state on identity reset (clearIdentify).
88-
* Resets in-memory cache, persisted location, and sync filter —
89-
* similar to how device tokens and other per-user state are
90-
* cleared on reset.
91-
*/
92-
fun onReset() {
93-
lastLocation = null
94-
locationPreferenceStore.clearCachedLocation()
95-
locationSyncFilter.clearSyncedData()
96-
logger.debug("Location state reset")
97-
}
98-
99101
/**
100102
* Re-evaluates the cached location for sending.
101103
* Called on identify (via [onUserIdentified]) and on cold start

location/src/main/kotlin/io/customer/location/ModuleLocation.kt

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import io.customer.sdk.communication.subscribe
1010
import io.customer.sdk.core.di.SDKComponent
1111
import io.customer.sdk.core.module.CustomerIOModule
1212
import io.customer.sdk.core.pipeline.DataPipeline
13-
import io.customer.sdk.core.pipeline.identifyContextRegistry
13+
import io.customer.sdk.core.pipeline.identifyHookRegistry
1414
import io.customer.sdk.core.util.Logger
1515

1616
/**
@@ -77,14 +77,11 @@ class ModuleLocation @JvmOverloads constructor(
7777

7878
locationTracker.restorePersistedLocation()
7979

80-
// Register as IdentifyContextProvider so location is added to identify event context.
81-
// This ensures every identify() call carries the device's current location
82-
// in the event context — the primary way location reaches a user's profile.
83-
SDKComponent.identifyContextRegistry.register(locationTracker)
84-
85-
eventBus.subscribe<Event.ResetEvent> {
86-
locationTracker.onReset()
87-
}
80+
// Register as IdentifyHook so location is added to identify event context
81+
// and cleared synchronously during analytics.reset(). This ensures every
82+
// identify() call carries the device's current location in the event context —
83+
// the primary way location reaches a user's profile.
84+
SDKComponent.identifyHookRegistry.register(locationTracker)
8885

8986
// On identify, attempt to send a supplementary "Location Update" track event.
9087
// The identify event itself already carries location via context enrichment —

location/src/test/java/io/customer/location/LocationTrackerTest.kt

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -214,16 +214,18 @@ class LocationTrackerTest {
214214
verify(exactly = 0) { dataPipeline.track(any(), any()) }
215215
}
216216

217-
// -- onReset --
217+
// -- resetContext (synchronous, called by analytics.reset during clearIdentify) --
218218

219219
@Test
220-
fun givenReset_expectClearsEverything() {
220+
fun givenResetContext_expectClearsEverything() {
221221
tracker.onLocationReceived(37.7749, -122.4194)
222222

223-
tracker.onReset()
223+
tracker.resetContext()
224224

225+
// In-memory location cleared — no stale data for next identify
226+
tracker.getIdentifyContext().shouldBeEmpty()
227+
// Persistence and sync filter also cleared synchronously
225228
verify { store.clearCachedLocation() }
226229
verify { syncFilter.clearSyncedData() }
227-
tracker.getIdentifyContext().shouldBeEmpty()
228230
}
229231
}

0 commit comments

Comments
 (0)