Skip to content

[PM-33510] feat: Add Play Billing Library dependency and PlayBillingManager#6680

Open
SaintPatrck wants to merge 1 commit intomainfrom
premium-upgrade/PM-33510-billing-manager
Open

[PM-33510] feat: Add Play Billing Library dependency and PlayBillingManager#6680
SaintPatrck wants to merge 1 commit intomainfrom
premium-upgrade/PM-33510-billing-manager

Conversation

@SaintPatrck
Copy link
Contributor

🎟️ Tracking

https://bitwarden.atlassian.net/browse/PM-33510

📔 Objective

Introduces a flavor-specific PlayBillingManager wrapping the Google Play Billing Library to determine whether in-app billing is available on the current device.

  • Adds com.android.billingclient:billing v8.3.0 as a standardImplementation dependency
  • Defines PlayBillingManager interface with isInAppBillingSupportedFlow: StateFlow<Boolean>
  • Standard flavor implementation uses BillingClient.getBillingConfigAsync() with a connect-per-call lifecycle to probe billing availability
  • F-Droid flavor always emits true (F-Droid users are eligible for the upgrade flow)
  • Wires PlayBillingManager into BillingRepositoryImpl, delegating isInAppBillingSupportedFlow through the repository layer
  • Updates BillingModule to provide PlayBillingManager as a singleton

@github-actions github-actions bot added app:password-manager Bitwarden Password Manager app context app:authenticator Bitwarden Authenticator app context t:deps Change Type - Dependencies t:feature Change Type - Feature Development labels Mar 16, 2026
@SaintPatrck SaintPatrck removed the t:deps Change Type - Dependencies label Mar 16, 2026
@github-actions
Copy link
Contributor

github-actions bot commented Mar 16, 2026

Logo
Checkmarx One – Scan Summary & Detailsc30d3b05-631d-4ddd-aceb-c0cbaca77fcf

Great job! No new security vulnerabilities introduced in this pull request

@codecov
Copy link

codecov bot commented Mar 16, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 85.73%. Comparing base (2b9e142) to head (e0d8808).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #6680      +/-   ##
==========================================
+ Coverage   85.57%   85.73%   +0.16%     
==========================================
  Files         885      937      +52     
  Lines       59189    59691     +502     
  Branches     8406     8426      +20     
==========================================
+ Hits        50650    51176     +526     
+ Misses       5606     5567      -39     
- Partials     2933     2948      +15     
Flag Coverage Δ
app-data 17.93% <100.00%> (+0.52%) ⬆️
app-ui-auth-tools 21.13% <0.00%> (+0.12%) ⬆️
app-ui-platform 15.73% <0.00%> (+0.07%) ⬆️
app-ui-vault 26.50% <0.00%> (-0.02%) ⬇️
authenticator 6.60% <0.00%> (-0.01%) ⬇️
lib-core-network-bridge 4.28% <0.00%> (-0.01%) ⬇️
lib-data-ui 0.94% <0.00%> (-0.02%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

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

@SaintPatrck SaintPatrck force-pushed the premium-upgrade/PM-33510-billing-manager branch from 81de98d to 4e6c5a7 Compare March 16, 2026 19:34
@github-actions github-actions bot added the t:deps Change Type - Dependencies label Mar 16, 2026
@SaintPatrck SaintPatrck added ai-review Request a Claude code review and removed t:deps Change Type - Dependencies labels Mar 16, 2026
@SaintPatrck SaintPatrck force-pushed the premium-upgrade/PM-33509-billing-repository branch 3 times, most recently from b8796c3 to 2aaac13 Compare March 18, 2026 17:25
@SaintPatrck SaintPatrck force-pushed the premium-upgrade/PM-33510-billing-manager branch from 4e6c5a7 to 8fd77ba Compare March 18, 2026 18:51
@github-actions github-actions bot added the t:deps Change Type - Dependencies label Mar 18, 2026
@claude
Copy link
Contributor

claude bot commented Mar 18, 2026

Claude finished @SaintPatrck's task in 2m 22s —— View job


Code Review: PlayBillingManager (PR #6680)

  • Gather context and read changed files
  • Perform code review analysis
  • Post review findings

Summary

No new issues found — this is a clean, well-structured PR.

The existing review feedback from @david-livefront has already identified the actionable items (narrowing the try-catch scope, calling continuation.cancel() on disconnect, removing unnecessary suppression, and the useConnection helper refactor), and @SaintPatrck has acknowledged those. No additional concerns beyond what's already been raised.

Key observations:

  • Standard flavor (PlayBillingManagerImpl): Connect-per-call BillingClient lifecycle with useConnection helper and getBillingConfigAsync() to probe billing support. Proper finally teardown and suspendCancellableCoroutine usage with isActive guards.
  • F-Droid flavor: Simple stub always emitting true — appropriate since F-Droid users should be eligible for the upgrade flow.
  • Interface + DI: PlayBillingManager follows the project's Interface/...Impl convention, wired via BillingModule as a singleton.
  • Repository integration: BillingRepositoryImpl cleanly delegates isInAppBillingSupportedFlow.
  • Tests: Repository test covers the delegation path with a mock PlayBillingManager. All modified and coverable lines are covered.
  • Dependency: billing v8.3.0 added as standardImplementation only, keeping F-Droid builds free of Google dependencies.

No security, performance, or architectural concerns identified.

@SaintPatrck SaintPatrck force-pushed the premium-upgrade/PM-33510-billing-manager branch from fb9683d to 10562f1 Compare March 18, 2026 19:17
@SaintPatrck SaintPatrck force-pushed the premium-upgrade/PM-33509-billing-repository branch from dd9fd33 to d692519 Compare March 18, 2026 19:17
@SaintPatrck SaintPatrck force-pushed the premium-upgrade/PM-33510-billing-manager branch from 10562f1 to b785449 Compare March 18, 2026 19:19
Base automatically changed from premium-upgrade/PM-33509-billing-repository to main March 18, 2026 19:45
@SaintPatrck SaintPatrck force-pushed the premium-upgrade/PM-33510-billing-manager branch 2 times, most recently from e71988b to 5323470 Compare March 18, 2026 19:50
@SaintPatrck SaintPatrck marked this pull request as ready for review March 18, 2026 19:51
@SaintPatrck SaintPatrck requested review from a team and david-livefront as code owners March 18, 2026 19:51
@SaintPatrck SaintPatrck removed the t:deps Change Type - Dependencies label Mar 18, 2026
.enableOneTimeProducts()
.build(),
)
.build()
Copy link
Collaborator

Choose a reason for hiding this comment

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

Would it make sense to inject this as a singleton or do we want a new BillingClient for each request?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Keeping it around is not needed. Maybe if we add more billing related functionality in the future, but right now we only need this one-off check that can be done on init.

} catch (e: IllegalStateException) {
e.asFailure()
} finally {
billingClient.endConnection()
Copy link
Collaborator

Choose a reason for hiding this comment

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

The startConnection and endConnection calls make me feel like we don't want to inject it 😄

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agreed. That's another reason I chose to go with creating a new instance every time.

return BillingException(
"Connection failed: ${connectionResult.debugMessage}",
)
.asFailure()
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is there a specific part of this code that throws?

It would be nice if we could pull some of this stuff out of the try-catch block.

Copy link
Contributor Author

@SaintPatrck SaintPatrck Mar 18, 2026

Choose a reason for hiding this comment

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

A few calls do. I can isolate them.

@SaintPatrck SaintPatrck force-pushed the premium-upgrade/PM-33510-billing-manager branch from 5323470 to 1c8c1e5 Compare March 18, 2026 21:06
@github-actions github-actions bot added the t:deps Change Type - Dependencies label Mar 18, 2026
@SaintPatrck SaintPatrck force-pushed the premium-upgrade/PM-33510-billing-manager branch from 1c8c1e5 to 6acd1f5 Compare March 18, 2026 21:11
@SaintPatrck SaintPatrck removed the t:deps Change Type - Dependencies label Mar 18, 2026
.build()

return try {
val connectionResult = billingClient.connect()
Copy link
Collaborator

@david-livefront david-livefront Mar 18, 2026

Choose a reason for hiding this comment

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

What do you think of a reusable function like this?

    private suspend fun <T> BillingClient.useConnection(
        block: suspend BillingResult.() -> Result<T>,
    ): Result<T> = try {
        block(
            suspendCancellableCoroutine { continuation ->
                startConnection(
                    object : BillingClientStateListener {
                        override fun onBillingSetupFinished(
                            billingResult: BillingResult,
                        ) {
                            if (continuation.isActive) {
                                continuation.resume(billingResult)
                            }
                        }

                        override fun onBillingServiceDisconnected() {
                            // No-op: connect-per-call lifecycle, no reconnection.
                        }
                    },
                )
            },
        )
    } catch (e: IllegalStateException) {
        e.asFailure()
    } finally {
        endConnection()
    }
}

Then we can do this:

        return billingClient.useConnection {
            if (responseCode != BillingClient.BillingResponseCode.OK) {
                return@useConnection BillingException("Connection failed: $debugMessage")
                    .asFailure()
            }
            val (configResult, billingConfig) = billingClient.getBillingConfig()
            if (configResult.responseCode != BillingClient.BillingResponseCode.OK ||
                billingConfig == null
            ) {
                BillingException("Config query failed: ${configResult.debugMessage}").asFailure()
            } else {
                billingConfig.countryCode.asSuccess()
            }
        }
    }

You might even be able to move the responseCode check into the helper method

@SaintPatrck SaintPatrck force-pushed the premium-upgrade/PM-33510-billing-manager branch from 6acd1f5 to e0d8808 Compare March 18, 2026 21:41
@github-actions github-actions bot added the t:deps Change Type - Dependencies label Mar 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai-review Request a Claude code review app:authenticator Bitwarden Authenticator app context app:password-manager Bitwarden Password Manager app context t:deps Change Type - Dependencies t:feature Change Type - Feature Development

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants