Skip to content

[Do not merge] PoC: Add new APIs to handle deep links and process RCBilling redemption links#1880

Closed
tonidero wants to merge 16 commits into
mainfrom
eco-1550-rcbillingmobilesdk-add-new-apis-to-read-deep-links
Closed

[Do not merge] PoC: Add new APIs to handle deep links and process RCBilling redemption links#1880
tonidero wants to merge 16 commits into
mainfrom
eco-1550-rcbillingmobilesdk-add-new-apis-to-read-deep-links

Conversation

@tonidero

@tonidero tonidero commented Oct 16, 2024

Copy link
Copy Markdown
Contributor

Description

This is a PoC of what it would take to listen to deep links to be able to redeem RCBilling purchases in the app using custom schema urls and using an old aliasing endpoint. Note that it has quite a few things that are not final and need to change before merging.

This PoC includes 2 new APIs:

  • A DeepLinkHandler.handleDeepLink(intent) to input deep links by the app
  • A RedeemWebPurchaseListener to accept and process web redemption links

@tonidero tonidero force-pushed the eco-1550-rcbillingmobilesdk-add-new-apis-to-read-deep-links branch from 3cddca1 to 49ef1a8 Compare October 16, 2024 11:25

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Had to do this to avoid the same activity from launching multiple times with the deep link... We might want to recommend developers to do the same.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Currently hardcoded schema.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added this override since it might be needed if the activity is already there, like in this example

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We will create a new endpoint for this, instead of relying on the aliasing endpoint. For now, reusing that one and we need to use the corresponding parameter names.

Comment thread purchases/src/defaults/kotlin/com/revenuecat/purchases/Purchases.kt Outdated
Comment thread purchases/src/main/kotlin/com/revenuecat/purchases/PurchasesOrchestrator.kt Outdated
@codecov

codecov Bot commented Oct 18, 2024

Copy link
Copy Markdown

Codecov Report

Attention: Patch coverage is 53.38346% with 62 lines in your changes missing coverage. Please review.

Project coverage is 81.78%. Comparing base (dad3113) to head (b9d7d11).
Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
.../kotlin/com/revenuecat/purchases/common/Backend.kt 3.22% 30 Missing ⚠️
...purchases/deeplinks/WebPurchaseRedemptionHelper.kt 46.42% 15 Missing ⚠️
.../purchases/interfaces/RedeemWebPurchaseListener.kt 0.00% 6 Missing ⚠️
...revenuecat/purchases/common/networking/Endpoint.kt 33.33% 4 Missing ⚠️
...m/revenuecat/purchases/deeplinks/DeepLinkParser.kt 75.00% 2 Missing and 1 partial ⚠️
.../com/revenuecat/purchases/PurchasesOrchestrator.kt 87.50% 2 Missing ⚠️
.../revenuecat/purchases/deeplinks/DeepLinkHandler.kt 94.11% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1880      +/-   ##
==========================================
- Coverage   82.25%   81.78%   -0.48%     
==========================================
  Files         227      231       +4     
  Lines        7968     8101     +133     
  Branches     1121     1140      +19     
==========================================
+ Hits         6554     6625      +71     
- Misses        967     1027      +60     
- Partials      447      449       +2     

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

@tonidero

Copy link
Copy Markdown
Contributor Author

Hi @RevenueCat/coresdk! While this PR is not final (the endpoint is wrong among other things), I would appreciate a look, specially on the new public APIs. I left comments in the places where it's still WIP. Lmk what you think! Also happy to chat over Slack if you have questions.

@tonidero tonidero requested review from a team October 21, 2024 13:43

@JayShortway JayShortway 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.

This is getting good! Had some thoughts, and a lot of questions to help me understand the design better haha, but we're well on our way!

Comment thread purchases/src/main/kotlin/com/revenuecat/purchases/deeplinks/DeepLinkHandler.kt Outdated
Comment thread purchases/src/main/kotlin/com/revenuecat/purchases/deeplinks/DeepLinkHandler.kt Outdated
Comment on lines 31 to 36

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.

Are devs expected to implement this? If not, can we avoid this being an interface?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Actually I originally started without this and having the handleRCBillingPurchaseRedemption receive a lambda like (ResultListener) -> Unit. However, using lambdas in Java is not great 😅. So I decided to switch it with an interface... I think it could potentially be a class, as long as we extract the logic, which is implemented within the SDK outside the current class, which also felt like separating the logic too much... So I feel this is better as an interface but not a strong opinion and could be convinced to move to a class by refactoring the RCBillingPurchaseRedemptionHelper

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.

Yea I do agree this shouldn't be a lambda for Java's sake! I think I'd prefer this being a final class with an internal constructor. That minimizes the API surface. The internal code organization can be tackled in a few ways, but I think the API surface is more important.

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.

For my understanding, why is this in DeepLinkHandler instead of in Purchases? We now have a second entry point for the SDK (DeepLinkHandler.handleDeepLink()), while this could also be Purchases.handleDeepLink().

(I'm referring to the public API. In terms of code organization, the implementation could still live here of course.)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Hmm honestly this was a bit of my preference to try to not bloat the Purchases class even more. Specially considering this can actually work without configuring the SDK... But honestly not a strong preference, if you prefer it to be inside Purchases, I'm ok moving it. As you said, it keeps everything into a single entry point.

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.

Yea I think my preference would be to move the API into the Purchases class. Purchases can be (made into) a super thin facade if we care about bloating it, but I think it helps a developer if there's just 1 entry point into the RevenueCat SDK.

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.

For my understanding (again, sorry haha): I know we talked about this, but why do we want to support calling this before the SDK is configured again? It might be simpler to just make Purchases.sharedInstance.handleDeepLink() the public API for this, but I'm probably overlooking something.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

There are some cases where developers might not be configuring the SDK until later, for example, when a user signs up/signs in. In those cases, I think it's good we cache the deep link and process it once the SDK is actually configured (and the listener set for web redemptions)

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.

What do you think about requiring the SDK to be configured for now? We can relatively easily build the caching later, if people end up asking for it and the product is more fleshed out. Would allow us to remove some complexity from the PoC.

Comment thread purchases/src/main/kotlin/com/revenuecat/purchases/deeplinks/DeepLinkParser.kt Outdated
Comment on lines 72 to 76

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.

Maybe this function could be dispatchResult()? Also, why does this need to be called on the main thread? (You guessed it: just for my understanding 😅)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Well, the result from the Backend operation will always be in our background dispatcher which is used internally. Seemed like a bad option to use that, so thought to just return in the main thread by default for now... We could also try to return in the same thread as the startRedemption was called, but that seemed more work.

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.

Alright, makes sense!

Oh, sorry for being unclear. I see you renamed dispatch to dispatchResult, but I actually meant to rename handleResult to dispatchResult. 🙈

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I wondered about making this a parameter for the SDK configuration… but since this will need to be something that handles the UI for redeeming web purchases, it seemed more flexible to be able to set it later, but lmk what you think!

@tonidero tonidero force-pushed the eco-1550-rcbillingmobilesdk-add-new-apis-to-read-deep-links branch from ca4b6fa to 810da26 Compare October 23, 2024 07:39
@nicfix nicfix marked this pull request as ready for review October 23, 2024 13:16
@nicfix nicfix marked this pull request as draft October 23, 2024 13:16

@JayShortway JayShortway 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.

Very nice! Basically 2 open points left for me:

  • Some ideas to simplify the API.
  • Maybe we can require the SDK to be configured, for now.

Happy to have a broader discussion about this if you prefer!

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.

Parameter naming suggestion:

-    fun handleWebPurchaseRedemption(startRedemption: WebPurchaseRedeemer)
+    fun handleWebPurchaseRedemption(redeemer: WebPurchaseRedeemer)

Comment on lines 31 to 36

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.

Yea I do agree this shouldn't be a lambda for Java's sake! I think I'd prefer this being a final class with an internal constructor. That minimizes the API surface. The internal code organization can be tackled in a few ways, but I think the API surface is more important.

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.

Suggested change
interface ResultListener {
fun interface ResultListener {

This allows for SAM conversion, meaning Kotlin consumers can call this as if it were a lambda, avoiding the need for an anonymous object.

E.g. in the sample MainApplication, we can then make this change:

- webPurchaseRedeemer.redeemWebPurchase(object : RedeemWebPurchaseListener.ResultListener {
-     override fun handleResult(result: RedeemWebPurchaseListener.Result) {
-         // ...
-     }
- })
+ webPurchaseRedeemer.redeemWebPurchase { result ->
+         // ...
+ }

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.

What do you think about requiring the SDK to be configured for now? We can relatively easily build the caching later, if people end up asking for it and the product is more fleshed out. Would allow us to remove some complexity from the PoC.

Comment on lines 72 to 76

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.

Alright, makes sense!

Oh, sorry for being unclear. I see you renamed dispatch to dispatchResult, but I actually meant to rename handleResult to dispatchResult. 🙈

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.

Yea I think my preference would be to move the API into the Purchases class. Purchases can be (made into) a super thin facade if we care about bloating it, but I think it helps a developer if there's just 1 entry point into the RevenueCat SDK.

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.

I understand the scenarios we'd like to cover. I'm a bit concerned about the long chain of callbacks here. We have:

  1. onCreate() / onNewIntent(), in which handleDeepLink() is called.
  2. handleWebPurchaseRedemption(), in which redeemWebPurchase() is called.
  3. handleResult(), containing the result of the redemption.

It's hard to follow the flow of the code through all these callbacks, if a developer is trying to reason about their own web redemption code. It possibly involves going through multiple files to see what each of these callbacks is doing.

I have 2 alternatives in mind, mostly intended to challenge your assumptions haha.

Idea 1: no RedeemWebPurchaseListener

One idea I had is to remove RedeemWebPurchaseListener.handleWebPurchaseRedemption() from the equation. We could define an instance method, e.g. in Purchases, along the lines of:

fun getWebPurchaseRedeemerOrNull(intent: Intent): RedeemWebPurchaseListener.WebPurchaseRedeemer?

This will return null if the intent is not a web purchase intent. This can then be used like so:

override fun onNewIntent(intent: Intent) {
    Purchases.sharedInstance
        .getWebPurchaseRedeemerOrNull(intent)
        ?.redeemWebPurchase { result -> }
}

Devs can keep a hold of the WebPurchaseRedeemer, or the Intent, if they don't want to redeem the purchase right away.

Idea 2: no RedeemWebPurchaseListener & no WebPurchaseRedeemer

To simplify even further, we could remove the concept of a WebPurchaseRedeemer. We could define an instance method in Purchases along the lines of:

fun redeemWebPurchase(intent: Intent, resultListener: ResultListener)

The ResultListener would return an error result if the intent is not a web purchase intent. (If devs want to check whether the intent is a web purchase intent before calling redeemWebPurchase(), we can add a method isWebPurchaseRedemptionIntent() that they can use.)

This can then be used like so:

override fun onNewIntent(intent: Intent) {
    Purchases.sharedInstance.redeemWebPurchase(intent) { result -> }
}

In this case, if devs don't want to redeem right away, they can keep a hold of the Intent, and call redeemWebPurchase() whenever they want.


Again, these are mostly intended to challenge your assumptions and see if there are opportunities to simplify. If we can get away with it, I like idea 2 best because it's by far the simplest and removes a lot of concepts for devs to understand.

Comment thread purchases/src/main/kotlin/com/revenuecat/purchases/PurchasesOrchestrator.kt Outdated
Comment thread purchases/src/main/kotlin/com/revenuecat/purchases/deeplinks/DeepLinkHandler.kt Outdated
@tonidero tonidero force-pushed the eco-1550-rcbillingmobilesdk-add-new-apis-to-read-deep-links branch from 0627571 to b9d7d11 Compare October 24, 2024 11:57
@tonidero

Copy link
Copy Markdown
Contributor Author

I'm closing this PR since looks like we will end up going with the approach in #1889

@tonidero tonidero closed this Oct 29, 2024
@tonidero tonidero deleted the eco-1550-rcbillingmobilesdk-add-new-apis-to-read-deep-links branch October 29, 2024 12:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants