Skip to content

Commit 2102026

Browse files
committed
Merge branch 'feat/real-time-location' into feat/location-core-types
# Conflicts: # core/api/core.api
2 parents f32d05d + eb4d17e commit 2102026

File tree

13 files changed

+158
-101
lines changed

13 files changed

+158
-101
lines changed

.github/workflows/snapshot-release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ jobs:
5252
body: |
5353
Build available to test
5454
Version: `${{ steps.set-snapshot-version.outputs.VERSION }}`
55-
Repository: `https://s01.oss.sonatype.org/content/repositories/snapshots/`
55+
Repository: `https://central.sonatype.com/repository/maven-snapshots/`
5656
edit-mode: replace
5757
token: ${{ secrets.GITHUB_TOKEN }}
5858

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## [4.15.1](https://github.com/customerio/customerio-android/compare/4.15.0...4.15.1) (2026-02-04)
2+
3+
### Bug Fixes
4+
5+
* In app improvements ([#640](https://github.com/customerio/customerio-android/issues/640)) ([2270377](https://github.com/customerio/customerio-android/commit/22703777fa43d091600e93a873908b2538185f7e))
6+
17
## [4.15.0](https://github.com/customerio/customerio-android/compare/4.14.0...4.15.0) (2026-01-13)
28

39
### Features

core/api/core.api

Lines changed: 13 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,6 @@ public abstract class io/customer/sdk/communication/Event {
1212
public fun getTimestamp ()Ljava/util/Date;
1313
}
1414

15-
public final class io/customer/sdk/communication/Event$AnonymousIdGeneratedEvent : io/customer/sdk/communication/Event {
16-
public fun <init> (Ljava/lang/String;)V
17-
public final fun component1 ()Ljava/lang/String;
18-
public final fun copy (Ljava/lang/String;)Lio/customer/sdk/communication/Event$AnonymousIdGeneratedEvent;
19-
public static synthetic fun copy$default (Lio/customer/sdk/communication/Event$AnonymousIdGeneratedEvent;Ljava/lang/String;ILjava/lang/Object;)Lio/customer/sdk/communication/Event$AnonymousIdGeneratedEvent;
20-
public fun equals (Ljava/lang/Object;)Z
21-
public final fun getAnonymousId ()Ljava/lang/String;
22-
public fun hashCode ()I
23-
public fun toString ()Ljava/lang/String;
24-
}
25-
2615
public final class io/customer/sdk/communication/Event$DeleteDeviceTokenEvent : io/customer/sdk/communication/Event {
2716
public fun <init> ()V
2817
}
@@ -40,17 +29,6 @@ public final class io/customer/sdk/communication/Event$LocationData {
4029
public fun toString ()Ljava/lang/String;
4130
}
4231

43-
public final class io/customer/sdk/communication/Event$ProfileIdentifiedEvent : io/customer/sdk/communication/Event {
44-
public fun <init> (Ljava/lang/String;)V
45-
public final fun component1 ()Ljava/lang/String;
46-
public final fun copy (Ljava/lang/String;)Lio/customer/sdk/communication/Event$ProfileIdentifiedEvent;
47-
public static synthetic fun copy$default (Lio/customer/sdk/communication/Event$ProfileIdentifiedEvent;Ljava/lang/String;ILjava/lang/Object;)Lio/customer/sdk/communication/Event$ProfileIdentifiedEvent;
48-
public fun equals (Ljava/lang/Object;)Z
49-
public final fun getIdentifier ()Ljava/lang/String;
50-
public fun hashCode ()I
51-
public fun toString ()Ljava/lang/String;
52-
}
53-
5432
public final class io/customer/sdk/communication/Event$RegisterDeviceTokenEvent : io/customer/sdk/communication/Event {
5533
public fun <init> (Ljava/lang/String;)V
5634
public final fun component1 ()Ljava/lang/String;
@@ -119,6 +97,19 @@ public final class io/customer/sdk/communication/Event$TrackPushMetricEvent : io
11997
public fun toString ()Ljava/lang/String;
12098
}
12199

100+
public final class io/customer/sdk/communication/Event$UserChangedEvent : io/customer/sdk/communication/Event {
101+
public fun <init> (Ljava/lang/String;Ljava/lang/String;)V
102+
public final fun component1 ()Ljava/lang/String;
103+
public final fun component2 ()Ljava/lang/String;
104+
public final fun copy (Ljava/lang/String;Ljava/lang/String;)Lio/customer/sdk/communication/Event$UserChangedEvent;
105+
public static synthetic fun copy$default (Lio/customer/sdk/communication/Event$UserChangedEvent;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lio/customer/sdk/communication/Event$UserChangedEvent;
106+
public fun equals (Ljava/lang/Object;)Z
107+
public final fun getAnonymousId ()Ljava/lang/String;
108+
public final fun getUserId ()Ljava/lang/String;
109+
public fun hashCode ()I
110+
public fun toString ()Ljava/lang/String;
111+
}
112+
122113
public abstract interface class io/customer/sdk/communication/EventBus {
123114
public abstract fun getFlow ()Lkotlinx/coroutines/flow/SharedFlow;
124115
public abstract fun publish (Lio/customer/sdk/communication/Event;)V

core/src/main/kotlin/io/customer/sdk/Version.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,5 @@ package io.customer.sdk
77

88
// TODO: Check before final release if we still need to keep Version class public or can be internal
99
object Version {
10-
const val version: String = "4.15.0"
10+
const val version: String = "4.15.1"
1111
}

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

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,14 @@ sealed class Event {
1717
// Timestamp of the event
1818
open val timestamp: Date = Date()
1919

20-
data class ProfileIdentifiedEvent(
21-
val identifier: String
22-
) : Event()
23-
24-
data class AnonymousIdGeneratedEvent(
20+
/**
21+
* Event published when user identity changes (identify or clearIdentify).
22+
*
23+
* @param userId The user ID if identified, null if anonymous
24+
* @param anonymousId The anonymous ID (always present)
25+
*/
26+
data class UserChangedEvent(
27+
val userId: String?,
2528
val anonymousId: String
2629
) : Event()
2730

core/src/test/java/io/customer/sdk/communication/EventBusTest.kt

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,19 +44,22 @@ class EventBusTest : JUnit5Test() {
4444
@Test
4545
fun givenPublishEventVerifySubscribe() = runBlocking {
4646
val events = mutableListOf<Event>()
47-
val job = eventBus.subscribe<Event.ProfileIdentifiedEvent> { event ->
47+
val job = eventBus.subscribe<Event.UserChangedEvent> { event ->
4848
events.add(event)
4949
}
5050

51-
val testEvent = Event.ProfileIdentifiedEvent("Test Message")
51+
val testEvent = Event.UserChangedEvent(userId = "testUserId", anonymousId = "testAnonymousId")
5252
println("Publishing event: $testEvent")
5353
eventBus.publish(testEvent)
5454

5555
yield() // Allow event processing
5656

5757
events.shouldHaveSingleItem()
58-
.shouldBeInstanceOf<Event.ProfileIdentifiedEvent>()
59-
.identifier shouldBeEqualTo testEvent.identifier
58+
.shouldBeInstanceOf<Event.UserChangedEvent>()
59+
.also {
60+
it.userId shouldBeEqualTo testEvent.userId
61+
it.anonymousId shouldBeEqualTo testEvent.anonymousId
62+
}
6063

6164
job.cancel()
6265
}

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

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -137,14 +137,8 @@ class CustomerIO private constructor(
137137

138138
private fun postUserIdentificationEvents() {
139139
val userId = analytics.userId()
140-
if (userId != null) {
141-
eventBus.publish(Event.ProfileIdentifiedEvent(identifier = userId))
142-
} else {
143-
val anonymousId = analytics.anonymousId()
144-
if (anonymousId.isNotBlank()) {
145-
eventBus.publish(Event.AnonymousIdGeneratedEvent(anonymousId = anonymousId))
146-
}
147-
}
140+
val anonymousId = analytics.anonymousId()
141+
eventBus.publish(Event.UserChangedEvent(userId = userId, anonymousId = anonymousId))
148142
}
149143

150144
private fun subscribeToJourneyEvents() {
@@ -264,7 +258,7 @@ class CustomerIO private constructor(
264258

265259
logger.info("identify profile with identifier $userId and traits $traits")
266260
// publish event to EventBus for other modules to consume
267-
eventBus.publish(Event.ProfileIdentifiedEvent(identifier = userId))
261+
eventBus.publish(Event.UserChangedEvent(userId = userId, anonymousId = analytics.anonymousId()))
268262
analytics.identify(
269263
userId = userId,
270264
traits = traits,
@@ -322,6 +316,9 @@ class CustomerIO private constructor(
322316
// publish event to EventBus for other modules to consume
323317
eventBus.publish(Event.ResetEvent)
324318
analytics.reset()
319+
320+
val newAnonymousId = analytics.anonymousId()
321+
eventBus.publish(Event.UserChangedEvent(userId = null, anonymousId = newAnonymousId))
325322
}
326323

327324
override val registeredDeviceToken: String?

location/src/main/kotlin/io/customer/location/provider/LocationProvider.kt

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,6 @@ internal interface LocationProvider {
2121
*/
2222
suspend fun requestLocation(granularity: LocationGranularity): LocationSnapshot
2323

24-
/**
25-
* Cancels any in-flight location request. Idempotent - safe to call
26-
* even when no request is in progress.
27-
*/
28-
suspend fun cancelRequestLocation()
29-
3024
/**
3125
* Current authorization state for location access.
3226
* Used for pre-checks before requesting location.

messaginginapp/src/main/java/io/customer/messaginginapp/ModuleMessagingInApp.kt

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,18 @@ class ModuleMessagingInApp(
4848
gistProvider.setCurrentRoute(it.name)
4949
}
5050

51-
eventBus.subscribe<Event.ProfileIdentifiedEvent> {
52-
gistProvider.setUserId(it.identifier)
53-
}
51+
eventBus.subscribe<Event.UserChangedEvent> { event ->
52+
val userId = event.userId
53+
logger.debug("User changed: userId=$userId, anonymousId=${event.anonymousId}")
54+
55+
gistProvider.setAnonymousId(event.anonymousId)
56+
setCustomAttribute("cio_anonymous_id", event.anonymousId)
57+
58+
if (userId != null) {
59+
gistProvider.setUserId(userId)
60+
}
5461

55-
eventBus.subscribe<Event.AnonymousIdGeneratedEvent> {
56-
gistProvider.setAnonymousId(it.anonymousId)
57-
setCustomAttribute("cio_anonymous_id", it.anonymousId)
62+
gistProvider.fetchInAppMessages()
5863
}
5964

6065
eventBus.subscribe<Event.ResetEvent> {

messaginginapp/src/main/java/io/customer/messaginginapp/gist/presentation/GistSdk.kt

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,8 @@ internal class GistSdk(
125125
fetchInAppMessages(duration = interval, initialDelay = interval)
126126
}
127127

128-
// Subscribe to SSE flag changes
128+
// Subscribe to SSE flag changes - only manage timer state, not triggering fetches
129+
// Fetches are controlled by ModuleMessagingInApp event handlers and onActivityResumed()
129130
inAppMessagingManager.subscribeToAttribute({ it.sseEnabled }) { _ ->
130131
// Only manage polling when app is foregrounded
131132
if (!isAppForegrounded) {
@@ -137,14 +138,12 @@ internal class GistSdk(
137138
// SSE is now active - stop polling
138139
logger.debug("SSE enabled for identified user, stopping polling")
139140
resetTimer()
140-
} else {
141-
// SSE not active - start polling
142-
logger.debug("SSE not active, starting polling")
143-
fetchInAppMessages(state.pollInterval)
144141
}
142+
// Note: Starting polling is handled by onActivityResumed() or event handlers
145143
}
146144

147-
// Subscribe to user identification changes
145+
// Subscribe to user identification changes - only manage timer state, not triggering fetches
146+
// Fetches are controlled by ModuleMessagingInApp event handlers and onActivityResumed()
148147
inAppMessagingManager.subscribeToAttribute({ it.isUserIdentified }) { _ ->
149148
// Only manage polling when app is foregrounded
150149
if (!isAppForegrounded) {
@@ -156,11 +155,8 @@ internal class GistSdk(
156155
// SSE is now active - stop polling
157156
logger.debug("User identified with SSE enabled, stopping polling")
158157
resetTimer()
159-
} else {
160-
// SSE not active - start polling
161-
logger.debug("SSE not active, starting polling")
162-
fetchInAppMessages(state.pollInterval)
163158
}
159+
// Note: Starting polling is handled by onActivityResumed() or event handlers
164160
}
165161
}
166162

@@ -178,7 +174,7 @@ internal class GistSdk(
178174
return
179175
}
180176
inAppMessagingManager.dispatch(InAppMessagingAction.SetUserIdentifier(userId))
181-
fetchInAppMessages(state.pollInterval)
177+
// Note: fetch is now controlled by the event handler, not here
182178
}
183179

184180
override fun setAnonymousId(anonymousId: String) {
@@ -188,7 +184,7 @@ internal class GistSdk(
188184
}
189185
logger.debug("Setting anonymous id to: $anonymousId")
190186
inAppMessagingManager.dispatch(InAppMessagingAction.SetAnonymousIdentifier(anonymousId))
191-
fetchInAppMessages(state.pollInterval)
187+
// Note: fetch is now controlled by the event handler, not here
192188
}
193189

194190
override fun dismissMessage() {

0 commit comments

Comments
 (0)