Skip to content

Commit 798d03d

Browse files
evan-masseaukennyklaviyoajaysubradan-peluso
authored
Staging branch for next major release 3.0.0 (#161)
* 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 <5167687+evan-masseau@users.noreply.github.com> * 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 <daniel.peluso@klaviyo.com> * 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 <daniel.peluso@klaviyo.com> * fixed some formatting * updated the SDK name to be non lazy --------- Co-authored-by: dan-peluso <daniel.peluso@klaviyo.com> * 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 <63658871+kennyklaviyo@users.noreply.github.com> Co-authored-by: Ajay Subramanya <118314354+ajaysubra@users.noreply.github.com> Co-authored-by: dan-peluso <daniel.peluso@klaviyo.com>
1 parent c842528 commit 798d03d

File tree

73 files changed

+2811
-849
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

73 files changed

+2811
-849
lines changed

MIGRATION_GUIDE.md

+13
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,19 @@
22
This document provides guidance on how to migrate from the old version of the SDK to a newer version.
33
It will be updated as new versions are released including deprecations or breaking changes.
44

5+
# 3.0.0
6+
7+
### Improvements
8+
- The Klaviyo Android SDK now automatically tracks changes to the
9+
user's notification permission whenever the app is opened or resumed.
10+
- Additionally, the SDK will now hold the push token internally after you `resetProfile`
11+
and automatically attach the token to the next profile. This is a change from past behavior where the token
12+
would need to be explicitly set again after resetting.
13+
14+
### Breaking Changes
15+
- The `ProfileKey` options deprecated in `2.3.0` have been removed
16+
- `Klaviyo.lifecycleCallbacks`, deprecated in `2.1.0` has been removed
17+
518
## 2.3.0 Deprecations
619
#### Deprecated `ProfileKey` objects pertaining to identifiers
720
The following `ProfileKey` objects have been deprecated in favor of using the explicit

README.md

+22-8
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,8 @@ send them timely push notifications via [FCM (Firebase Cloud Messaging)](https:/
5555
```kotlin
5656
// build.gradle.kts
5757
dependencies {
58-
implementation("com.github.klaviyo.klaviyo-android-sdk:analytics:2.4.1")
59-
implementation("com.github.klaviyo.klaviyo-android-sdk:push-fcm:2.4.1")
58+
implementation("com.github.klaviyo.klaviyo-android-sdk:analytics:3.0.0")
59+
implementation("com.github.klaviyo.klaviyo-android-sdk:push-fcm:3.0.0")
6060
}
6161
```
6262
</details>
@@ -67,8 +67,8 @@ send them timely push notifications via [FCM (Firebase Cloud Messaging)](https:/
6767
```groovy
6868
// build.gradle
6969
dependencies {
70-
implementation "com.github.klaviyo.klaviyo-android-sdk:analytics:2.4.1"
71-
implementation "com.github.klaviyo.klaviyo-android-sdk:push-fcm:2.4.1"
70+
implementation "com.github.klaviyo.klaviyo-android-sdk:analytics:3.0.0"
71+
implementation "com.github.klaviyo.klaviyo-android-sdk:push-fcm:3.0.0"
7272
}
7373
```
7474
</details>
@@ -235,7 +235,7 @@ In order to send push notifications to your users, you must collect their push t
235235
This is done via the `Klaviyo.setPushToken` method, which registers push token and current authorization state
236236
via the [Create Client Push Token API](https://developers.klaviyo.com/en/reference/create_client_push_token).
237237
Once registered in your manifest, `KlaviyoPushService` will receive *new* push tokens via the `onNewToken` method.
238-
We also recommend retrieving the current token on app startup and registering it with Klaviyo SDK.
238+
We also recommend retrieving the latest token value on app startup and registering it with Klaviyo SDK.
239239
Add the following to your `Application.onCreate` method.
240240

241241
```kotlin
@@ -249,6 +249,9 @@ override fun onCreate(savedInstanceState: Bundle?) {
249249
}
250250
```
251251

252+
*As of version 3.0.0*: After setting a push token, the Klaviyo SDK will automatically track changes to
253+
the user's notification permission whenever the application is opened or resumed from the background.
254+
252255
**Reminder**: `Klaviyo.initialize` is required before using any other Klaviyo SDK functionality, even
253256
if you are only using the SDK for push notifications and not analytics.
254257

@@ -261,9 +264,9 @@ if you are only using the SDK for push notifications and not analytics.
261264
provide code examples for requesting permission and handling the user's response.
262265

263266
#### Push tokens and multiple profiles
264-
Klaviyo SDK will disassociate the device push token from the current profile whenever it is reset by calling
265-
`setProfile` or `resetProfile`. You should call `setPushToken` again after resetting the currently tracked profile
266-
to explicitly associate the device token to the new profile.
267+
If a new profile was set using `setProfile` or if `resetProfile` was called and a new anonymous
268+
profile was created, the push token will be automatically associated with the new profile without
269+
any additional action (like setting token again) required. This functionality was added in release `3.0.0`.
267270

268271
### Receiving Push Notifications
269272
`KlaviyoPushService` will handle displaying all notifications via the `onMessageReceived` method regardless of
@@ -487,6 +490,17 @@ the following metadata tag to your manifest file.
487490
</manifest>
488491
```
489492

493+
#### Proguard / R8 Issues
494+
495+
If you notice issues in the release build of your apps, you can try to manually add a couple rules
496+
to your `proguard-rules.pro` to prevent obfuscation:
497+
```
498+
-keep class com.klaviyo.analytics.** { *; }
499+
-keep class com.klaviyo.core.** { *; }
500+
-keep class com.klaviyo.push-fcm.** { *; }
501+
```
502+
503+
490504
## Contributing
491505
See the [contributing guide](.github/CONTRIBUTING.md) to learn how to contribute to the Klaviyo Android SDK.
492506
We welcome your feedback in the [issues](https://github.com/klaviyo/klaviyo-android-sdk/issues) section of our public GitHub repository.

build.gradle

+39-3
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,33 @@ tasks.register("clean", Delete) {
3838
delete rootProject.layout.buildDirectory
3939
}
4040

41+
static def readXmlValue(String filePath, String tagName, Project project) {
42+
def xmlFile = new File(project.projectDir, filePath)
43+
// Check if the file exists
44+
if (!xmlFile.exists()) {
45+
throw new FileNotFoundException("The XML file does not exist: ${xmlFile.absolutePath}")
46+
}
47+
48+
// Parse the XML file
49+
def xmlContent
50+
try {
51+
xmlContent = new XmlSlurper().parse(xmlFile)
52+
} catch (Exception e) {
53+
throw new RuntimeException("Failed to parse XML file: ${xmlFile.absolutePath}. Error: ${e.message}", e)
54+
}
55+
56+
// Look for the string with the specific name attribute
57+
def result = xmlContent.'string'.find { it.@name == tagName }
58+
59+
if (result == null) {
60+
throw new IllegalArgumentException("No string found with the name '${tagName}' in the file: ${xmlFile.absolutePath}")
61+
}
62+
63+
return result.text()
64+
}
65+
4166
dokkaHtmlMultiModule {
42-
def versionName = versionFor(project, "version.klaviyo.versionName") as String
67+
def versionName = readXmlValue('sdk/core/src/main/res/values/strings.xml','klaviyo_sdk_version_override', project)
4368
def oldVersionsDir = layout.buildDirectory.dir("../docs/")
4469
outputDirectory = layout.buildDirectory.dir("../docs/${versionName}")
4570
includes.from("README.md")
@@ -73,17 +98,28 @@ public class BumpVersion extends DefaultTask {
7398
return nextVersion;
7499
}
75100

101+
static String readXmlValue(String filePath, String tagName) {
102+
def xmlFile = new File(filePath)
103+
def xmlContent = new XmlSlurper().parse(xmlFile)
104+
// Look for the string with the specific name attribute
105+
def result = xmlContent.'string'.find { it.@name == tagName }
106+
return result?.text() ?: ""
107+
}
108+
76109
@TaskAction
77110
public void bumpVersion() {
78-
def currentVersion = versionFor(project, "version.klaviyo.versionName") as String
111+
def currentVersion = readXmlValue('sdk/core/src/main/res/values/strings.xml','klaviyo_sdk_version_override')
112+
println(currentVersion)
79113
def nextVersion = this.getNextVersion()
80114
def currentBuild = versionFor(project, "version.klaviyo.versionCode") as Integer
81115
def nextBuild = currentBuild + 1
82116
print("Changing semantic version number from $currentVersion to $nextVersion\n")
83117
print("Auto-incrementing version code from $currentBuild to $nextBuild\n")
84118

85119
ant.replace(file:"versions.properties", token:"versionCode=$currentBuild", value:"versionCode=$nextBuild")
86-
ant.replace(file:"versions.properties", token:"versionName=$currentVersion", value:"versionName=$nextVersion")
120+
def file = new File('sdk/core/src/main/res/values/strings.xml')
121+
def newName = file.text.replace(currentVersion,nextVersion)
122+
file.text = newName
87123
ant.replace(file:"README.md", token:"analytics:$currentVersion", value:"analytics:$nextVersion")
88124
ant.replace(file:"README.md", token:"push-fcm:$currentVersion", value:"push-fcm:$nextVersion")
89125
ant.replace(file:"docs/index.html", token:"$currentVersion", value:"$nextVersion")

docs/index.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
<!-- Redirect to latest version -->
2-
<meta HTTP-EQUIV="REFRESH" content="0; url=./2.4.1/index.html">
2+
<meta HTTP-EQUIV="REFRESH" content="0; url=./3.0.0/index.html">

gradle.properties

+3
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,8 @@ android.enableJetifier=true
2626
# URL to Klaviyo server
2727
klaviyoServerUrl=https://a.klaviyo.com
2828

29+
# Klaviyo API Revision
30+
klaviyoApiRevision=2023-07-15
31+
2932
# Group ID prefix for all our published modules
3033
klaviyoGroupId=com.klaviyo

sdk/analytics/build.gradle

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import static de.fayard.refreshVersions.core.Versions.versionFor
21

32
project.description = "Public analytics API functionality for the Klaviyo SDK suite"
43
evaluationDependsOn(":sdk")
@@ -30,7 +29,7 @@ afterEvaluate {
3029
from components[ext.publishBuildVariant]
3130
groupId = klaviyoGroupId
3231
artifactId = "analytics"
33-
version = versionFor(project, "version.klaviyo.versionName")
32+
version = readXmlValue('src/main/res/values/strings.xml','klaviyo_sdk_version_override', project(":sdk:core"))
3433
}
3534
}
3635
}

sdk/analytics/consumer-rules.pro

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
-keep class com.klaviyo.core.** { *; }

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

+8-10
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import android.content.pm.ApplicationInfo
55
import android.content.pm.PackageInfo
66
import android.os.Build
77
import androidx.core.app.NotificationManagerCompat
8-
import com.klaviyo.core.BuildConfig
98
import com.klaviyo.core.Registry
9+
import com.klaviyo.core.config.KlaviyoConfig
1010
import com.klaviyo.core.config.getPackageInfoCompat
1111
import com.klaviyo.core.model.fetchOrCreate
1212
import java.util.UUID
@@ -47,19 +47,17 @@ internal object DeviceProperties {
4747
packageInfo.getVersionCodeCompat().toString()
4848
}
4949

50-
val sdkVersion: String by lazy {
51-
BuildConfig.VERSION
52-
}
50+
val sdkVersion: String
51+
get() = KlaviyoConfig.sdkVersion
5352

54-
val sdkName: String by lazy {
55-
"android"
56-
}
53+
val sdkName: String
54+
get() = KlaviyoConfig.sdkName
5755

58-
val backgroundData: Boolean by lazy {
59-
activityManager.isBackgroundRestrictedCompat()
56+
val backgroundDataEnabled: Boolean by lazy {
57+
!activityManager.isBackgroundRestrictedCompat()
6058
}
6159

62-
val notificationPermission: Boolean
60+
val notificationPermissionGranted: Boolean
6361
get() = NotificationManagerCompat.from(Registry.config.applicationContext).areNotificationsEnabled()
6462

6563
val applicationId: String by lazy {

0 commit comments

Comments
 (0)