Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor singleton service initializers #172

Merged
merged 26 commits into from
Sep 23, 2024
Merged

Conversation

evan-masseau
Copy link
Contributor

@evan-masseau evan-masseau commented May 24, 2024

Description

Update: Some of the work originally in this PR wound up in the 2.4 release instead. I've now merged master into rel/3.0 and then merged that into this branch. At this point, this PR is some lower priority changes to some service initializers and a lot of test fixture naming convention corrections.

We had some assumptions about the order in which dependencies are initialized, and encountered unrecoverable exceptions when those assumptions were incorrect. Most importantly, accessing KlaviyoApiClient before applicationContext was available would throw an exception in the initializer. An initialization error cannot be recovered, even if applicationContext were later provided, because init will only run once.

Check List

  • Are you changing anything with the public API?
  • Are your changes backwards compatible with previous SDK Versions?
  • Have you tested this change on real device?
  • Have you added unit test coverage for your changes?
  • Have you verified that your changes are compatible with all Android versions the SDK currently supports?

Changelog / Code Overview

As a safety precaution, I've removed all the object initializers that had an implicit dependency, though the API client was the only one I've observed an issue with from testing with our test apps.

Test Plan

Updated all the unit tests already
Functional changes are smaller now that some of this branch's original work was cherry picked and released in 2.4 after testing.

Related Issues/Tickets

CHNL-8569 -- this does not address all the AC of that ticket.

Evan Masseau added 8 commits May 23, 2024 10:46
…t rid of objects, just have to be more particular about using object initializers.
…KlaviyoApiClient being a singleton object that can hit an unrecoverable exception in its initializer...
…a class was complicated by the inner class, and besides it works well as a singleton. The initializer was the real problem, so I extract the listeners from the initializer and created a startService method. This is safe to run multiple times if the SDK is re-initialized.
… body except the methods that are themselves protected anyway
@evan-masseau evan-masseau requested a review from a team as a code owner May 24, 2024 18:40
@evan-masseau evan-masseau changed the title Ecm/services refactor Refactor singleton service initializers May 24, 2024
@evan-masseau evan-masseau changed the base branch from master to rel/3.0.0 May 24, 2024 18:40
@@ -61,34 +61,35 @@ internal class KlaviyoTest : BaseTest() {
}

private val capturedProfile = slot<Profile>()
private val apiClientMock: ApiClient = mockk<ApiClient>().apply {
private val mockApiClient: ApiClient = mockk<ApiClient>().apply {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Naming convention fix, sometimes using mock as a prefix, sometimes as a suffix. Suggesting we consolidate to prefix.

@@ -6,7 +6,26 @@ import com.klaviyo.core.config.Log
import com.klaviyo.core.config.Log.Level
import java.util.regex.Pattern

open class KLog : Log {
object KLog : Log {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

KLog doesn't need to be instantiated, it is essentially just a set of static methods anyway.

@@ -14,10 +14,6 @@ internal object KlaviyoLifecycleMonitor : LifecycleMonitor, Application.Activity

private var activityObservers = mutableListOf<ActivityObserver>()

init {
onActivityEvent { Registry.log.verbose(it.type) }
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This one wasn't causing any exceptions, but I thought it best to get rid of any unnecessary object inits to head off the same issues.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah inits can be notoriously hard to track - better safe than sorry

@evan-masseau evan-masseau added the major-release For PRs that contain breaking changes and belong in a major release label Jun 4, 2024
@evan-masseau evan-masseau marked this pull request as draft June 4, 2024 15:57
@evan-masseau
Copy link
Contributor Author

Reverted to draft, will need to update once 2.4.0 is release. I think these changes were still valuable.

Evan Masseau added 3 commits June 11, 2024 10:56
# Conflicts:
#	README.md
#	docs/index.html
#	sdk/analytics/src/main/java/com/klaviyo/analytics/Klaviyo.kt
#	sdk/analytics/src/main/java/com/klaviyo/analytics/networking/KlaviyoApiClient.kt
#	sdk/analytics/src/test/java/com/klaviyo/analytics/KlaviyoPreInitializeTest.kt
#	sdk/analytics/src/test/java/com/klaviyo/analytics/KlaviyoTest.kt
#	sdk/analytics/src/test/java/com/klaviyo/analytics/KlaviyoUninitializedTest.kt
#	sdk/analytics/src/test/java/com/klaviyo/analytics/networking/KlaviyoApiClientTest.kt
#	sdk/analytics/src/test/java/com/klaviyo/analytics/networking/requests/KlaviyoApiRequestTest.kt
#	versions.properties
# Conflicts:
#	sdk/analytics/src/main/java/com/klaviyo/analytics/Klaviyo.kt
#	sdk/analytics/src/test/java/com/klaviyo/analytics/KlaviyoTest.kt
#	sdk/analytics/src/test/java/com/klaviyo/analytics/KlaviyoUninitializedTest.kt
#	sdk/analytics/src/test/java/com/klaviyo/analytics/networking/requests/KlaviyoApiRequestTest.kt
Copy link
Contributor Author

@evan-masseau evan-masseau left a comment

Choose a reason for hiding this comment

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

Merged master into 3.0 and that back into this branch, after the 2.4 release.
At this point, this PR is some lower priority changes to some service initializers and a lot of test fixture naming convention corrections.

@evan-masseau evan-masseau marked this pull request as ready for review June 11, 2024 15:52
Copy link

This PR has not seen any updates in the last 16 days. Without further action this PR will be closed in 14 days. To disable further staleness checks add the evergreen label.

every { lifecycleMonitorMock.offActivityEvent(capture(slotOnActivityEvent)) } returns Unit
every { networkMonitorMock.onNetworkChange(capture(slotOnNetworkChange)) } returns Unit
every { networkMonitorMock.offNetworkChange(capture(slotOnNetworkChange)) } returns Unit
every { mockConfig.networkFlushDepth } returns queueDepth
Copy link
Contributor

Choose a reason for hiding this comment

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

thanks for taking care of this

versions.properties Outdated Show resolved Hide resolved
@evan-masseau evan-masseau merged commit 7038aa1 into rel/3.0.0 Sep 23, 2024
6 checks passed
@evan-masseau evan-masseau deleted the ecm/services-refactor branch September 23, 2024 14:59
dan-peluso added a commit that referenced this pull request Oct 23, 2024
* Separation of API, State, and Side Effects (#160)

* Separation of API, State and Side Effects. Largely left tests alone except for adding a couple and adjusting setup to accommodate changes.

* Use consistent nullability pattern so we aren't converting between empty string and null with identifiers.
Added proper deserializing of the profile attributes store property

* There is indeed a better way to do that

* Renamed some of the new classes and added new tests for them

* Doc blocks, minor cleanup, naming conventions

* Fix nullability test
Don't need immutable profile to expose attributes, but would be good to expose single attribute getter

* First round of PR comments

* missed another arg label

---------

Co-authored-by: Evan Masseau <>

* Set an alpha version number on this branch in case it gets pulled by a snapshot jitpack build

* Remove deprecated lifecycle property (#156)

Co-authored-by: Evan Masseau <>

* Transition profile identifier keys to internal visibility (#155)

Co-authored-by: Evan Masseau <>

* Add Unregister Push Token Request (#162)

* Publish old value of a state property (#164)

Co-authored-by: Evan Masseau <>

* Rename these properties so it is clear what true and false mean. Fix polarity of how we check background data (#166)

Co-authored-by: Evan Masseau <>

* Added tests for publishing of the old value (#165)

* Added test coverage for publishing old value with change callback

* Broadcast on reset, and tests

* Reset as a cleanup step, not a setup step.

---------

Co-authored-by: Evan Masseau <>

* Trigger Unregister on API Key Change (#163)

* Using token endpoint for profile requests when a push token is present in SDK state (#168)

* making token request if token is present

* minor refactor

* fixed tests

* added some tests

* removed push state when resetting

* updated tests

* fixed side effects tests

* removed unused comment

* updated readme

* finally fixed tests

* Fix some setup/teardown in tests to take care of isolation issues. (#169)

Co-authored-by: Evan Masseau <>

---------

Co-authored-by: Evan C Masseau <[email protected]>

* API Header Fix: Move attempt count increment to before we write request headers into the url connection. (#170)

Co-authored-by: Evan Masseau <>

* setProfileAttribute should accept any Serializable value not just String (#179)

* changing profile attribute to accept serializable

* updating for 3.0 release

* logging incorrectly typed profile attribute

* Concurrent network observer fix (#180)

* using synchronized list for observers

* adding concurreny safe structure to all observer instances

* Refresh versions syntax for dependencies

* removing concurrent suffix

---------

Co-authored-by: Evan Masseau <>

* Retry on 503 (#181)

* Sending 503 through retry logic

* removing version.properties unecessary changes

* fixing http import

* CHNL-6996 Proguard docs and consumer rules (#184)

* Adding consumer rules and updating docs

* progaurd changes

* Introduce a de-dupe mechanism for push notifications (#177)

* Introduce tag support so we have the option to de-dupe push notifications similar to how stock FCM sdk can.

* adding constant id and null-checked notification tag

---------

Co-authored-by: Evan Masseau <>
Co-authored-by: Daniel Peluso <[email protected]>

* CHNL-3990 Remove identifiers from state if format issues (#186)

* Remove identifiers from state if API reports format errors

* pr comments

* removing extraneous decoder test

* Exposing klaviyo SDK name and version privately (#185)

* added klaviyo sdk name and version

* Update sdk/analytics/src/main/java/com/klaviyo/analytics/DeviceProperties.kt

Co-authored-by: dan-peluso <[email protected]>

* fixed some formatting

* updated the SDK name to be non lazy

---------

Co-authored-by: dan-peluso <[email protected]>

* Refactor singleton service initializers (#172)

* Convert SystemClock to class

* These shouldn't have been mocked in this test class, its masking a bug.

* Remove unnecessary, slightly risky, object initializers in core.

* Restore system clock to object -- I take it back, we don't have to get rid of objects, just have to be more particular about using object initializers.

* KLog can be a singleton object, no risk there.

* This simple fix would take care of the issue, but i still don't love KlaviyoApiClient being a singleton object that can hit an unrecoverable exception in its initializer...

* Found a balanced solution for KlaviyoApiClient -- converting this to a class was complicated by the inner class, and besides it works well as a singleton. The initializer was the real problem, so I extract the listeners from the initializer and created a startService method. This is safe to run multiple times if the SDK is re-initialized.

* Prefer this way: we don't need to wrap any of the code in this method body except the methods that are themselves protected anyway

* Forgot to add back starting the service in these tests' setup

* Naming convention things

* First shot at a pre-init buffer for failed operations. Needs tests

* Added test of in-memory slate

* I don't like import *

* Added comments, logging, and wrapped the retry operations in safeCall

* Trivial: move error logging out of exception class. Now that we wrap all SDK functions in safeCall, it is more straightforward to log from there.

* Allow ONLY handlePush to be buffered

* Remove unused test

* keep 3.0 version increment

* don't add this back

* never trust github

* Update versions.properties

---------

Co-authored-by: Evan Masseau <>

* Automatically check push permission status on app resume (#188)

* Protect against double subscriptions
* readme and migration guide updates

---------

Co-authored-by: Evan Masseau <>

* Move API revision to core config property pulled from build property (#190)

Co-authored-by: Evan Masseau <>

* [CHNL-12521] Looking for react-native strings to determine sdk name and version for config (#191)

* resource reading maven attmept

* working resource share attempt

* update log level

* fixing unit tests and working resource sharing

* pr comments

* fixing config

* replace bump version task with xml-based task

* fixing unit test

* fixing file pathing for composite builds

* fixing bump version task

* removing build config field from build.gradle

* using readXml for default config

* removing unused versionFor import

* removing capability change broadcast (#192)

* renaming removals to 'breaking changes' (#193)

* bumping version

---------

Co-authored-by: Evan Masseau <>
Co-authored-by: Kenny Tsui <[email protected]>
Co-authored-by: Ajay Subramanya <[email protected]>
Co-authored-by: dan-peluso <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
evergreen major-release For PRs that contain breaking changes and belong in a major release
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants