From 9df4943209cdf72300ae52d3050f0adc75f5b283 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Mon, 24 Nov 2025 18:31:46 +0300 Subject: [PATCH 1/5] feat: user props on sessions --- .../java/ly/count/sdk/java/internal/SessionImpl.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/sdk-java/src/main/java/ly/count/sdk/java/internal/SessionImpl.java b/sdk-java/src/main/java/ly/count/sdk/java/internal/SessionImpl.java index 22a59e8d..7eee7c33 100644 --- a/sdk-java/src/main/java/ly/count/sdk/java/internal/SessionImpl.java +++ b/sdk-java/src/main/java/ly/count/sdk/java/internal/SessionImpl.java @@ -118,6 +118,9 @@ Future begin(Long now) { } this.consents = SDKCore.instance.consents; + if (config.sdk.userProfile() != null) { + config.sdk.module(ModuleUserProfile.class).saveInternal(); + } if (pushOnChange) { Storage.pushAsync(config, this); @@ -157,6 +160,9 @@ Future update(Long now) { } this.consents = SDKCore.instance.consents; + if (config.sdk.userProfile() != null) { + config.sdk.module(ModuleUserProfile.class).saveInternal(); + } Long duration = updateDuration(now); @@ -192,7 +198,10 @@ Future end(Long now, final Tasks.Callback callback, String did ended = now == null ? System.nanoTime() : now; this.consents = SDKCore.instance.consents; - + if (config.sdk.userProfile() != null) { + config.sdk.module(ModuleUserProfile.class).saveInternal(); + } + if (currentView != null) { currentView.stop(true); } else { From a52b71f777fad6523f91435f4622b9424ab5a66f Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Mon, 24 Nov 2025 18:32:21 +0300 Subject: [PATCH 2/5] feat: user props on sessions changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index dbdeb7c8..d704f753 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## XX.XX.XX +* Updated user properties caching mechanism according to sessions. + ## 24.1.3 * Extended minimum JDK support to 8. From b91bdfa408648625a917b12d83806aa63629a1a1 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Wed, 3 Dec 2025 13:03:28 +0300 Subject: [PATCH 3/5] feat: make it reversable --- sdk-java/src/main/java/ly/count/sdk/java/Config.java | 11 +++++++++++ .../ly/count/sdk/java/internal/InternalConfig.java | 5 +++++ .../java/ly/count/sdk/java/internal/SessionImpl.java | 8 ++++---- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/sdk-java/src/main/java/ly/count/sdk/java/Config.java b/sdk-java/src/main/java/ly/count/sdk/java/Config.java index c4f2372b..953521ff 100644 --- a/sdk-java/src/main/java/ly/count/sdk/java/Config.java +++ b/sdk-java/src/main/java/ly/count/sdk/java/Config.java @@ -243,6 +243,7 @@ public class Config { protected String city = null; protected String country = null; protected boolean locationEnabled = true; + protected boolean autoSendUserPropertiesOnSessions = true; // TODO: storage limits & configuration // protected int maxRequestsStored = 0; @@ -1480,4 +1481,14 @@ public String toString() { return "DID " + id + " ( " + strategy + ")"; } } + + /** + * Disable automatic sending of user properties on session begin, update and end + * + * @return {@code this} instance for method chaining + */ + public Config disableAutoSendUserPropertiesOnSessions() { + this.autoSendUserPropertiesOnSessions = false; + return this; + } } diff --git a/sdk-java/src/main/java/ly/count/sdk/java/internal/InternalConfig.java b/sdk-java/src/main/java/ly/count/sdk/java/internal/InternalConfig.java index 5c51185b..9aa60e9e 100644 --- a/sdk-java/src/main/java/ly/count/sdk/java/internal/InternalConfig.java +++ b/sdk-java/src/main/java/ly/count/sdk/java/internal/InternalConfig.java @@ -32,6 +32,7 @@ public class InternalConfig extends Config { protected IdGenerator viewIdGenerator; protected IdGenerator eventIdGenerator; protected ViewIdProvider viewIdProvider; + /** * Shouldn't be used! */ @@ -211,4 +212,8 @@ String[] getLocationParams() { boolean isLocationDisabled() { return !locationEnabled; } + + boolean isAutoSendUserPropertiesOnSessions() { + return autoSendUserPropertiesOnSessions; + } } diff --git a/sdk-java/src/main/java/ly/count/sdk/java/internal/SessionImpl.java b/sdk-java/src/main/java/ly/count/sdk/java/internal/SessionImpl.java index 7eee7c33..f966034f 100644 --- a/sdk-java/src/main/java/ly/count/sdk/java/internal/SessionImpl.java +++ b/sdk-java/src/main/java/ly/count/sdk/java/internal/SessionImpl.java @@ -118,7 +118,7 @@ Future begin(Long now) { } this.consents = SDKCore.instance.consents; - if (config.sdk.userProfile() != null) { + if (config.isAutoSendUserPropertiesOnSessions() && config.sdk.userProfile() != null) { config.sdk.module(ModuleUserProfile.class).saveInternal(); } @@ -160,7 +160,7 @@ Future update(Long now) { } this.consents = SDKCore.instance.consents; - if (config.sdk.userProfile() != null) { + if (config.isAutoSendUserPropertiesOnSessions() && config.sdk.userProfile() != null) { config.sdk.module(ModuleUserProfile.class).saveInternal(); } @@ -198,10 +198,10 @@ Future end(Long now, final Tasks.Callback callback, String did ended = now == null ? System.nanoTime() : now; this.consents = SDKCore.instance.consents; - if (config.sdk.userProfile() != null) { + if (config.isAutoSendUserPropertiesOnSessions() && config.sdk.userProfile() != null) { config.sdk.module(ModuleUserProfile.class).saveInternal(); } - + if (currentView != null) { currentView.stop(true); } else { From e85b36b826899d2478c4f0d12ba2968152a2fd6b Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Fri, 5 Dec 2025 09:54:14 +0300 Subject: [PATCH 4/5] feat: validate sessions trigger sending user props --- .../sdk/java/internal/SessionImplTests.java | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/sdk-java/src/test/java/ly/count/sdk/java/internal/SessionImplTests.java b/sdk-java/src/test/java/ly/count/sdk/java/internal/SessionImplTests.java index 859d50c3..d7f3d2f0 100644 --- a/sdk-java/src/test/java/ly/count/sdk/java/internal/SessionImplTests.java +++ b/sdk-java/src/test/java/ly/count/sdk/java/internal/SessionImplTests.java @@ -629,6 +629,49 @@ public void view_stopStartedAndNext() { ModuleViewsTests.validateView("next", 0.0, 2, 3, false, true, null, TestUtils.keysValues[1], TestUtils.keysValues[0]); } + /** + * Validates that when session calls are made, if any user properties are set, + * they are sent before sending that session call + * Validated with all session calls: begin, update, end + * + * @throws InterruptedException if thread is interrupted + */ + @Test + public void userPropsOnSessions() throws InterruptedException { + Countly.instance().init(TestUtils.getConfigSessions(Config.Feature.UserProfiles)); + Countly.instance().userProfile().setProperty("name", "John Doe"); + Countly.instance().userProfile().setProperty("custom_key", "custom_value"); + + Countly.session().begin(); + Map[] RQ = TestUtils.getCurrentRQ(); + UserEditorTests.validateUserDetailsRequestInRQ(TestUtils.map("user_details", TestUtils.json("name", "John Doe", "custom", TestUtils.map("custom_key", "custom_value"))), 0, 2); + Assert.assertEquals("1", RQ[1].get("begin_session")); + + Thread.sleep(2000); // wait for session to update + Countly.instance().userProfile().save(); + RQ = TestUtils.getCurrentRQ(); + Assert.assertEquals(2, RQ.length); // Validate that user properties are flushed + + Countly.instance().userProfile().setProperty("email", "john@doe.com"); + Countly.session().update(); + + RQ = TestUtils.getCurrentRQ(); + Assert.assertEquals(TestUtils.json("email", "john@doe.com"), RQ[2].get("user_details")); + Assert.assertEquals("2", RQ[3].get("session_duration")); + + Thread.sleep(2000); // wait for session to update + Countly.instance().userProfile().save(); + RQ = TestUtils.getCurrentRQ(); + Assert.assertEquals(4, RQ.length); // Validate that user properties are flushed with update call + + Countly.instance().userProfile().setProperty("done", "yes"); + Countly.session().end(); + + RQ = TestUtils.getCurrentRQ(); + Assert.assertEquals(TestUtils.json("custom", TestUtils.map("done", "yes")), RQ[4].get("user_details")); + Assert.assertEquals("1", RQ[5].get("end_session")); + } + private void validateNotEquals(int idOffset, BiFunction> setter) { Countly.instance().init(TestUtils.getConfigSessions()); long ts = TimeUtils.timestampMs(); From 1924def7a411ed9ebda71bd395fff8839e423590 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Fri, 5 Dec 2025 13:02:03 +0300 Subject: [PATCH 5/5] feat: with disabled using --- .../sdk/java/internal/SessionImplTests.java | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/sdk-java/src/test/java/ly/count/sdk/java/internal/SessionImplTests.java b/sdk-java/src/test/java/ly/count/sdk/java/internal/SessionImplTests.java index d7f3d2f0..52e16e10 100644 --- a/sdk-java/src/test/java/ly/count/sdk/java/internal/SessionImplTests.java +++ b/sdk-java/src/test/java/ly/count/sdk/java/internal/SessionImplTests.java @@ -672,6 +672,38 @@ public void userPropsOnSessions() throws InterruptedException { Assert.assertEquals("1", RQ[5].get("end_session")); } + /** + * Validates that when session calls are made, if any user properties are set, + * they are not packed because auto-send is disabled + * + * @throws InterruptedException if thread is interrupted + */ + @Test + public void userPropsOnSessions_reversed() throws InterruptedException { + Countly.instance().init(TestUtils.getConfigSessions(Config.Feature.UserProfiles).disableAutoSendUserPropertiesOnSessions()); + Countly.instance().userProfile().setProperty("name", "John Doe"); + Countly.instance().userProfile().setProperty("custom_key", "custom_value"); + + Countly.session().begin(); + Map[] RQ = TestUtils.getCurrentRQ(); + Assert.assertEquals(1, RQ.length); + Assert.assertEquals("1", RQ[0].get("begin_session")); + + Thread.sleep(2000); // wait for session to update + Countly.session().update(); + RQ = TestUtils.getCurrentRQ(); + + Assert.assertEquals(2, RQ.length); + Assert.assertEquals("2", RQ[1].get("session_duration")); + + Thread.sleep(2000); + Countly.session().end(); + RQ = TestUtils.getCurrentRQ(); + + Assert.assertEquals(3, RQ.length); + Assert.assertEquals("1", RQ[2].get("end_session")); + } + private void validateNotEquals(int idOffset, BiFunction> setter) { Countly.instance().init(TestUtils.getConfigSessions()); long ts = TimeUtils.timestampMs();