Skip to content

Commit ebc2067

Browse files
committed
shift files
1 parent fc58c72 commit ebc2067

File tree

12 files changed

+116
-97
lines changed

12 files changed

+116
-97
lines changed

core/api/core.api

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,17 @@ public final class io/customer/sdk/communication/Event$LocationData {
2929
public fun toString ()Ljava/lang/String;
3030
}
3131

32+
public final class io/customer/sdk/communication/Event$LocationTrackedEvent : io/customer/sdk/communication/Event {
33+
public fun <init> (Lio/customer/sdk/communication/Event$LocationData;)V
34+
public final fun component1 ()Lio/customer/sdk/communication/Event$LocationData;
35+
public final fun copy (Lio/customer/sdk/communication/Event$LocationData;)Lio/customer/sdk/communication/Event$LocationTrackedEvent;
36+
public static synthetic fun copy$default (Lio/customer/sdk/communication/Event$LocationTrackedEvent;Lio/customer/sdk/communication/Event$LocationData;ILjava/lang/Object;)Lio/customer/sdk/communication/Event$LocationTrackedEvent;
37+
public fun equals (Ljava/lang/Object;)Z
38+
public final fun getLocation ()Lio/customer/sdk/communication/Event$LocationData;
39+
public fun hashCode ()I
40+
public fun toString ()Ljava/lang/String;
41+
}
42+
3243
public final class io/customer/sdk/communication/Event$RegisterDeviceTokenEvent : io/customer/sdk/communication/Event {
3344
public fun <init> (Ljava/lang/String;)V
3445
public final fun component1 ()Ljava/lang/String;
@@ -128,6 +139,11 @@ public final class io/customer/sdk/communication/EventBusImpl : io/customer/sdk/
128139
public fun subscribe (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/Job;
129140
}
130141

142+
public abstract interface class io/customer/sdk/communication/LocationCache {
143+
public abstract fun getLastLocation ()Lio/customer/sdk/communication/Event$LocationData;
144+
public abstract fun setLastLocation (Lio/customer/sdk/communication/Event$LocationData;)V
145+
}
146+
131147
public final class io/customer/sdk/core/BuildConfig {
132148
public static final field BUILD_TYPE Ljava/lang/String;
133149
public static final field DEBUG Z

core/src/main/kotlin/io/customer/sdk/communication/Event.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ sealed class Event {
6161
val location: LocationData
6262
) : Event()
6363

64+
data class LocationTrackedEvent(
65+
val location: LocationData
66+
) : Event()
67+
6468
/**
6569
* Location data in a framework-agnostic format.
6670
* Used to pass location information between modules without
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package io.customer.sdk.communication
2+
3+
interface LocationCache {
4+
var lastLocation: Event.LocationData?
5+
}

datapipelines/src/main/kotlin/io/customer/datapipelines/di/SDKComponentExt.kt

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@ package io.customer.datapipelines.di
22

33
import com.segment.analytics.kotlin.core.Analytics
44
import io.customer.datapipelines.config.DataPipelinesModuleConfig
5-
import io.customer.datapipelines.store.LocationPreferenceStore
6-
import io.customer.datapipelines.store.LocationPreferenceStoreImpl
75
import io.customer.sdk.DataPipelinesLogger
86
import io.customer.sdk.core.di.SDKComponent
97
import io.customer.sdk.core.extensions.getOrNull
@@ -14,8 +12,3 @@ internal val SDKComponent.analyticsFactory: ((moduleConfig: DataPipelinesModuleC
1412

1513
internal val SDKComponent.dataPipelinesLogger: DataPipelinesLogger
1614
get() = singleton<DataPipelinesLogger> { DataPipelinesLogger(logger) }
17-
18-
internal val SDKComponent.locationPreferenceStore: LocationPreferenceStore
19-
get() = singleton<LocationPreferenceStore> {
20-
LocationPreferenceStoreImpl(android().applicationContext, logger)
21-
}

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,20 @@ 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
99
import io.customer.sdk.communication.Event
10+
import io.customer.sdk.communication.LocationCache
1011
import io.customer.sdk.core.util.Logger
1112
import kotlinx.serialization.json.JsonPrimitive
1213

1314
/**
1415
* Plugin that enriches identify events with the last known location in context,
1516
* so Customer.io knows where the user is when their profile is identified.
1617
*/
17-
internal class LocationPlugin(private val logger: Logger) : EventPlugin {
18+
internal class LocationPlugin(private val logger: Logger) : EventPlugin, LocationCache {
1819
override val type: Plugin.Type = Plugin.Type.Enrichment
1920
override lateinit var analytics: Analytics
2021

2122
@Volatile
22-
internal var lastLocation: Event.LocationData? = null
23+
override var lastLocation: Event.LocationData? = null
2324

2425
override fun identify(payload: IdentifyEvent): BaseEvent {
2526
val location = lastLocation ?: return payload

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

Lines changed: 11 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,10 @@ import io.customer.base.internal.InternalCustomerIOApi
1414
import io.customer.datapipelines.config.DataPipelinesModuleConfig
1515
import io.customer.datapipelines.di.analyticsFactory
1616
import io.customer.datapipelines.di.dataPipelinesLogger
17-
import io.customer.datapipelines.di.locationPreferenceStore
1817
import io.customer.datapipelines.extensions.asMap
1918
import io.customer.datapipelines.extensions.sanitizeForJson
2019
import io.customer.datapipelines.extensions.type
2120
import io.customer.datapipelines.extensions.updateAnalyticsConfig
22-
import io.customer.datapipelines.location.LocationTracker
2321
import io.customer.datapipelines.migration.TrackingMigrationProcessor
2422
import io.customer.datapipelines.plugins.ApplicationLifecyclePlugin
2523
import io.customer.datapipelines.plugins.AutoTrackDeviceAttributesPlugin
@@ -30,6 +28,7 @@ import io.customer.datapipelines.plugins.CustomerIODestination
3028
import io.customer.datapipelines.plugins.LocationPlugin
3129
import io.customer.datapipelines.plugins.ScreenFilterPlugin
3230
import io.customer.sdk.communication.Event
31+
import io.customer.sdk.communication.LocationCache
3332
import io.customer.sdk.communication.subscribe
3433
import io.customer.sdk.core.di.AndroidSDKComponent
3534
import io.customer.sdk.core.di.SDKComponent
@@ -111,7 +110,6 @@ class CustomerIO private constructor(
111110

112111
private val contextPlugin: ContextPlugin = ContextPlugin(deviceStore)
113112
private val locationPlugin: LocationPlugin = LocationPlugin(logger)
114-
private val locationTracker: LocationTracker = LocationTracker(locationPlugin, SDKComponent.locationPreferenceStore, logger) { analytics.userId() }
115113

116114
init {
117115
// Set analytics logger and debug logs based on SDK logger configuration
@@ -135,8 +133,8 @@ class CustomerIO private constructor(
135133
analytics.add(locationPlugin)
136134
analytics.add(ApplicationLifecyclePlugin())
137135

138-
// Restore persisted location so identify events have context immediately
139-
locationTracker.restorePersistedLocation()
136+
// Register LocationPlugin as LocationCache so the location module can update it
137+
SDKComponent.registerDependency<LocationCache> { locationPlugin }
140138

141139
// subscribe to journey events emitted from push/in-app module to send them via data pipelines
142140
subscribeToJourneyEvents()
@@ -161,10 +159,10 @@ class CustomerIO private constructor(
161159
registerDeviceToken(deviceToken = it.token)
162160
}
163161
eventBus.subscribe<Event.TrackLocationEvent> {
164-
locationTracker.onLocationReceived(it)?.let { location ->
165-
sendLocationTrack(location)
166-
locationTracker.confirmSync(location.latitude, location.longitude)
167-
}
162+
val userId = analytics.userId()
163+
if (userId.isNullOrEmpty()) return@subscribe
164+
sendLocationTrack(it.location)
165+
eventBus.publish(Event.LocationTrackedEvent(location = it.location))
168166
}
169167
}
170168

@@ -213,12 +211,6 @@ class CustomerIO private constructor(
213211
if (moduleConfig.trackApplicationLifecycleEvents) {
214212
analytics.add(AutomaticApplicationLifecycleTrackingPlugin())
215213
}
216-
217-
// Re-evaluate cached location on cold start (e.g. >24h + >1km since last sync)
218-
locationTracker.syncCachedLocationIfNeeded()?.let { location ->
219-
sendLocationTrack(location)
220-
locationTracker.confirmSync(location.latitude, location.longitude)
221-
}
222214
}
223215

224216
@Deprecated("Use setProfileAttributes() function instead")
@@ -274,20 +266,15 @@ class CustomerIO private constructor(
274266

275267
logger.info("identify profile with identifier $userId and traits $traits")
276268

277-
// publish event to EventBus for other modules to consume
278-
eventBus.publish(Event.UserChangedEvent(userId = userId, anonymousId = analytics.anonymousId()))
279269
analytics.identify(
280270
userId = userId,
281271
traits = traits,
282272
serializationStrategy = serializationStrategy
283273
)
284-
285-
// Re-evaluate cached location now that the user is identified.
286-
// Must come after analytics.identify() so userIdProvider returns the correct userId.
287-
locationTracker.syncCachedLocationIfNeeded()?.let { location ->
288-
sendLocationTrack(location)
289-
locationTracker.confirmSync(location.latitude, location.longitude)
290-
}
274+
// publish event to EventBus for other modules to consume
275+
// Must come after analytics.identify() so that analytics.userId() returns the
276+
// new userId when downstream subscribers (e.g. location resync) gate on it.
277+
eventBus.publish(Event.UserChangedEvent(userId = userId, anonymousId = analytics.anonymousId()))
291278

292279
if (isFirstTimeIdentifying || isChangingIdentifiedProfile) {
293280
logger.debug("first time identified or changing identified profile")
@@ -337,7 +324,6 @@ class CustomerIO private constructor(
337324
}
338325

339326
logger.debug("resetting user profile")
340-
locationTracker.onUserReset()
341327
// publish event to EventBus for other modules to consume
342328
eventBus.publish(Event.ResetEvent)
343329
analytics.reset()

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import io.customer.location.provider.LocationProvider
44
import io.customer.location.provider.LocationRequestException
55
import io.customer.location.type.LocationGranularity
66
import io.customer.sdk.communication.Event
7-
import io.customer.sdk.communication.EventBus
87
import io.customer.sdk.core.util.Logger
98
import kotlinx.coroutines.CancellationException
109

@@ -15,7 +14,7 @@ import kotlinx.coroutines.CancellationException
1514
internal class LocationOrchestrator(
1615
private val config: LocationModuleConfig,
1716
private val logger: Logger,
18-
private val eventBus: EventBus,
17+
private val locationTracker: LocationTracker,
1918
private val locationProvider: LocationProvider
2019
) {
2120

@@ -52,6 +51,6 @@ internal class LocationOrchestrator(
5251
latitude = latitude,
5352
longitude = longitude
5453
)
55-
eventBus.publish(Event.TrackLocationEvent(location = locationData))
54+
locationTracker.onLocationReceived(locationData)
5655
}
5756
}

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package io.customer.location
22

33
import android.location.Location
44
import io.customer.sdk.communication.Event
5-
import io.customer.sdk.communication.EventBus
65
import io.customer.sdk.core.util.Logger
76
import java.util.concurrent.locks.ReentrantLock
87
import kotlin.concurrent.withLock
@@ -18,7 +17,7 @@ import kotlinx.coroutines.launch
1817
internal class LocationServicesImpl(
1918
private val config: LocationModuleConfig,
2019
private val logger: Logger,
21-
private val eventBus: EventBus,
20+
private val locationTracker: LocationTracker,
2221
private val orchestrator: LocationOrchestrator,
2322
private val scope: CoroutineScope
2423
) : LocationServices {
@@ -43,7 +42,7 @@ internal class LocationServicesImpl(
4342
latitude = latitude,
4443
longitude = longitude
4544
)
46-
eventBus.publish(Event.TrackLocationEvent(location = locationData))
45+
locationTracker.onLocationReceived(locationData)
4746
}
4847

4948
override fun setLastKnownLocation(location: Location) {

datapipelines/src/main/kotlin/io/customer/datapipelines/location/LocationTracker.kt renamed to location/src/main/kotlin/io/customer/location/LocationTracker.kt

Lines changed: 38 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
package io.customer.datapipelines.location
1+
package io.customer.location
22

33
import android.location.Location
4-
import io.customer.datapipelines.plugins.LocationPlugin
5-
import io.customer.datapipelines.store.LocationPreferenceStore
4+
import io.customer.location.store.LocationPreferenceStore
65
import io.customer.sdk.communication.Event
6+
import io.customer.sdk.communication.EventBus
7+
import io.customer.sdk.communication.LocationCache
78
import io.customer.sdk.core.util.Logger
89

910
/**
@@ -20,90 +21,85 @@ import io.customer.sdk.core.util.Logger
2021
* 2. >= 1 km distance from the last synced location.
2122
*
2223
* If no synced location exists yet (first time), the filter passes automatically.
24+
*
25+
* Unlike the datapipelines version, this tracker does NOT gate on userId.
26+
* The userId gate is handled by datapipelines when it subscribes to
27+
* [Event.TrackLocationEvent].
2328
*/
2429
internal class LocationTracker(
25-
private val locationPlugin: LocationPlugin,
30+
private val locationCache: LocationCache?,
2631
private val locationPreferenceStore: LocationPreferenceStore,
2732
private val logger: Logger,
28-
private val userIdProvider: () -> String?
33+
private val eventBus: EventBus
2934
) {
3035
/**
3136
* Reads persisted cached location from the preference store and sets it on
32-
* the [LocationPlugin] so that identify events have location context
37+
* the [LocationCache] so that identify events have location context
3338
* immediately after SDK restart.
3439
*/
3540
fun restorePersistedLocation() {
3641
val lat = locationPreferenceStore.getCachedLatitude() ?: return
3742
val lng = locationPreferenceStore.getCachedLongitude() ?: return
38-
locationPlugin.lastLocation = Event.LocationData(latitude = lat, longitude = lng)
43+
locationCache?.lastLocation = Event.LocationData(latitude = lat, longitude = lng)
3944
logger.debug("Restored persisted location: lat=$lat, lng=$lng")
4045
}
4146

4247
/**
43-
* Processes an incoming location event: always caches in the plugin and
44-
* persists coordinates for identify enrichment. Only returns non-null
45-
* (signalling the caller to send a "Location Update" track) when the
46-
* [shouldSync] filter passes.
47-
*
48-
* @return the [Event.LocationData] to send as a track, or null if suppressed.
48+
* Processes an incoming location: always caches in the plugin and
49+
* persists coordinates for identify enrichment. Publishes a
50+
* [Event.TrackLocationEvent] when the [shouldSync] filter passes.
4951
*/
50-
fun onLocationReceived(event: Event.TrackLocationEvent): Event.LocationData? {
51-
val location = event.location
52+
fun onLocationReceived(location: Event.LocationData) {
5253
logger.debug("Location update received: lat=${location.latitude}, lng=${location.longitude}")
5354

5455
// Always cache and persist so identifies have context and location
5556
// survives app restarts — regardless of whether we send a track
56-
locationPlugin.lastLocation = location
57+
locationCache?.lastLocation = location
5758
locationPreferenceStore.saveCachedLocation(location.latitude, location.longitude)
5859

59-
// Only send location tracks for identified users
60-
if (userIdProvider().isNullOrEmpty()) {
61-
logger.debug("Location cached but track skipped: no identified user")
62-
return null
63-
}
64-
6560
if (!shouldSync(location.latitude, location.longitude)) {
6661
logger.debug("Location cached but track suppressed: filter not met")
67-
return null
62+
return
6863
}
6964

70-
logger.debug("Location filter passed, sending Location Update track")
71-
return location
65+
logger.debug("Location filter passed, publishing TrackLocationEvent")
66+
eventBus.publish(Event.TrackLocationEvent(location = location))
7267
}
7368

7469
/**
7570
* Re-evaluates whether the cached location should be synced. Called on
76-
* identify and on cold start to handle cases where:
71+
* identify (via [Event.UserChangedEvent]) and on cold start to handle
72+
* cases where:
7773
* - An identify was sent without location context in a previous session,
7874
* and location has since arrived.
7975
* - The app was restarted after >24h and the cached location should be
8076
* re-sent.
81-
*
82-
* @return the [Event.LocationData] to send as a track, or null if
83-
* the filter does not pass or no cached location exists.
8477
*/
85-
fun syncCachedLocationIfNeeded(): Event.LocationData? {
86-
if (userIdProvider().isNullOrEmpty()) return null
87-
88-
val lat = locationPreferenceStore.getCachedLatitude() ?: return null
89-
val lng = locationPreferenceStore.getCachedLongitude() ?: return null
78+
fun syncCachedLocationIfNeeded() {
79+
val lat = locationPreferenceStore.getCachedLatitude() ?: return
80+
val lng = locationPreferenceStore.getCachedLongitude() ?: return
9081

91-
if (!shouldSync(lat, lng)) return null
82+
if (!shouldSync(lat, lng)) return
9283

9384
logger.debug("Syncing cached location: lat=$lat, lng=$lng")
94-
return Event.LocationData(latitude = lat, longitude = lng)
85+
eventBus.publish(Event.TrackLocationEvent(location = Event.LocationData(latitude = lat, longitude = lng)))
9586
}
9687

9788
/**
98-
* Returns true if the [LocationPlugin] has a cached location.
89+
* Records that a location was successfully queued for delivery.
90+
* Called when [Event.LocationTrackedEvent] is received, confirming
91+
* that datapipelines sent the track.
9992
*/
100-
fun hasLocationContext(): Boolean = locationPlugin.lastLocation != null
93+
fun confirmSync(latitude: Double, longitude: Double) {
94+
locationPreferenceStore.saveSyncedLocation(latitude, longitude, System.currentTimeMillis())
95+
}
10196

10297
/**
103-
* Resets user-level location state on logout. Clears synced data
104-
* (timestamp and synced coordinates) so the next user gets their own
105-
* location track — not gated by the previous user's 24h window.
106-
* Cached coordinates are kept as they are device-level, not user-level.
98+
* Resets user-level location state on logout or profile switch.
99+
* Clears synced data (timestamp and synced coordinates) so the next
100+
* user gets their own location track — not gated by the previous
101+
* user's 24h window. Cached coordinates are kept as they are
102+
* device-level, not user-level.
107103
*/
108104
fun onUserReset() {
109105
locationPreferenceStore.clearSyncedData()
@@ -133,17 +129,6 @@ internal class LocationTracker(
133129
return distance >= MINIMUM_DISTANCE_METERS
134130
}
135131

136-
/**
137-
* Records that a location was successfully queued for delivery.
138-
* Must be called AFTER the "Location Update" track has been enqueued
139-
* via [analytics.track], so that if the process is killed between
140-
* the track and the record, the worst case is a harmless duplicate
141-
* rather than a missed send.
142-
*/
143-
fun confirmSync(latitude: Double, longitude: Double) {
144-
locationPreferenceStore.saveSyncedLocation(latitude, longitude, System.currentTimeMillis())
145-
}
146-
147132
/**
148133
* Computes the distance in meters between two lat/lng points using
149134
* [android.location.Location.distanceBetween]. This is a static math

0 commit comments

Comments
 (0)