Skip to content

Save SDK configuration on paywall activity so it can reconfigure automatically#1872

Merged
tonidero merged 9 commits into
mainfrom
save-sdk-configuration-reconfigure-paywall-activity
Oct 8, 2024
Merged

Save SDK configuration on paywall activity so it can reconfigure automatically#1872
tonidero merged 9 commits into
mainfrom
save-sdk-configuration-reconfigure-paywall-activity

Conversation

@tonidero

@tonidero tonidero commented Oct 7, 2024

Copy link
Copy Markdown
Contributor

Description

In here we store the latest known state for Purchases so we can reconfigure when displaying the PaywallActivity and the SDK is not configured. This may happen if the app is killed while displaying the PaywallActivity, the developer is not configuring in the application but on an activity and the user returns through the app through the recent apps menu.

Additionally, it includes the changes from #1866 so we avoid reconfiguring the SDK if the configuration parameters are the same.

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

Pretty cool! Some suggestions, but this should be a nice improvement!

}

fun getLatestConfiguration(): PurchasesConfiguration {
return configuration.copy(appUserID = this.appUserID)

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.

We could also make configuration always be the latest? Otherwise we might get confused between configuration and getLatestConfiguration(), and when to use which.

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.

yeah good point... can probably clean this up a bit.


private fun getSdkConfigArgs(savedInstanceState: Bundle): SdkConfigArgs? {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
savedInstanceState.getParcelable(SDK_CONFIG_EXTRA, SdkConfigArgs::class.java)

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 always found the warning in the docs for this one a bit scary:

Warning: the class that implements Parcelable has to be the immediately enclosing class of the runtime type of its CREATOR field (that is, Class.getEnclosingClass() has to return the parcelable implementing class), otherwise this method might throw an exception. If the Parcelable class does not enclose the CREATOR, use the deprecated getParcelable(String) instead.

We should be good, but we can also just use the deprecated variant to avoid it altogether. If we ever change things around in the future, we might run into this exception.

private fun configureSdkWithSavedData(savedInstanceState: Bundle) {
val sdkConfigArgs = getSdkConfigArgs(savedInstanceState)
if (sdkConfigArgs == null) {
Logger.e("TEST: Missing SDK configuration arguments")

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.

Good log, but maybe without the "TEST"? 😛

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.

😅 Yeah my bad, I didn't clean it up :P

}

override fun hashCode(): Int {
var result = context.hashCode()

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 think we can't compare the context nor the ExecutorService to do this since they can potentially be different. Wdyt?

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 think so too. For the purpose of checking whether the configuration set in PaywallActivity is the same as one set in a different Activity, we can ignore the Context and ExecutorService in the equality check. I don't know if we're doing any other equality checks though, or are intending to do so in the future?

private fun configureSdkWithSavedData(savedInstanceState: Bundle) {
val sdkConfigArgs = getSdkConfigArgs(savedInstanceState)
if (sdkConfigArgs == null) {
Logger.e("TEST: Missing SDK configuration arguments")

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.

😅 Yeah my bad, I didn't clean it up :P

}

fun getLatestConfiguration(): PurchasesConfiguration {
return configuration.copy(appUserID = this.appUserID)

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.

yeah good point... can probably clean this up a bit.

@tonidero tonidero changed the base branch from avoid-recreating-purchases-same-config to main October 8, 2024 08:46
@tonidero

tonidero commented Oct 8, 2024

Copy link
Copy Markdown
Contributor Author

I changed the base to main so it also includes the changes in #1866, since I think it's going to be easier to review all together.

}

val latestConfiguration: PurchasesConfiguration
get() = if (initialConfiguration.appUserID == null) {

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 had to add this condition... Basically, the condition to avoid reconfiguration was never wrong if the developer doesn't set an appUserId. In this case, we should just reuse the last configured user id, so it should be safe.

@tonidero tonidero marked this pull request as ready for review October 8, 2024 08:57
@tonidero tonidero requested review from a team and JayShortway October 8, 2024 08:57
@codecov

codecov Bot commented Oct 8, 2024

Copy link
Copy Markdown

Codecov Report

Attention: Patch coverage is 50.00000% with 23 lines in your changes missing coverage. Please review.

Project coverage is 82.23%. Comparing base (256b7ee) to head (e66ce8b).
Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
...com/revenuecat/purchases/PurchasesConfiguration.kt 47.50% 15 Missing and 6 partials ⚠️
.../com/revenuecat/purchases/PurchasesOrchestrator.kt 50.00% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1872      +/-   ##
==========================================
- Coverage   82.41%   82.23%   -0.19%     
==========================================
  Files         226      226              
  Lines        7915     7958      +43     
  Branches     1105     1119      +14     
==========================================
+ Hits         6523     6544      +21     
- Misses        952      967      +15     
- Partials      440      447       +7     

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

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

Looks good! Just had 2 more minor suggestions.

Comment thread purchases/src/defaults/kotlin/com/revenuecat/purchases/Purchases.kt Outdated
get() = if (initialConfiguration.appUserID == null) {
initialConfiguration
} else {
initialConfiguration.copy(appUserID = this.appUserID)

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.

We're now creating new copies every time this is read. Could we keep the up-to-date configuration in a backing var maybe? We can update it whenever the appUserId changes.

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 I've been thinking whether this would be more work right now to calculate on user change or as it is now, when there is a configuration or the PaywallActivity's onSaveInstanceState is called... And I think it might be less work as it is right now? Normally users only configure once, so this won't be called, but there is a bigger chance of logging a different user (though also an uncommon case)... In any case I think we might be trying to early optimizing and I prefer to calculate it on the fly rather than trying to cache the value which might be outdated later... Lmk if you think otherwise!

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 this is less error prone, and the performance difference is probably negligible. I'm okay with it!

}

@Test
fun `Calling configure multiple times with same configuration does not create a new instance`() {

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 tests! 🙌

Comment on lines +185 to +196
Purchases.configure(
PurchasesConfiguration.Builder(this, sdkConfigArgs.apiKey)
.appUserID(sdkConfigArgs.appUserId)
.purchasesAreCompletedBy(sdkConfigArgs.purchasesAreCompletedBy)
.showInAppMessagesAutomatically(sdkConfigArgs.showInAppMessagesAutomatically)
.store(sdkConfigArgs.store)
.diagnosticsEnabled(sdkConfigArgs.diagnosticsEnabled)
.entitlementVerificationMode(sdkConfigArgs.verificationMode)
.dangerousSettings(sdkConfigArgs.dangerousSettings)
.pendingTransactionsForPrepaidPlansEnabled(sdkConfigArgs.pendingTransactionsForPrepaidPlansEnabled)
.build(),
)

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.

If we ever add a configuration option, we have to remember to add it to SdkConfigArgs. Can't really think of a solution though haha (other than some custom Detekt stuff or 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.

Yeah... it's not great but we are missing the mandatory Context parameter so can't think of a good solution either 😞

@tonidero tonidero merged commit f889c76 into main Oct 8, 2024
@tonidero tonidero deleted the save-sdk-configuration-reconfigure-paywall-activity branch October 8, 2024 15:55
JayShortway pushed a commit that referenced this pull request Oct 10, 2024
**This is an automatic release.**

## RevenueCat SDK
### 🐞 Bugfixes
* Save SDK configuration on paywall activity so it can reconfigure
automatically (#1872) via Toni Rico (@tonidero)
### 📦 Dependency Updates
* Bump fastlane from 2.223.1 to 2.224.0 (#1870) via dependabot[bot]
(@dependabot[bot])
* Bump fastlane-plugin-revenuecat_internal from `5b2e35c` to `3b1e7cf`
(#1865) via dependabot[bot] (@dependabot[bot])
* Bump fastlane from 2.222.0 to 2.223.1 (#1860) via dependabot[bot]
(@dependabot[bot])
* Bump fastlane-plugin-revenuecat_internal from `55a0455` to `5b2e35c`
(#1858) via dependabot[bot] (@dependabot[bot])

### 🔄 Other Changes
* [CustomerCenter] Fix help path deserializing when unknown type (#1869)
via Toni Rico (@tonidero)
* [CustomerCenter] Create `CustomerCenter` composable and view model
with some initial UI (#1867) via Toni Rico (@tonidero)
* [CustomerCenter] Add networking layer and models (#1859) via Toni Rico
(@tonidero)
* [CustomerCenter] Adds SubscriptionDetailsView (#1863) via JayShortway
(@JayShortway)

Co-authored-by: revenuecat-ops <ops@revenuecat.com>
JZDesign pushed a commit that referenced this pull request Nov 25, 2025
### Motivation & Description
This is a follow-up of
#1872

We're getting some errors like this one in flutter:

```
Purchases$Companion.getSharedInstance
pf.K - There is no singleton instance. Make sure you configure Purchases before trying to get the default instance.
```

The theory is that this might be related to a similar bug we fixed for
paywalls of the app getting backgrounded and killed while on the
CustomerCenter activity, and then crashing when coming back
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr:fix A bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants