Skip to content

Release 8.25.0#2739

Merged
vegaro merged 45 commits into
previous-majorfrom
release/8.25.0
Oct 16, 2025
Merged

Release 8.25.0#2739
vegaro merged 45 commits into
previous-majorfrom
release/8.25.0

Conversation

@vegaro
Copy link
Copy Markdown
Member

@vegaro vegaro commented Oct 15, 2025

RevenueCat SDK

Note

This release brings all non-breaking changes between SDK versions 9.6.0 and 9.11.0 to major 8, so developers that don't/can't use major 9, can still get the latest updates. meant to be bought only once, for example, lifetime subscriptions.

✨ New Features

🐞 Bugfixes

RevenueCatUI SDK

Paywallv2

✨ New Features

🐞 Bugfixes

Customer Center

✨ New Features

🐞 Bugfixes

🔄 Other Changes

fire-at-will and others added 9 commits October 15, 2025 16:47
### Description
Adds support for displaying virtual currency balances in the Customer
Center. This mimics the design found in the iOS Customer Center as much
as possible. It adds a list of virtual currency balances to
the`RelevantPurchasesListView` and `NoActiveUserManagementView` views
if:
- The app has virtual currencies
- The "Display virtual currency balances" toggle is enabled in the
Customer Center configuration in the dashboard

### Changes
- Adds `displayVirtualCurrencies` to the CustomerCenterConfig
- Adds a list of virtual currency balances to the
`RelevantPurchasesListView` and `NoActiveUserManagementView`. It
displays a max of 4 virtual currencies. If there are >4 virtual
currencies, it will display the 3 VCs with the highest balance, and show
a "See all in-app currencies" button that navigates to a full VC
balances screen
- Adds a screen that displays all virtual currency balances. It
refreshes the VC balances when this screen is loaded in case the
balances have changed since they were last loaded.

### Testing
- Tested manually
- Added automated tests for the new view model functions

### Screenshots
On the `RelevantPurchasesListView`, with just 2 virtual currencies (no
"see all" button):
<img width="300" alt="Screenshot 2025-09-05 at 2 12 25 PM"
src="https://github.com/user-attachments/assets/f1ca5fbc-1beb-4a77-9d0d-8f729fd853e0"
/>

On the `RelevantPurchasesListView`, with just 5 virtual currencies ("see
all" button is visible):
<img width="300" alt="Screenshot 2025-09-05 at 2 17 12 PM"
src="https://github.com/user-attachments/assets/2ce98cb0-2317-4a24-b22c-239308383ee6"
/>

VC Balances screen (accessed by tapping "See all in-app currencies"
button)
<img width="300" alt="Screenshot 2025-09-05 at 2 18 03 PM"
src="https://github.com/user-attachments/assets/185b57c4-5738-48a5-94b5-25eaf10d8320"
/>

On the `NoActiveUserManagementView` screen:
<img width="300" alt="Screenshot 2025-09-05 at 2 19 20 PM"
src="https://github.com/user-attachments/assets/e5f5cc8d-e4c4-496d-b6f1-ca694569678b"
/>
<!-- Thank you for contributing to Purchases! Before pressing the
"Create Pull Request" button, please provide the following: -->

### Checklist
- [x] If applicable, unit tests
- [ ] If applicable, create follow-up issues for `purchases-ios` and
hybrids

### Motivation

We will be supporting Video components in the Paywalls soon, to do that
we need a good way to cache the large files to the device.

### Description

This PR is a carbon copy of what I did in the iOS sdk recently. There
are 2 cache mechanisms.

1. A deferred value cache—This essentially allows us to reuse the same
coroutine job across multiple call-sites. That way we can prefetch and
move on. When the paywall is launched the same download can be awaited
when it's partially completed. Until it finishes, we can show the
fallback image that should download far faster.

2. The file repository—This knows when to fetch the data from the web or
when to read it from the file system. There are likely things I've
missed here. But there are a couple of important things to note.
a. We are not currently hosting the videos in our ecosystem so a
checksum isn't realistic in our v1
b. I'm good at Kotlin, but not at all experienced with Android SDK
development . Totally open to feedback.
…2671)

Bumps
[fastlane-plugin-revenuecat_internal](https://github.com/RevenueCat/fastlane-plugin-revenuecat_internal)
from `7d97553` to `489faef`.
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/RevenueCat/fastlane-plugin-revenuecat_internal/commit/489faef4846f2a2fa8fc13f79f94631c6dda95a1"><code>489faef</code></a>
Add OTP warning to changelogs (<a
href="https://redirect.github.com/RevenueCat/fastlane-plugin-revenuecat_internal/issues/81">#81</a>)</li>
<li>See full diff in <a
href="https://github.com/RevenueCat/fastlane-plugin-revenuecat_internal/compare/7d97553e9c5baabcd18286f03d8034797a27dd64...489faef4846f2a2fa8fc13f79f94631c6dda95a1">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…2673)

Bumps
[fastlane-plugin-revenuecat_internal](https://github.com/RevenueCat/fastlane-plugin-revenuecat_internal)
from `489faef` to `a6dc551`.
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/RevenueCat/fastlane-plugin-revenuecat_internal/commit/a6dc5513a0e81217913b32778cd627821b06e2b8"><code>a6dc551</code></a>
Strips new lines from versions (<a
href="https://redirect.github.com/RevenueCat/fastlane-plugin-revenuecat_internal/issues/82">#82</a>)</li>
<li>See full diff in <a
href="https://github.com/RevenueCat/fastlane-plugin-revenuecat_internal/compare/489faef4846f2a2fa8fc13f79f94631c6dda95a1...a6dc5513a0e81217913b32778cd627821b06e2b8">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
### Description
As a follow-up from
#2624.

That PR fixed embedded font families when they were in the resources
folder, as it's common in android. However, that didn't fix the issue
when fonts are in the assets folder. For example, when being added in
React native when using `npx react-native-asset`.

This will fix that so font families load correctly when using the assets
folder.
Bumps [rexml](https://github.com/ruby/rexml) from 3.4.1 to 3.4.2.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/ruby/rexml/releases">rexml's
releases</a>.</em></p>
<blockquote>
<h2>REXML 3.4.2 - 2025-08-26</h2>
<h3>Improvement</h3>
<ul>
<li>
<p>Improved performance.</p>
<ul>
<li><a
href="https://redirect.github.com/ruby/rexml/issues/244">GH-244</a></li>
<li><a
href="https://redirect.github.com/ruby/rexml/issues/245">GH-245</a></li>
<li><a
href="https://redirect.github.com/ruby/rexml/issues/246">GH-246</a></li>
<li><a
href="https://redirect.github.com/ruby/rexml/issues/249">GH-249</a></li>
<li><a
href="https://redirect.github.com/ruby/rexml/issues/256">GH-256</a></li>
<li>Patch by NAITOH Jun</li>
</ul>
</li>
<li>
<p>Raise appropriate exception when failing to match start tag in
DOCTYPE</p>
<ul>
<li><a
href="https://redirect.github.com/ruby/rexml/issues/247">GH-247</a></li>
<li>Patch by NAITOH Jun</li>
</ul>
</li>
<li>
<p>Deprecate accepting array as an element in XPath.match, first and
each</p>
<ul>
<li><a
href="https://redirect.github.com/ruby/rexml/issues/252">GH-252</a></li>
<li>Patch by tomoya ishida</li>
</ul>
</li>
<li>
<p>Don't call needless encoding_updated</p>
<ul>
<li><a
href="https://redirect.github.com/ruby/rexml/issues/259">GH-259</a></li>
<li>Patch by Sutou Kouhei</li>
</ul>
</li>
<li>
<p>Reuse XPath::match</p>
<ul>
<li><a
href="https://redirect.github.com/ruby/rexml/issues/263">GH-263</a></li>
<li>Patch by pboling</li>
</ul>
</li>
<li>
<p>Cache redundant calls for doctype</p>
<ul>
<li><a
href="https://redirect.github.com/ruby/rexml/issues/264">GH-264</a></li>
<li>Patch by pboling</li>
</ul>
</li>
<li>
<p>Use Safe Navigation (&amp;.) from Ruby 2.3</p>
<ul>
<li><a
href="https://redirect.github.com/ruby/rexml/issues/265">GH-265</a></li>
<li>Patch by pboling</li>
</ul>
</li>
<li>
<p>Remove redundant return statements</p>
<ul>
<li><a
href="https://redirect.github.com/ruby/rexml/issues/266">GH-266</a></li>
<li>Patch by pboling</li>
</ul>
</li>
<li>
<p>Added XML declaration check &amp; Source#skip_spaces method</p>
<ul>
<li><a
href="https://redirect.github.com/ruby/rexml/issues/282">GH-282</a></li>
<li>Patch by NAITOH Jun</li>
<li>Reported by Sofi Aberegg</li>
</ul>
</li>
</ul>
<h3>Fixes</h3>
<ul>
<li>Fix docs typo
<ul>
<li><a
href="https://redirect.github.com/ruby/rexml/issues/248">GH-248</a></li>
<li>Patch by James Coleman</li>
</ul>
</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/ruby/rexml/blob/master/NEWS.md">rexml's
changelog</a>.</em></p>
<blockquote>
<h2>3.4.2 - 2025-08-26 {#version-3-4-2}</h2>
<h3>Improvement</h3>
<ul>
<li>
<p>Improved performance.</p>
<ul>
<li><a
href="https://redirect.github.com/ruby/rexml/issues/244">GH-244</a></li>
<li><a
href="https://redirect.github.com/ruby/rexml/issues/245">GH-245</a></li>
<li><a
href="https://redirect.github.com/ruby/rexml/issues/246">GH-246</a></li>
<li><a
href="https://redirect.github.com/ruby/rexml/issues/249">GH-249</a></li>
<li><a
href="https://redirect.github.com/ruby/rexml/issues/256">GH-256</a></li>
<li>Patch by NAITOH Jun</li>
</ul>
</li>
<li>
<p>Raise appropriate exception when failing to match start tag in
DOCTYPE</p>
<ul>
<li><a
href="https://redirect.github.com/ruby/rexml/issues/247">GH-247</a></li>
<li>Patch by NAITOH Jun</li>
</ul>
</li>
<li>
<p>Deprecate accepting array as an element in XPath.match, first and
each</p>
<ul>
<li><a
href="https://redirect.github.com/ruby/rexml/issues/252">GH-252</a></li>
<li>Patch by tomoya ishida</li>
</ul>
</li>
<li>
<p>Don't call needless encoding_updated</p>
<ul>
<li><a
href="https://redirect.github.com/ruby/rexml/issues/259">GH-259</a></li>
<li>Patch by Sutou Kouhei</li>
</ul>
</li>
<li>
<p>Reuse XPath::match</p>
<ul>
<li><a
href="https://redirect.github.com/ruby/rexml/issues/263">GH-263</a></li>
<li>Patch by pboling</li>
</ul>
</li>
<li>
<p>Cache redundant calls for doctype</p>
<ul>
<li><a
href="https://redirect.github.com/ruby/rexml/issues/264">GH-264</a></li>
<li>Patch by pboling</li>
</ul>
</li>
<li>
<p>Use Safe Navigation (&amp;.) from Ruby 2.3</p>
<ul>
<li><a
href="https://redirect.github.com/ruby/rexml/issues/265">GH-265</a></li>
<li>Patch by pboling</li>
</ul>
</li>
<li>
<p>Remove redundant return statements</p>
<ul>
<li><a
href="https://redirect.github.com/ruby/rexml/issues/266">GH-266</a></li>
<li>Patch by pboling</li>
</ul>
</li>
<li>
<p>Added XML declaration check &amp; Source#skip_spaces method</p>
<ul>
<li><a
href="https://redirect.github.com/ruby/rexml/issues/282">GH-282</a></li>
<li>Patch by NAITOH Jun</li>
<li>Reported by Sofi Aberegg</li>
</ul>
</li>
</ul>
<h3>Fixes</h3>
<ul>
<li>Fix docs typo
<ul>
<li><a
href="https://redirect.github.com/ruby/rexml/issues/248">GH-248</a></li>
<li>Patch by James Coleman</li>
</ul>
</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/ruby/rexml/commit/f36916fe1c66b8cdc1fe482263115625e084d8fe"><code>f36916f</code></a>
Add 3.4.2 entry (<a
href="https://redirect.github.com/ruby/rexml/issues/284">#284</a>)</li>
<li><a
href="https://github.com/ruby/rexml/commit/5859bdeac792687eaf93d8e8f0b7e3c1e2ed5c23"><code>5859bde</code></a>
Added XML declaration check &amp; <code>Source#skip_spaces</code> method
(<a
href="https://redirect.github.com/ruby/rexml/issues/282">#282</a>)</li>
<li><a
href="https://github.com/ruby/rexml/commit/1d876e3bf658b7b4ec7c3372867521695e8eb023"><code>1d876e3</code></a>
Bump actions/checkout from 4 to 5 (<a
href="https://redirect.github.com/ruby/rexml/issues/283">#283</a>)</li>
<li><a
href="https://github.com/ruby/rexml/commit/c87bda8bb8773da7e5a0faf9f16ff165eb052a35"><code>c87bda8</code></a>
Remove ostruct from dev deps (<a
href="https://redirect.github.com/ruby/rexml/issues/281">#281</a>)</li>
<li><a
href="https://github.com/ruby/rexml/commit/c60ae027a3c20f359fdf76fa41ae64d22313f482"><code>c60ae02</code></a>
Remove bundler from dev deps (<a
href="https://redirect.github.com/ruby/rexml/issues/277">#277</a>)</li>
<li><a
href="https://github.com/ruby/rexml/commit/9b084d78708638cedff54743edc0907c4bd6574a"><code>9b084d7</code></a>
Fix &amp; Deprecate REXML::Text#text_indent (<a
href="https://redirect.github.com/ruby/rexml/issues/275">#275</a>)</li>
<li><a
href="https://github.com/ruby/rexml/commit/04a589a61bf4e366abee8764ee74b03f4aecc4aa"><code>04a589a</code></a>
Fix a bug that XPath can't be used for no document element (<a
href="https://redirect.github.com/ruby/rexml/issues/268">#268</a>)</li>
<li><a
href="https://github.com/ruby/rexml/commit/66232eaf680d0937ae59bea285cdb8e4d3d88a93"><code>66232ea</code></a>
Remove redundant return statements (<a
href="https://redirect.github.com/ruby/rexml/issues/266">#266</a>)</li>
<li><a
href="https://github.com/ruby/rexml/commit/63f3e9772595a64b036953f0ab026d2ea5560a3b"><code>63f3e97</code></a>
Use Safe Navigation (&amp;.) from Ruby 2.3 (<a
href="https://redirect.github.com/ruby/rexml/issues/265">#265</a>)</li>
<li><a
href="https://github.com/ruby/rexml/commit/d427fc5914fcc17d7247c5ff9099ee38639d6702"><code>d427fc5</code></a>
Avoid redundant calls for doctype (<a
href="https://redirect.github.com/ruby/rexml/issues/264">#264</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/ruby/rexml/compare/v3.4.1...v3.4.2">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=rexml&package-manager=bundler&previous-version=3.4.1&new-version=3.4.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/RevenueCat/purchases-android/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…2683)

Bumps
[fastlane-plugin-revenuecat_internal](https://github.com/RevenueCat/fastlane-plugin-revenuecat_internal)
from `a6dc551` to `401d148`.
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/RevenueCat/fastlane-plugin-revenuecat_internal/commit/401d1486a070907c8ecaac3fcaa10e8f2b8cc91b"><code>401d148</code></a>
Add <code>CheckGithubAuthenticationAction</code> to check rate limits
(<a
href="https://redirect.github.com/RevenueCat/fastlane-plugin-revenuecat_internal/issues/83">#83</a>)</li>
<li>See full diff in <a
href="https://github.com/RevenueCat/fastlane-plugin-revenuecat_internal/compare/a6dc5513a0e81217913b32778cd627821b06e2b8...401d1486a070907c8ecaac3fcaa10e8f2b8cc91b">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Fix multiple cases:

Broken trials when more than one period unit:
**Before (P2M):** `First 1 month free, then $3.99/mth`
**After:** `First 2 months free, then $3.99/mth`
**Before (P3D):** `First 1 day free, then $3.99/mth`
**After:** `First 3 days free, then $3.99/mth`

Broken discounted recurring price :
**Before (2 P6M):** `$0.99 during 2 months, then $3.99/6 mths`
**After:** `$0.99/6 mths for 2 periods, then $3.99/6 mths`

This case was not broken but it's more clear now:
**Before (2 P1M):** `$0.99 during 2 months, then $3.99/mth`
**After:** `$0.99/mth for 2 periods, then $3.99/mth`

Single discounted payment when more than one period unit:
**Before (P6M):** `1 month for $1.99, then $3.99/mth`
**After:** `6 months for $1.99, then $3.99/mth`
**Before (P6Y):** `1 year for $1.99, then $3.99/mth`
**After:** `6 years for $1.99, then $3.99/mth`

Three phases were broken as well. Some examples:
**Before (P2M trial, 1M single payment):** `Try 1 month for free, then 1
month for $1.99, and $3.99/mth thereafter`
**After:** `Try 2 months for free, then 1 month for $1.99, and $3.99/mth
thereafter`
**Before (P3D trial, 1M single payment):** `Try 1 day for free, then 1
month for $1.99, and $3.99/mth thereafter`
**After:** `Try 3 days for free, then 1 month for $1.99, and $3.99/mth
thereafter`
**Before (P2M trial, 6M single payment):** `Try 1 month for free, then 1
month for $1.99, and $3.99/mth thereafter`
**After:** `Try 2 months for free, then 6 months for $1.99, and
$3.99/mth thereafter`
**Before (P3D trial, 6M single payment):** `Try 1 day for free, then 1
month for $1.99, and $3.99/mth thereafter`
**After:** `Try 3 days for free, then 6 months for $1.99, and $3.99/mth
thereafter`
**Before (P2M trial, 2 months discounted):** `Try 1 month for free, then
$0.99 during 2 months, and $3.99/mth thereafter`
**After:** `Try 2 months for free, then $0.99/mth for 2 periods, and
$3.99/mth thereafter`
**Before (P2M trial, 2 6-month discounted):** `Try 1 month for free,
then $0.99 during 2 months, and $3.99/6 mths thereafter`
**After:** `Try 2 months for free, then $0.99/6 mths for 2 periods, and
$3.99/6 mths thereafter`
This PR removes the ENABLE_SIMULATED_STORE build config to enable the
Test Store functionality in the SDK
# Conflicts:
#	purchases/src/main/kotlin/com/revenuecat/purchases/PurchasesOrchestrator.kt
@vegaro vegaro changed the title Release/8.25.0 Release 8.25.0 Oct 15, 2025
skydoves and others added 20 commits October 15, 2025 17:09
### Motivation
Reimplement placeholder with `Modifier.Node` for better UI performance.

### Description

As follows the [official
documentation](https://developer.android.com/develop/ui/compose/custom-modifiers#implement-custom),
the `composed` API isn't recommended anymore because of the performance
issue.

> Note: There is another API for creating custom modifiers, [composed
{}](https://developer.android.com/reference/kotlin/androidx/compose/ui/Modifier#(androidx.compose.ui.Modifier).composed(kotlin.Function1,kotlin.Function1)).
This API is no longer recommended due to the performance issues it
created. Modifier.Node was designed from the ground up to be far more
performant than composed modifiers. For more details on the problems
with composed modifiers, see the Android Dev Summit talk [Compose
Modifiers Deep Dive](https://www.youtube.com/watch?v=BjGX2RftXsU).

The result:


![screenshot](https://github.com/user-attachments/assets/795424ec-723b-4c17-b281-bd7b54363269)

---------

Co-authored-by: JayShortway <29483617+JayShortway@users.noreply.github.com>
Reported in #2681

- I added some opacity to the Expired badge, so it shows some contrast against the background, in case it's the same color as `onSurface`. This is the same for Cancelled and Active.
- Added previews for dark mode.
- Improved the border of lifetime, so it's based off the `onSurface` color with an opacity and not a hardcoded color

<img width="310" height="99" alt="Screenshot 2025-09-29 at 16 33 14" src="https://github.com/user-attachments/assets/1cf97c1b-f9aa-47b7-ad5f-206df9f2241c" />

<img width="307" height="149" alt="Screenshot 2025-09-29 at 16 35 20" src="https://github.com/user-attachments/assets/16853aa1-d020-4ab4-8bc9-10b98cbf1d83" />

<img width="312" height="100" alt="Screenshot 2025-09-29 at 16 35 51" src="https://github.com/user-attachments/assets/55355c7f-8c19-434a-b62d-ba16b09707e0" />
…2697)

Bumps
[fastlane-plugin-revenuecat_internal](https://github.com/RevenueCat/fastlane-plugin-revenuecat_internal)
from `401d148` to `7508f17`.
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/RevenueCat/fastlane-plugin-revenuecat_internal/commit/7508f173ab5224816b52fdfaf8efe5d433c471a1"><code>7508f17</code></a>
Add missing GitHub auth in some actions (<a
href="https://redirect.github.com/RevenueCat/fastlane-plugin-revenuecat_internal/issues/88">#88</a>)</li>
<li><a
href="https://github.com/RevenueCat/fastlane-plugin-revenuecat_internal/commit/1593f78d0b9b24b48238337666183e3ba82f848e"><code>1593f78</code></a>
Add <code>current_version</code> parameter to
<code>DetermineNextVersionUsingLabelsAction</code> (<a
href="https://redirect.github.com/RevenueCat/fastlane-plugin-revenuecat_internal/issues/87">#87</a>)</li>
<li><a
href="https://github.com/RevenueCat/fastlane-plugin-revenuecat_internal/commit/e1c0e045dfef42a8a7c6a4abc6458ed1b228fd4a"><code>e1c0e04</code></a>
Use correct <code>GITHUB_TOKEN</code> (<a
href="https://redirect.github.com/RevenueCat/fastlane-plugin-revenuecat_internal/issues/86">#86</a>)</li>
<li><a
href="https://github.com/RevenueCat/fastlane-plugin-revenuecat_internal/commit/24d8edac49bf1f7e09ffe4c184038536938077e7"><code>24d8eda</code></a>
Allow automatic bump in older majors (<a
href="https://redirect.github.com/RevenueCat/fastlane-plugin-revenuecat_internal/issues/85">#85</a>)</li>
<li><a
href="https://github.com/RevenueCat/fastlane-plugin-revenuecat_internal/commit/6d539b31901a6e076d53e45de928f33582f994a2"><code>6d539b3</code></a>
Consider <code>next_version</code> to create changelog (<a
href="https://redirect.github.com/RevenueCat/fastlane-plugin-revenuecat_internal/issues/84">#84</a>)</li>
<li>See full diff in <a
href="https://github.com/RevenueCat/fastlane-plugin-revenuecat_internal/compare/401d1486a070907c8ecaac3fcaa10e8f2b8cc91b...7508f173ab5224816b52fdfaf8efe5d433c471a1">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
### Checklist
- [x] If applicable, unit tests
- [x] If applicable, create follow-up issues for `purchases-android` and
hybrids

### Motivation

We are adding a native RC <-> Airbridge integration. The backend and
frontend changes for the integration have been released. On the SDK
side, we need a method to set the airbridge device ID captured by
[Airbridge
SDK](https://help.airbridge.io/en/developers/fetching-guide-for-android-sdk).

### Description

Expose a method so developers can set an Airbridge Device ID after
capturing it from [Airbridge
SDK](https://help.airbridge.io/en/developers/fetching-guide-for-android-sdk)

This works exactly the same way as the existing Kochava integration &&
`setKochavaDeviceID`
## Description
Adds the `tab_id`, `id` and `default_tab_id` properties to the
`TabsComponent` model where necessary. These new properties are not used
yet. This makes the same changes to the JSON models as these iOS PRs:
- RevenueCat/purchases-ios#5209
- RevenueCat/purchases-ios#5430

---------

Co-authored-by: Franco Correa <4152942+francocorreasosa@users.noreply.github.com>
### Motivation
It's useful to be notified when some changes happen. Follow-up of
RevenueCat/purchases-ios#5541

### Description
- Add customer center to some paths
…2703)

### Checklist
- [x] If applicable, unit tests
- [ ] If applicable, create follow-up issues for `purchases-ios` and
hybrids

### Motivation
<!-- Why is this change required? What problem does it solve? --> Test
named "GooglePlayServicesNotAvailableException when calling
getDeviceIdentifiers" is testing GooglePlayServicesRepairableException
instead of GooglePlayServicesNotAvailableException.
GooglePlayServicesRepairableException is already tested in the next
test.
<!-- Please link to issues following this format: Resolves #999999 -->

### Description
<!-- Describe your changes in detail -->
Test named "GooglePlayServicesNotAvailableException when calling
getDeviceIdentifiers" is testing GooglePlayServicesRepairableException
instead of GooglePlayServicesNotAvailableException.
GooglePlayServicesRepairableException is already tested in the next
test.

This was contributed by @nikit19 in #2674

Co-authored-by: Nikit Bhandari <nikit.bhandari@gmail.com>
Co-authored-by: Antonio Pallares <ajpallares@users.noreply.github.com>
## Summary
- Updates fastlane-plugin-revenuecat_internal to revision
e555afbcf227f82883e26d72c4386873e41dc56d

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <noreply@anthropic.com>
<!-- Thank you for contributing to Purchases! Before pressing the
"Create Pull Request" button, please provide the following: -->

> [!IMPORTANT]
> This is behind a compiler flag. When it merges it will be dark code.
There is a follow up PR planned to implement a
> Checksum and more robust file storage to better handle the load on
device memory

https://github.com/user-attachments/assets/6a6fd861-e243-4216-a8e2-272e6c7d3e28

> [!TIP]
> To test this, run PR 6173 in the revenue cat app

### Checklist
- [ ] If applicable, unit tests
- [ ] If applicable, create follow-up issues for `purchases-ios` and
hybrids

### Motivation
<!-- Why is this change required? What problem does it solve? -->
<!-- Please link to issues following this format: Resolves #999999 -->

We have requests for video support.

### Description
<!-- Describe your changes in detail -->
<!-- Please describe in detail how you tested your changes -->

Creates a video component that uses the native MediaPlayer under the
hood.

It supports autoplay, looping, muting audio, showing playback controls,
and content scale overrides like fill while retaining the aspect ratio
_(which was surprisingly the hardest part)_
# Conflicts:
#	purchases/src/main/kotlin/com/revenuecat/purchases/paywalls/components/VideoComponent.kt
#	purchases/src/test/java/com/revenuecat/purchases/paywalls/components/VideoComponentTests.kt
#	ui/revenuecatui/src/main/kotlin/com/revenuecat/purchases/ui/revenuecatui/components/style/StyleFactory.kt
We believe this scenario can deadlock:
- `Purchases.logOut()` , acquires `IdentityManager` lock
- Calls
`subscriberAttributesManager.synchronizeSubscriberAttributesForAllUsers()`
which does some stuff and actually has a `waitUntilIdle()`. It may need
`PurchasesOrchestrator` lock for the operations to complete, but it
might be held by the next step (`onAppForegrounded`)
- `onAppForegrounded` calls `allowSharingPlayStoreAccount` , acquires
`PurchasesOrchestrator` lock
- it tries to call `identityManager.currentUserIsAnonymous()` but
`IdentityManager` is locked
- at the same time `onBillingSetupFinished` and calls
`PurchasesOrchestrator.getAllowSharingPlayStoreAccount` , which is
locked on `PurchasesOrchestrator`

So basically, both `PurchasesOrchestrator` and `IdentityManager` get
locked and never complete because they depend on each other

I think this nested lock situation (holds `PurchasesOrchestrator` then
IdentityManager) can be problematic:
```
@synchronized get() =
    state.allowSharingPlayStoreAccount ?: identityManager.currentUserIsAnonymous()
```    
    
This is the reported stacktrace:

main (onBillingSetupFinished)
```
"main" tid=1 Blocked
  at com.revenuecat.purchases.PurchasesOrchestrator.getAllowSharingPlayStoreAccount (unavailable)
  at com.revenuecat.purchases.PurchasesOrchestrator$3.onConnected (PurchasesOrchestrator.kt:207)
  at com.revenuecat.purchases.google.BillingWrapper.onBillingSetupFinished$lambda$34 (BillingWrapper.kt:594)
```

pool-5-thread-1 (onAppForegrounded)
```
"pool-5-thread-1" tid=52 Blocked
  at com.revenuecat.purchases.identity.IdentityManager.currentUserIsAnonymous (unavailable)
  at com.revenuecat.purchases.PurchasesOrchestrator.getAllowSharingPlayStoreAccount (PurchasesOrchestrator.kt:189)
  at com.revenuecat.purchases.PurchasesOrchestrator$onAppForegrounded$3.invoke (PurchasesOrchestrator.kt:263)
```

CapacitorPlugins (logOut)
```
"CapacitorPlugins" tid=37 Blocked
  at com.revenuecat.purchases.PurchasesOrchestrator$logOut$1.invoke (PurchasesOrchestrator.kt:630)
  at com.revenuecat.purchases.PurchasesOrchestrator$logOut$1.invoke (PurchasesOrchestrator.kt:626)
  at com.revenuecat.purchases.identity.IdentityManager$logOut$2.invoke (IdentityManager.kt:161)
  at com.revenuecat.purchases.identity.IdentityManager$logOut$2.invoke (IdentityManager.kt:158)
  at com.revenuecat.purchases.subscriberattributes.SubscriberAttributesManager$synchronizeSubscriberAttributesForAllUsers$1.invoke (SubscriberAttributesManager.kt:69)
  at com.revenuecat.purchases.subscriberattributes.SubscriberAttributesManager$synchronizeSubscriberAttributesForAllUsers$1.invoke (SubscriberAttributesManager.kt:60)
  at com.revenuecat.purchases.subscriberattributes.SubscriberAttributesManager$ObtainDeviceIdentifiersObservable.waitUntilIdle (SubscriberAttributesManager.kt:233)
  at com.revenuecat.purchases.subscriberattributes.SubscriberAttributesManager.synchronizeSubscriberAttributesForAllUsers (SubscriberAttributesManager.kt:60)
  at com.revenuecat.purchases.identity.IdentityManager.logOut (IdentityManager.kt:158)
```
Before the next release is cut, correct the opt in so we don't show
breaking changes when I finish up the video component work
…2708)

Bumps
[fastlane-plugin-revenuecat_internal](https://github.com/RevenueCat/fastlane-plugin-revenuecat_internal)
from `e555afb` to `a8770fd`.
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/RevenueCat/fastlane-plugin-revenuecat_internal/commit/a8770fd8fde3263444bc2881812b6f737b6a705b"><code>a8770fd</code></a>
nit: add only one newline when inserting changelog latest (<a
href="https://redirect.github.com/RevenueCat/fastlane-plugin-revenuecat_internal/issues/94">#94</a>)</li>
<li><a
href="https://github.com/RevenueCat/fastlane-plugin-revenuecat_internal/commit/db640e8eec35ef25db152097dd737e0b36992682"><code>db640e8</code></a>
Insert changelog of older version release in main (<a
href="https://redirect.github.com/RevenueCat/fastlane-plugin-revenuecat_internal/issues/89">#89</a>)</li>
<li>See full diff in <a
href="https://github.com/RevenueCat/fastlane-plugin-revenuecat_internal/compare/e555afbcf227f82883e26d72c4386873e41dc56d...a8770fd8fde3263444bc2881812b6f737b6a705b">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…2712)

Bumps
[fastlane-plugin-revenuecat_internal](https://github.com/RevenueCat/fastlane-plugin-revenuecat_internal)
from `a8770fd` to `3f7fffc`.
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/RevenueCat/fastlane-plugin-revenuecat_internal/commit/3f7fffc2e3111e92d2ad86e7c2644743bde69119"><code>3f7fffc</code></a>
Use <code>pr:changelog_ignore</code> for old-major-changelog update PRs
(<a
href="https://redirect.github.com/RevenueCat/fastlane-plugin-revenuecat_internal/issues/96">#96</a>)</li>
<li><a
href="https://github.com/RevenueCat/fastlane-plugin-revenuecat_internal/commit/b35cae09e9be5e5272c41f7c5d8d24f246f697ad"><code>b35cae0</code></a>
Add <code>pr:changelog_ignore</code> label (<a
href="https://redirect.github.com/RevenueCat/fastlane-plugin-revenuecat_internal/issues/95">#95</a>)</li>
<li>See full diff in <a
href="https://github.com/RevenueCat/fastlane-plugin-revenuecat_internal/compare/a8770fd8fde3263444bc2881812b6f737b6a705b...3f7fffc2e3111e92d2ad86e7c2644743bde69119">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
### Description
Added new test app to run E2E tests with the test store. I did the
minimal to make it work, so no view models, and no great navigation
system... but it's usable for automated testing. The screen/page
structure looks like:
- `MainPage` is the root/initial page, that has 2 tabs
`CustomerInfoPage` and `OfferingsPage`.
- From the `OfferingsPage`, you can navigate to `OfferingScreen`.
- From the `OfferingScreen` you can navigate to `PackageScreen`.

This is the first part to get #2711 in
### Description
This adds a new value to the `Store` enum: `TEST_STORE`. This will be
the value used when using RevenueCat's test store.
# Conflicts:
#	purchases/src/main/kotlin/com/revenuecat/purchases/EntitlementInfo.kt
### Motivation
- Add a per-view CustomerCenterListener hook so hybrid apps can override
callbacks without relying solely on the global Purchases listener.
- Ensure the non-Compose API mirrors PaywallView.
- Provide a working sample and API coverage so partners can see and
compile against the new surface.
- Needed for RevenueCat/purchases-flutter#1476
and RevenueCat/react-native-purchases#1411

### Description
- CustomerCenterView now installs an internal proxy listener, accepts an
optional listener via constructor or setter, and keeps the global
listener as the fallback.
- Updated the Paywall Tester app: the App Info screen launches a
full-screen dialog that hosts CustomerCenterView, logging/toasting to
differentiate the per-view listener from the global handler.
### Description
We don't want to use offline entitlements for the test store, since the
store in the client won't actually return any purchases, so it's kinda
useless.
…2721)

Bumps
[fastlane-plugin-revenuecat_internal](https://github.com/RevenueCat/fastlane-plugin-revenuecat_internal)
from `3f7fffc` to `25c7fb8`.
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/RevenueCat/fastlane-plugin-revenuecat_internal/commit/25c7fb83d3e6f590a7130547f75135abda9f86a2"><code>25c7fb8</code></a>
Fix make_latest release logic (<a
href="https://redirect.github.com/RevenueCat/fastlane-plugin-revenuecat_internal/issues/98">#98</a>)</li>
<li>See full diff in <a
href="https://github.com/RevenueCat/fastlane-plugin-revenuecat_internal/compare/3f7fffc2e3111e92d2ad86e7c2644743bde69119...25c7fb83d3e6f590a7130547f75135abda9f86a2">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
### Description
Adds a new error code to be used when simulating purchase errors in the
test store.
vegaro and others added 9 commits October 15, 2025 17:09
…material3 1.4.0 (#2727)

It looks like material3 1.4.0 pulled the dependency from
material-icons-core (release notes
[here](https://developer.android.com/jetpack/androidx/releases/compose-material3#compose_material3_version_14_2)).
We do depend transitively on version 1.3.0, which has the dependency,
but if your app is overriding that to 1.4.0, the app will crash with:

```
Fatal Exception: java.lang.NoClassDefFoundError: Failed resolution of: Landroidx/compose/material/icons/Icons;
       at com.revenuecat.purchases.ui.revenuecatui.customercenter.InternalCustomerCenterKt$CustomerCenterNavigationIcon$2.invoke(InternalCustomerCenter.kt:355)
```
## Description
Adds experimental APIs providing the storefront country as `Locale`
instead of as 2-letter code. This allows developers more flexibility in
handling this information.

Relates to
[SDK-4119](https://linear.app/revenuecat/issue/SDK-4119/consolidate-storefront-format-between-ios-and-android).
# Conflicts:
#	purchases/src/defaults/kotlin/com/revenuecat/purchases/Purchases.kt
…rial 1.4.0 (#2732)

#2727 fixed some Icons, but I somehow missed some other `Icons` being
used in other parts of the code. This PR adds the missing ones:

```
java.lang.NoClassDefFoundError: Failed resolution of: Landroidx/compose/material/icons/Icons$Rounded;
```
@vegaro vegaro added the pr:next_release Preparing a new release label Oct 15, 2025
@vegaro vegaro requested a review from a team October 15, 2025 15:12
@vegaro
Copy link
Copy Markdown
Member Author

vegaro commented Oct 15, 2025

Please focus on the latest commits since those change stuff.

The rest are cherry picked. I cherry picked all commits but:

The rest of the merge conflicts I got were imports, and in #2726 a conflict with the docs in Purchases.kt. I made sure yo keep the one that made sense

enum_constant @kotlinx.serialization.SerialName("rc_billing") public static final com.revenuecat.purchases.Store RC_BILLING;
enum_constant @kotlinx.serialization.SerialName("stripe") public static final com.revenuecat.purchases.Store STRIPE;
enum_constant @kotlinx.serialization.SerialName("unknown") public static final com.revenuecat.purchases.Store UNKNOWN_STORE;
@kotlinx.serialization.Serializable(with=StoreSerializer::class) public enum Store {
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 am a bit concerned about this change 🤔

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.

This was changed to use a custom serializer in order to fallback to unknown_store if an unknown value was used for CustomerCenter (or any place where we were deserializing the Store. Do you have a particular concern about this one?

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.

just that I didn't understand it, so I wasn't sure if it was a breaking change

@emerge-tools
Copy link
Copy Markdown

emerge-tools Bot commented Oct 15, 2025

1 build increased size

Name Version Download Change Install Change Approval
TestPurchasesUIAndroidCompatibility
com.revenuecat.testpurchasesuiandroidcompatibility
1.0 (1) 65.0 MB ⬆️ 267.0 kB (0.41%) 106.4 MB ⬆️ 779.5 kB (0.74%) N/A

TestPurchasesUIAndroidCompatibility 1.0 (1)
com.revenuecat.testpurchasesuiandroidcompatibility

⚖️ Compare build
⏱️ Analyze build performance

Total install size change: ⬆️ 779.5 kB (0.74%)
Total download size change: ⬆️ 267.0 kB (0.41%)

Largest size changes

Item Install Size Change Download Size Change
📝 com.revenuecat.purchases.virtualcurrencies.VirtualCurrencyManager ⬆️ 10.6 kB ⬆️ 3.5 kB
📝 com.revenuecat.purchases.common.SharedPreferencesManager ⬆️ 9.6 kB ⬆️ 3.2 kB
📝 kotlin.text.HexExtensionsKt ⬆️ 6.9 kB ⬆️ 2.5 kB
com.revenuecat.purchases.common.caching.DeviceCache ⬆️ 7.5 kB ⬆️ 2.4 kB
📝 com.revenuecat.purchases.ui.revenuecatui.components.video.VideoCo... ⬆️ 5.9 kB ⬆️ 1.9 kB
View Treemap

Image of diff


🛸 Powered by Emerge Tools

Comment trigger: Size diff threshold of 100.00kB exceeded

@emerge-tools
Copy link
Copy Markdown

emerge-tools Bot commented Oct 15, 2025

📸 Snapshot Test

251 modified, 32 added, 397 unchanged

Name Added Removed Modified Renamed Unchanged Errored Approval
TestPurchasesUIAndroidCompatibility
com.revenuecat.testpurchasesuiandroidcompatibility
32 0 137 0 260 0 ✅ Approved
TestPurchasesUIAndroidCompatibility Paparazzi
com.revenuecat.testpurchasesuiandroidcompatibility.paparazzi
0 0 114 0 137 0 ✅ Approved

🛸 Powered by Emerge Tools

Comment thread test-apps/e2etests/build.gradle.kts
@Serializable
@SerialName("video")
@Immutable
@OptIn(InternalSerializationApi::class)
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.

Hmm why is this one needed? We need to be careful before using APIs like this, since they could (more easily) change in a patch/minor of kotlin and cause breaking changes.

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.

I guess we're using something that became stable in our current major's serialization version? If so it might be okish...

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.

Yes exactly it's the opposite, it became stable in 9 but it requieres the optin in 8

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.

This is me just being new to SDK development and not a pro in Android… Sorry 😆

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.

This is good @JZDesign! When you built it it wasn't experimental anymore so it's all good. Version 8 of android uses an earlier version of kotlin and serialization, that's why this needs the annotation

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.

But what is the thing here that's using an InternalSerializationApi?

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.

That's a good question! I will check

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.

This is very strange. It looks like just adding @Serializable triggers the OptIn warning

Screenshot 2025-10-15 at 19 26 30

Literally everything annotated with @Serializable has this warning. I think I only noticed for these two files because they had the Immutable annotation as well

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.

Ok... I went to version 8.22.0 I was still seeing it. So then I found Kotlin/kotlinx.serialization#2844 and https://youtrack.jetbrains.com/issue/KTIJ-31549

I think it's something on my AS version

@vegaro
Copy link
Copy Markdown
Member Author

vegaro commented Oct 15, 2025

I had messes up a merge that I fixed in 45b5965

Just to double check I ran the whole process again (cherry picking the 37 commits) and made sure there was no difference vs this branch :)

Copy link
Copy Markdown
Contributor

@tonidero tonidero left a comment

Choose a reason for hiding this comment

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

🚢

@codecov
Copy link
Copy Markdown

codecov Bot commented Oct 16, 2025

Codecov Report

❌ Patch coverage is 68.07980% with 128 lines in your changes missing coverage. Please review.
✅ Project coverage is 78.63%. Comparing base (e0bae0c) to head (327a04a).
⚠️ Report is 1 commits behind head on previous-major.

Files with missing lines Patch % Lines
...at/purchases/paywalls/components/VideoComponent.kt 24.28% 52 Missing and 1 partial ⚠️
...ecat/purchases/utils/OfferingVideoPredownloader.kt 0.00% 16 Missing ⚠️
...enuecat/purchases/storage/DefaultFileRepository.kt 87.70% 10 Missing and 5 partials ⚠️
...purchases/utils/PaywallComponentFilterExtension.kt 62.06% 0 Missing and 11 partials ⚠️
...rchases/customercenter/CustomerCenterConfigData.kt 43.75% 9 Missing ⚠️
...chases/paywalls/components/properties/VideoUrls.kt 64.70% 4 Missing and 2 partials ⚠️
...n/com/revenuecat/purchases/coroutinesExtensions.kt 0.00% 5 Missing ⚠️
...otlin/com/revenuecat/purchases/PurchasesFactory.kt 25.00% 1 Missing and 2 partials ⚠️
...cat/purchases/paywalls/components/TabsComponent.kt 60.00% 2 Missing ⚠️
...rchases/paywalls/components/common/Localization.kt 0.00% 2 Missing ⚠️
... and 5 more
Additional details and impacted files
@@                Coverage Diff                 @@
##           previous-major    #2739      +/-   ##
==================================================
- Coverage           78.86%   78.63%   -0.23%     
==================================================
  Files                 308      315       +7     
  Lines               11382    11716     +334     
  Branches             1610     1645      +35     
==================================================
+ Hits                 8976     9213     +237     
- Misses               1711     1803      +92     
- Partials              695      700       +5     

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

@vegaro vegaro merged commit 135bc66 into previous-major Oct 16, 2025
34 checks passed
@vegaro vegaro deleted the release/8.25.0 branch October 16, 2025 11:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr:next_release Preparing a new release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants