-
Notifications
You must be signed in to change notification settings - Fork 319
work-in-progress-do not review - add pbl snippets #758
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Summary of ChangesHello @kumann-dev, 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 introduces a new Android module dedicated to showcasing Google Play Billing Library functionalities. It establishes the necessary build configurations and dependencies, and provides a robust wrapper class that encapsulates a wide range of billing operations, from initializing the billing client to managing subscriptions and in-app messages. This module serves as a set of practical examples for integrating in-app purchases and subscriptions. Highlights
🧠 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. Using Gemini Code AssistThe 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
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 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
|
There was a problem hiding this 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 introduces a new playbillinglibrary module with code snippets for integrating Google Play Billing. While the PR title indicates it's a work-in-progress, there are several critical issues that should be addressed. The module is incorrectly configured as an Android application instead of a library. The BillingClientWrapper class has potential memory leaks due to incorrect handling of Activity and Context references, uses hardcoded values for product IDs and purchase tokens, and has incomplete error handling. These issues affect the correctness, security, and reusability of the billing library.
| @@ -0,0 +1,44 @@ | |||
| plugins { | |||
| alias(libs.plugins.android.application) | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| } | ||
|
|
||
| defaultConfig { | ||
| applicationId "com.example.pbl" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| private final Context context; | ||
| private final Activity activity; | ||
| private final BillingClient billingClient; | ||
|
|
||
| private List<ProductDetails> productDetailsList; | ||
| private ProductDetails productDetails; | ||
|
|
||
| public BillingClientWrapper(Context context, Activity activity) { | ||
| this.context = context; | ||
| this.activity = activity; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Storing a direct reference to an Activity or its Context in a long-lived object like this wrapper is a common source of memory leaks in Android. The Activity can't be garbage collected if the wrapper outlives it.
A safer pattern is to:
- Store the
applicationContextto avoid leaking theActivity. - Remove the
activityfield entirely. - Pass the
Activityas a parameter to the methods that require it, such aslaunchBillingFlowandshowInAppMessages.
| private final Context context; | |
| private final Activity activity; | |
| private final BillingClient billingClient; | |
| private List<ProductDetails> productDetailsList; | |
| private ProductDetails productDetails; | |
| public BillingClientWrapper(Context context, Activity activity) { | |
| this.context = context; | |
| this.activity = activity; | |
| private final Context context; | |
| private final BillingClient billingClient; | |
| private List<ProductDetails> productDetailsList; | |
| private ProductDetails productDetails; | |
| public BillingClientWrapper(Context context) { | |
| this.context = context.getApplicationContext(); |
| .setProductList( | ||
| ImmutableList.of( | ||
| QueryProductDetailsParams.Product.newBuilder() | ||
| .setProductId("product_id_example") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| // You could initiate a query here or show an error message. | ||
| return; | ||
| } | ||
| String purchaseTokenOfExistingSubscription = "purchase_token"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| billingClient.queryProductDetailsAsync( | ||
| queryProductDetailsParams, | ||
| (billingResult, fetchedProductDetailsList) -> { | ||
| if (billingResult.getResponseCode() == BillingResponseCode.OK && fetchedProductDetailsList != null) { | ||
| this.productDetailsList = fetchedProductDetailsList.getProductDetailsList(); | ||
| // Now that the list is populated, you can use it. | ||
| // For example, find a specific product. | ||
| if (!this.productDetailsList.isEmpty()) { | ||
| this.productDetails = this.productDetailsList.get(0); | ||
| // Any methods that require productDetails should be called from here. | ||
| } | ||
| } | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The product details are fetched asynchronously, but there's no mechanism for the caller of queryProductDetails to know when the data is ready or if an error occurred. This can lead to race conditions where other methods (e.g., changeSubscriptionPlan) try to use productDetails before it's initialized. Consider using a listener, callback, or a reactive approach (like Flow or LiveData) to expose the result of this asynchronous operation.
| // Now that the list is populated, you can use it. | ||
| // For example, find a specific product. | ||
| if (!this.productDetailsList.isEmpty()) { | ||
| this.productDetails = this.productDetailsList.get(0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Accessing the first element of the list with get(0) is unsafe because the list could be empty, which would cause an IndexOutOfBoundsException. You should always check if the list is empty before accessing elements. Furthermore, if you query for multiple products, you should iterate through the list to find the specific product you need rather than assuming it's the first one.
| billingClient.acknowledgePurchase(acknowledgePurchaseParams, (billingResult) -> { | ||
| // Acknowledgment handled. | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The result of the acknowledgePurchase call is ignored. It's crucial to check the billingResult to ensure the purchase was successfully acknowledged. If acknowledgement fails, Google will automatically refund the purchase after a few days. You should handle failures, for example by logging the error or implementing a retry mechanism.
billingClient.acknowledgePurchase(acknowledgePurchaseParams, (billingResult) -> {
if (billingResult.getResponseCode() != BillingResponseCode.OK) {
// Acknowledgment failed. Log the error and consider retrying.
}
});| <!-- | ||
| Copyright 2026 The Android Open Source Project | ||
|
|
||
| Licensed under the Apache License, Version 2.0 (the "License"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Setting android:allowBackup="true" can pose a security risk, as it allows users to back up and restore application data via adb. For an application handling sensitive information like billing, it's recommended to disable this feature unless you have a specific need and have implemented a secure backup strategy.
| Licensed under the Apache License, Version 2.0 (the "License"); | |
| android:allowBackup="false" |
| if (billingResult.getResponseCode() == BillingResponseCode.OK && billingConfig != null) { | ||
| String countryCode = billingConfig.getCountryCode(); | ||
| } else { | ||
| // TODO: Handle errors |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No description provided.