Skip to content

Adds setAppsFlyerConversionData to conveniently track AppsFlyer conversion data#2931

Merged
JayShortway merged 8 commits into
mainfrom
cursor/appsflyer-attribution-integration-1b66
Dec 18, 2025
Merged

Adds setAppsFlyerConversionData to conveniently track AppsFlyer conversion data#2931
JayShortway merged 8 commits into
mainfrom
cursor/appsflyer-attribution-integration-1b66

Conversation

@JayShortway

@JayShortway JayShortway commented Dec 15, 2025

Copy link
Copy Markdown
Member

Motivation

Mini-spec

Description

Introduces a new public function fun setAppsFlyerConversionData(data: Map<*, *>?) to Purchases that processes AppsFlyer conversion data and sets corresponding RevenueCat subscriber attributes.

@cursor

cursor Bot commented Dec 15, 2025

Copy link
Copy Markdown
Contributor

Cursor Agent can help with this pull request. Just @cursor in comments and I'll start working on changes in this branch.
Learn more about Cursor Agents

@JayShortway JayShortway self-assigned this Dec 15, 2025
@JayShortway JayShortway added the pr:feat A new feature label Dec 15, 2025
@JayShortway JayShortway force-pushed the cursor/appsflyer-attribution-integration-1b66 branch 2 times, most recently from b1a220e to d575239 Compare December 15, 2025 16:02
@JayShortway JayShortway changed the title AppsFlyer attribution integration Conveniently track AppsFlyer attribution data Dec 15, 2025
@JayShortway JayShortway marked this pull request as ready for review December 15, 2025 17:37
@JayShortway JayShortway requested a review from a team as a code owner December 15, 2025 17:37
@codecov

codecov Bot commented Dec 15, 2025

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 88.57143% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 78.84%. Comparing base (842f77b) to head (2d5e7b1).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
.../com/revenuecat/purchases/PurchasesOrchestrator.kt 0.00% 2 Missing ⚠️
...in/com/revenuecat/purchases/utils/MapExtensions.kt 50.00% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2931      +/-   ##
==========================================
+ Coverage   78.82%   78.84%   +0.02%     
==========================================
  Files         337      337              
  Lines       13129    13163      +34     
  Branches     1771     1786      +15     
==========================================
+ Hits        10349    10379      +30     
- Misses       2038     2041       +3     
- Partials      742      743       +1     

☔ 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.

@tonidero tonidero left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

My only real comment is to try to move this logic out of PurchasesOrchestrator. Other than that, looks great! Amazing job!

*
* @param data The conversion data map from AppsFlyer's `onConversionDataSuccess` callback.
*/
fun setAppsFlyerAttributionData(data: Map<*, *>?) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Maybe it's simpler this way... but I was wondering whether it would be worth adding a AppslyerAttributionData class that can be constructed with this map, so we perform some validations there and it's more flexible in case we want to change the signature later...

But honestly, don't think it's a blocker. Probably shouldn't throw in case it doesn't pass validations, and we can adapt the signature later if needed, so FFTI!!

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Right, I'm very much pro wrapper classes in the public API to allow us to evolve it in a non-breaking way. However in this case I'm not sure if it's worth it. It's really meant to be called like this:

override fun onConversionDataSuccess(data: MutableMap<String, Any>?) {
  Purchases.sharedInstance.setAppsFlyerAttributionData(data)
}

If AppsFlyer changes the data type, we would add a new function and deprecate the old one. If we had a wrapper class, we would add a new constructor and deprecate the old one. That's pretty much the same, with a tiny bit of added friction because developers need to understand that they need to construct the wrapper class first.

Comment thread purchases/src/main/kotlin/com/revenuecat/purchases/PurchasesOrchestrator.kt Outdated
Comment thread purchases/src/main/kotlin/com/revenuecat/purchases/PurchasesOrchestrator.kt Outdated
Comment thread purchases/src/main/kotlin/com/revenuecat/purchases/PurchasesOrchestrator.kt Outdated

@rickvdl rickvdl left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nice work!

log(LogIntent.DEBUG) { AttributionStrings.METHOD_CALLED.format("setAppsFlyerAttributionData") }

if (data == null) {
return

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Same question here as on iOS, is it ok that we're not support unsetting attributes?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I think that's okay. Conversion attribution is determined once for an install, and never changes. It might also be confusing. For instance, if you've set campaign data manually, and then call this function with a map that doesn't contain campaign data, it would be cleared. That might be unexpected.

I thought about supporting unsetting all attributes at once if the developer passes a nil dictionary. However I also think that would be confusing, if sometimes nil unsets and sometimes it doesn't. I chose to clarify this behavior in the docs instead. (ae39ec7).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for clarifying, agree!

@JayShortway JayShortway force-pushed the cursor/appsflyer-attribution-integration-1b66 branch 4 times, most recently from 6f9e3ab to 5a111dd Compare December 16, 2025 11:30

@tonidero tonidero left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks great to me!!

@JayShortway JayShortway changed the title Conveniently track AppsFlyer attribution data Adds setAppsFlyerConversionData to conveniently track AppsFlyer conversion data Dec 16, 2025

@ajpallares ajpallares left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks good! Just a couple of minor comments. Not a blocker though!

Comment on lines +18 to +24
internal fun Map<*, *>.getStringValue(key: String): String? {
return when (val value = this[key]) {
is String -> value.takeIf { it.isNotBlank() }
null -> null
else -> value.toString().takeIf { it.isNotBlank() }
}
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nit: Couldn't this be simplified to

internal fun Map<*, *>.getStringValue(key: String): String? =
    this[key]?.toString()?.takeIf { it.isNotBlank() }

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Ah yes, other than calling toString on a String, those should be equivalent indeed! Done here: 23d7411.

Comment thread .gitignore Outdated
Comment on lines +140 to +141
# Kotlin compiler
.kotlin

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This change seems unrelated to this PR. I think the change makes sense, but idk if maybe should be done in a separate PR? (for easier tracking mostly)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Good point, done! Changed the base branch of this PR.

@JayShortway JayShortway force-pushed the cursor/appsflyer-attribution-integration-1b66 branch from cb89c6d to 23d7411 Compare December 16, 2025 13:47
@JayShortway JayShortway changed the base branch from main to gitignores-kotlin-compiler-dir December 16, 2025 13:48
Base automatically changed from gitignores-kotlin-compiler-dir to main December 16, 2025 14:24
@JayShortway JayShortway force-pushed the cursor/appsflyer-attribution-integration-1b66 branch from 23d7411 to 39f700a Compare December 16, 2025 15:35
@JayShortway JayShortway disabled auto-merge December 16, 2025 15:39
@JayShortway JayShortway force-pushed the cursor/appsflyer-attribution-integration-1b66 branch from 39f700a to e792cfa Compare December 17, 2025 09:35
@JayShortway JayShortway force-pushed the cursor/appsflyer-attribution-integration-1b66 branch from e792cfa to 2d5e7b1 Compare December 17, 2025 18:44
@JayShortway JayShortway added this pull request to the merge queue Dec 18, 2025
@github-merge-queue github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Dec 18, 2025
@JayShortway JayShortway added this pull request to the merge queue Dec 18, 2025
@github-merge-queue github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Dec 18, 2025
@JayShortway JayShortway merged commit 7bfa5c8 into main Dec 18, 2025
22 of 23 checks passed
@JayShortway JayShortway deleted the cursor/appsflyer-attribution-integration-1b66 branch December 18, 2025 08:43
tonidero added a commit that referenced this pull request Dec 18, 2025
## RevenueCat SDK
### ✨ New Features
* Adds `setAppsFlyerConversionData` to conveniently track AppsFlyer
conversion data (#2931) via JayShortway (@JayShortway)
### 🐞 Bugfixes
* Make close() method also clear the shared instance of the SDK (#2940)
via Toni Rico (@tonidero)
* Fix purchase callback not firing for DEFERRED product changes with
baePlanId in oldProductId (#2937) via Facundo Menzella (@facumenzella)

### 🔄 Other Changes
* [AUTOMATIC] Update golden test files for backend integration tests
(#2949) via RevenueCat Git Bot (@RCGitBot)
* [AUTOMATIC] Update golden test files for backend integration tests
(#2944) via RevenueCat Git Bot (@RCGitBot)
* Adds `.kotlin` to `.gitignore` (#2941) via JayShortway (@JayShortway)
* Dont use reflection to instantiate AmazonOfferingParser (#2934) via
Will Taylor (@fire-at-will)

---------

Co-authored-by: revenuecat-ops <ops@revenuecat.com>
Co-authored-by: Toni Rico <toni.rico.diez@revenuecat.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr:feat A new feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants