Skip to content

Conversation

@Shahroz16
Copy link
Contributor

@Shahroz16 Shahroz16 commented Jun 15, 2025

closes: https://linear.app/customerio/issue/MBL-1231/refactor-sdk-initialization-component-to-avoid-main-thread-usage

Summary

Migrates device token management from SharedPreferences to DataStore to eliminate main thread violations discovered via StrictMode and synchronous access dealing with saving/retrieving data from storage. Introduces DeviceTokenManager as single source of truth with thread-safe access patterns.

Context & Problem

StrictMode violations revealed that SharedPreferences calls in GlobalPreferenceStore were blocking the main thread during SDK initialization. Device token access during event processing also had possible race condition risks with concurrent operations.

Key Changes

  • DeviceTokenManager: New interface providing immediate synchronous access with background persistence via DataStore
  • GlobalPreferenceStore: Migrated from SharedPreferences to DataStore for async operations
  • ContextPlugin: Updated to delegate device token access to DeviceTokenManager
  • CustomerIO: Simplified device token operations using replaceToken() method for cleaner transition handling
  • StrictMode enabled: Added to sample app to catch future main thread violations

Performance Improvements

  • Eliminates main thread blocking: DataStore operations happen asynchronously in background
  • Faster SDK startup: Device token loaded from memory immediately, storage sync happens in background
  • Improved thread safety: MutableStateFlow provides stronger memory consistency vs @volatile fields
  • Reduced race conditions: Single source of truth pattern prevents token inconsistencies during concurrent operations
  • Cleaner token transitions: replaceToken() method handles refresh scenarios without confusing parameters

Test Coverage

  • All existing tests pass (157 tests, 100% success rate)
  • Thread safety verified via ContextPluginBehaviorTest multi-threaded scenarios
  • Device token flows (new/refresh/delete/migration) remain functionally identical
  • Manual testing

@github-actions
Copy link

github-actions bot commented Jun 15, 2025

Sample app builds 📱

Below you will find the list of the latest versions of the sample apps. It's recommended to always download the latest builds of the sample apps to accurately test the pull request.


@codecov
Copy link

codecov bot commented Jun 15, 2025

Codecov Report

Attention: Patch coverage is 72.44094% with 35 lines in your changes missing coverage. Please review.

Please upload report for BASE (feature-performance-improvements@ca30241). Learn more about missing BASE report.

Files with missing lines Patch % Lines
...n/io/customer/sdk/data/store/DeviceTokenManager.kt 68.75% 15 Missing ⚠️
...o/customer/sdk/data/store/GlobalPreferenceStore.kt 72.41% 4 Missing and 4 partials ⚠️
...lin/io/customer/sdk/core/di/AndroidSDKComponent.kt 0.00% 5 Missing ⚠️
...ines/src/main/kotlin/io/customer/sdk/CustomerIO.kt 87.87% 0 Missing and 4 partials ⚠️
...ustomer/sdk/core/extensions/DataStoreExtensions.kt 66.66% 2 Missing ⚠️
...io/customer/datapipelines/plugins/ContextPlugin.kt 66.66% 1 Missing ⚠️
Additional details and impacted files
@@                         Coverage Diff                         @@
##             feature-performance-improvements     #578   +/-   ##
===================================================================
  Coverage                                    ?   67.66%           
  Complexity                                  ?      558           
===================================================================
  Files                                       ?      121           
  Lines                                       ?     3748           
  Branches                                    ?      455           
===================================================================
  Hits                                        ?     2536           
  Misses                                      ?     1040           
  Partials                                    ?      172           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@github-actions
Copy link

Build available to test
Version: make-saving-configuration-async-SNAPSHOT
Repository: https://s01.oss.sonatype.org/content/repositories/snapshots/

@github-actions
Copy link

github-actions bot commented Jun 15, 2025

📏 SDK Binary Size Comparison Report

Module Last Recorded Size Current Size Change in Size
core 1.5MB 2.1MB ⬆️ +0.60MB
datapipelines 1.8MB 2.3MB ⬆️ +0.50MB
messagingpush 2.3MB 2.8MB ⬆️ +0.50MB
messaginginapp 3.3MB 3.7MB ⬆️ +0.40MB
tracking-migration 1.6MB 2.1MB ⬆️ +0.50MB

@github-actions
Copy link

  • kotlin_compose: make-saving-configuration-async (1750013584)

@github-actions
Copy link

  • java_layout: make-saving-configuration-async (1750013598)

…io/customerio-android into make-saving-configuration-async
@github-actions
Copy link

  • kotlin_compose: make-saving-configuration-async (1750016673)

@github-actions
Copy link

  • java_layout: make-saving-configuration-async (1750016677)

@github-actions
Copy link

  • kotlin_compose: make-saving-configuration-async (1750130553)

@github-actions
Copy link

  • java_layout: make-saving-configuration-async (1750130558)

@github-actions
Copy link

  • java_layout: make-saving-configuration-async (1750168120)

@github-actions
Copy link

  • kotlin_compose: make-saving-configuration-async (1750168130)

@Shahroz16 Shahroz16 marked this pull request as ready for review June 17, 2025 16:22
@Shahroz16 Shahroz16 requested a review from a team as a code owner June 17, 2025 16:22

override val registeredDeviceToken: String?
get() = globalPreferenceStore.getDeviceToken()
get() = deviceTokenManager.deviceToken
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm going to defer discussing the complexity of the solution but my most prominent question is that our main CustomerIO instance access the device token through DeviceTokenManager synchronously while DeviceTokenManager loads the initial value of the token in the background.

Why do we think there is a guarantee that for the initial load in the case of a token being saved locally, the call to registeredDeviceToken will happen after the DeviceTokenManager.init runs have loaded its value?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thats a fair questions, I avoided tackling it in this PR as its already big, the next PR tackles this, introduces another API thats async, thats how it should be. We should not provide this value synchronously just like FCM only provides token only async.

I would ideally want to remove this API, instead of deprecating it as keeping it comes as a cost, but if we wanted to keep it then we can make the synchronous request and get the device token for it, until we decide to remove it.

deviceTokenManager.setDeviceToken(givenToken)

deviceTokenManager.deviceToken shouldBeEqualTo givenToken
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The point I mention above manifests in the tests here, we consider the state of givenValidToken as token being set using DeviceTokenManager.setDeviceToken() but not from saved GlobalPreferenceStore

What happens if the token isn't set through DeviceTokenManager.setDeviceToken() but exists saved in GlobalPreferenceStore, the init call loads the token on a background thread but calls to deviceTokenManager.deviceToken don't necessarily wait for that value to be loaded?

Am i getting this correctly?

@mahmoud-elmorabea mahmoud-elmorabea requested a review from a team June 18, 2025 08:48
Copy link
Contributor

@Ahmed-Ali Ahmed-Ali left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's hold on for this one till we get better clarity on what the StrictMode violations were, and how are we approaching them holistically. Specially with the incomplete clarity on how the device token storage will eventually look like

@Shahroz16 Shahroz16 marked this pull request as draft June 25, 2025 06:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants