Skip to content

Feature/decompose billing ktx#5

Open
oooRikOooo wants to merge 6 commits intodevelopfrom
feature/decompose-billing-ktx
Open

Feature/decompose billing ktx#5
oooRikOooo wants to merge 6 commits intodevelopfrom
feature/decompose-billing-ktx

Conversation

@oooRikOooo
Copy link
Copy Markdown
Contributor

@oooRikOooo oooRikOooo commented Feb 13, 2026

Decompose monolithic BillingKtxImpl (connection + queries + purchases + UI flows in one class) into single-responsibility components:

  • BillingConnection / BillingConnectionImpl — connection lifecycle (connect(), observeUpdates(), withConnectedClient())
  • BillingApi — data operations (getPurchases, getProductDetails, consumeProduct, acknowledge, getBillingConfig, isFeatureSupported)
  • BillingFlowLauncher — Android UI operations that require Activity (launchFlow, showInappMessages)

BillingKtx is now a concrete facade class (was an interface) that delegates to internal components and provides a simplified constructor — consumers no longer need to create BillingKtxFactory directly.

Also bumps Gradle 8.11.1, Kotlin 2.2.0, AGP 8.9.1, and other dependencies.

The monolithic `BillingKtx` class has been split into three distinct components to improve separation of concerns:

-   `BillingConnection`: Manages the connection to the `BillingClient` and observes purchase updates.
-   `BillingRepository`: Handles data-related operations like querying purchases, product details, and acknowledging/consuming products.
-   `BillingFlowLauncher`: Manages UI-related tasks such as launching the billing flow and showing in-app messages.

The sample app has been updated to use these new components.
@oooRikOooo oooRikOooo self-assigned this Feb 13, 2026
@gemini-code-assist
Copy link
Copy Markdown

Summary of Changes

Hello @oooRikOooo, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly refactors the billing module by decomposing a large, monolithic class into smaller, more focused components. This change improves the architecture by adhering to the single-responsibility principle, making the codebase more maintainable, testable, and easier to understand. It also updates core dependencies to ensure compatibility and leverage the latest features and improvements.

Highlights

  • Code Decomposition: The monolithic BillingKtxImpl class has been refactored into three distinct, single-responsibility components: BillingConnection (for connection lifecycle), BillingRepository (for data operations), and BillingFlowLauncher (for Android UI operations).
  • API Changes: The BillingKtx facade has been removed, requiring consumers to depend on specific interfaces tailored to their needs, promoting better modularity and dependency management.
  • Dependency Updates: Several Android and Kotlin dependencies, along with the Gradle wrapper version, have been updated to their latest stable releases.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • app/src/main/java/com/appsci/billingktx/sample/MainActivity.kt
    • Updated imports to reflect the new billing component structure.
    • Modified the initialization of billing services to use BillingConnectionImpl, BillingRepositoryImpl, and BillingFlowLauncherImpl.
    • Migrated calls from the old billingKtx instance to the appropriate new service instances for connection, data operations, and UI flows.
  • billingKtx/build.gradle.kts
    • Updated the billing-ktx library version from 1.0.0 to 1.1.0-RC1.
  • billingKtx/src/main/java/com/appsci/billingktx/client/BillingKtx.kt
    • Removed the BillingKtx interface and its BillingKtxImpl implementation, as their functionalities are now distributed among new classes.
  • billingKtx/src/main/java/com/appsci/billingktx/client/connection/BillingConnection.kt
    • Added a new interface BillingConnection to define the contract for managing billing client connection lifecycle and observing purchase updates.
  • billingKtx/src/main/java/com/appsci/billingktx/client/connection/BillingConnectionImpl.kt
    • Added the BillingConnectionImpl class, providing the concrete implementation for BillingConnection, handling the billing client connection and purchase update listeners.
  • billingKtx/src/main/java/com/appsci/billingktx/client/repository/BillingRepository.kt
    • Added a new interface BillingRepository to define the contract for billing data operations such as querying purchases, product details, and handling consumption/acknowledgement.
  • billingKtx/src/main/java/com/appsci/billingktx/client/repository/BillingRepositoryImpl.kt
    • Added the BillingRepositoryImpl class, providing the concrete implementation for BillingRepository, encapsulating all data-related billing logic.
  • billingKtx/src/main/java/com/appsci/billingktx/client/ui/BillingFlowLauncher.kt
    • Added a new interface BillingFlowLauncher to define the contract for launching Android UI-specific billing flows, such as purchase flows and in-app messages.
  • billingKtx/src/main/java/com/appsci/billingktx/client/ui/BillingFlowLauncherImpl.kt
    • Added the BillingFlowLauncherImpl class, providing the concrete implementation for BillingFlowLauncher, handling the launching of billing UI elements.
  • billingKtx/src/main/java/com/appsci/billingktx/lifecycle/BillingKtxLifecycle.kt
    • Updated the keepConnection extension function to now operate on the new BillingConnection interface instead of the removed BillingKtx.
  • gradle/libs.versions.toml
    • Updated android-gp from 8.2.2 to 8.9.1.
    • Updated kotlin from 2.0.21 to 2.2.0.
    • Updated kotlin-ksp from 2.0.21-1.0.26 to 2.2.0-2.0.2.
    • Updated android-core from 1.15.0 to 1.16.0.
    • Updated android-appcompat from 1.7.0 to 1.7.1.
    • Updated android-lifecycle from 2.8.7 to 2.9.1.
  • gradle/wrapper/gradle-wrapper.properties
    • Updated the Gradle distribution URL to gradle-8.11.1-bin.zip from gradle-8.9-bin.zip.
Activity
  • No specific activity (comments, reviews, or progress updates) has been recorded for this pull request yet.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request does an excellent job of decomposing the monolithic BillingKtxImpl into smaller, single-responsibility classes: BillingConnection, BillingRepository, and BillingFlowLauncher. This significantly improves the library's design, making it more modular and maintainable. My review focuses on further enhancing this new design by programming to interfaces rather than concrete implementations. This will improve decoupling and make the components easier to test. I've also included a suggestion to improve consistency within the new classes. Overall, this is a great refactoring.

Comment on lines +34 to +36
private lateinit var connection: BillingConnectionImpl
private lateinit var repository: BillingRepositoryImpl
private lateinit var flowLauncher: BillingFlowLauncherImpl
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

It's a good practice to declare fields using interface types (BillingConnection, BillingRepository, BillingFlowLauncher) rather than concrete implementation types. This makes the code more flexible and decoupled from specific implementations. You'll need to add the corresponding imports for the interfaces.

Suggested change
private lateinit var connection: BillingConnectionImpl
private lateinit var repository: BillingRepositoryImpl
private lateinit var flowLauncher: BillingFlowLauncherImpl
private lateinit var connection: com.appsci.billingktx.client.connection.BillingConnection
private lateinit var repository: com.appsci.billingktx.client.repository.BillingRepository
private lateinit var flowLauncher: com.appsci.billingktx.client.ui.BillingFlowLauncher

Comment on lines +9 to +11
fun connect(): Flow<BillingClient>

fun observeUpdates(): Flow<PurchasesUpdate>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

To allow BillingRepository and BillingFlowLauncher to depend on this interface (as suggested in other comments), the withConnectedClient function should be part of the public contract of BillingConnection.

    fun connect(): Flow<BillingClient>

    fun observeUpdates(): Flow<PurchasesUpdate>

    suspend fun <R> withConnectedClient(block: suspend (BillingClient) -> R): R

Comment on lines +57 to +63
internal suspend fun <R> withConnectedClient(
block: suspend (BillingClient) -> R,
): R {
return connectionFlow.map {
block(it)
}.first()
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

With withConnectedClient moved to the BillingConnection interface as suggested elsewhere, this implementation should be marked with override.

Suggested change
internal suspend fun <R> withConnectedClient(
block: suspend (BillingClient) -> R,
): R {
return connectionFlow.map {
block(it)
}.first()
}
override suspend fun <R> withConnectedClient(
block: suspend (BillingClient) -> R,
): R {
return connectionFlow.map {
block(it)
}.first()
}

Comment on lines +65 to +67
internal fun isSuccess(@BillingClient.BillingResponseCode responseCode: Int): Boolean {
return responseCode == BillingClient.BillingResponseCode.OK
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

This utility function is used by other components (BillingRepositoryImpl, BillingFlowLauncherImpl). To avoid coupling those components to BillingConnectionImpl, it would be better to move this to a top-level internal function in a shared utility file (e.g., BillingUtils.kt). After moving it, you can remove it from this class.

import kotlin.coroutines.resumeWithException

class BillingRepositoryImpl(
private val connection: BillingConnectionImpl,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

To follow the Dependency Inversion Principle and improve testability, this class should depend on the BillingConnection interface instead of the BillingConnectionImpl implementation. This change will require exposing withConnectedClient on the BillingConnection interface and moving the isSuccess helper to a shared location. I've added separate comments with suggestions on how to achieve this.

Suggested change
private val connection: BillingConnectionImpl,
private val connection: com.appsci.billingktx.client.connection.BillingConnection,

override suspend fun isFeatureSupported(@FeatureType feature: String): Boolean {
return connection.withConnectedClient {
val result = it.isFeatureSupported(feature)
result.responseCode == BillingClient.BillingResponseCode.OK
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

For consistency, you should use a helper function to check for success here, just like in other methods of this class. Assuming isSuccess is moved to a top-level utility function as suggested in another comment, you can call it directly.

Suggested change
result.responseCode == BillingClient.BillingResponseCode.OK
isSuccess(result.responseCode)

import kotlin.coroutines.resumeWithException

class BillingFlowLauncherImpl(
private val connection: BillingConnectionImpl,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Similar to BillingRepositoryImpl, this class should depend on the BillingConnection interface for better decoupling and testability. This promotes loose coupling and makes the class easier to test in isolation.

Suggested change
private val connection: BillingConnectionImpl,
private val connection: com.appsci.billingktx.client.connection.BillingConnection,

Introduced `BillingKtx` as a unified entry point to the library, simplifying its public API. This facade encapsulates the previously separate `BillingRepository`, `BillingFlowLauncher`, and `BillingConnection` components.

Internal implementation details, including `BillingApi`, `BillingFlowLauncher`, and `BillingConnectionImpl`, are now marked as `internal` to hide them from consumers.

Additionally, the Google Billing dependency is now exposed as an `api` dependency to allow consumers to use its types directly.
import kotlin.coroutines.resumeWithException

internal class BillingFlowLauncher(
private val connection: BillingConnectionImpl,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

why impl class?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants