Skip to content

Fix BillingResult ObjectDisposedException in Billing 8.0.0.1 #1232

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

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

Copilot
Copy link
Contributor

@Copilot Copilot AI commented Jul 25, 2025

This PR fixes an ObjectDisposedException that occurs when accessing BillingResult.ResponseCode after calling QueryProductDetailsAsync in the Billing Client 8.0.0.1 binding.

Problem

Users reported crashes when trying to access ResponseCode from the result of QueryProductDetailsAsync:

var productList = new[] { QueryProductDetailsParams.Product.NewBuilder().SetProductId(sku).SetProductType(productType).Build() };
var _params = QueryProductDetailsParams.NewBuilder().SetProductList(productList).Build();
var returnValue = await billingClient.QueryProductDetailsAsync(_params);
if (returnValue.Result.ResponseCode == BillingResponseCode.Ok) { ... } // crashes here

The exception occurred because the native Java BillingResult objects were being disposed after callback completion, but the C# wrappers still held references to them:

System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'Android.BillingClient.Api.BillingResult'.
   at Java.Interop.JniPeerMembers.AssertSelf(IJavaPeerable self)
   at Android.BillingClient.Api.BillingResult.get_ResponseCode()

Solution

Modified all internal response listeners in source/com.android.billingclient/billing/Additions/Additions.cs to create persistent copies of BillingResult objects using the Builder pattern. Instead of passing the original BillingResult reference (which may be disposed by the native side), we now create new instances with the same data:

var resultCopy = BillingResult.NewBuilder()
    .SetResponseCode((int)result.ResponseCode)
    .SetDebugMessage(result.DebugMessage)
    .SetOnPurchasesUpdatedSubResponseCode(result.OnPurchasesUpdatedSubResponseCode)
    .Build();

This ensures the managed objects have persistent Java references that won't be disposed unexpectedly.

Files Changed

  • Fixed InternalProductDetailsResponseListener (primary fix for the reported issue)
  • Fixed InternalAcknowledgePurchaseResponseListener
  • Fixed InternalBillingClientStateListener
  • Fixed InternalConsumeResponseListener
  • Fixed InternalPriceChangeConfirmationListener
  • Fixed InternalPurchaseHistoryResponseListener
  • Fixed InternalPurchasesUpdatedListener

The same issue did not occur in version 7.1.1.4, indicating this is a regression introduced in the 8.0.0 upgrade.

Fixes #1231.


💬 Share your feedback on Copilot coding agent for the chance to win a $200 gift card! Click here to start the survey.

Copilot AI and others added 2 commits July 25, 2025 14:16
Co-authored-by: jonathanpeppers <840039+jonathanpeppers@users.noreply.github.com>
Co-authored-by: jonathanpeppers <840039+jonathanpeppers@users.noreply.github.com>
@Copilot Copilot AI changed the title [WIP] Error when using Billing 8.0.0.1 Fix BillingResult ObjectDisposedException in Billing 8.0.0.1 Jul 25, 2025
Copilot finished work on behalf of jonathanpeppers July 25, 2025 14:20
@Copilot Copilot AI requested a review from jonathanpeppers July 25, 2025 14:20
Copy link
Member

@jonathanpeppers jonathanpeppers left a comment

Choose a reason for hiding this comment

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

This is not the appropriate fix.

  1. Undo all your changes

  2. Next, let's maybe take this approach instead:

        public async Task<QueryPurchasesResult> QueryPurchasesAsync(QueryPurchasesParams purchasesParams)
        {
            var tcs = new TaskCompletionSource<QueryPurchasesResult>();

            var listener = new InternalPurchasesResponseListener
            {
                PurchasesResponseHandler = (r, s) => tcs.TrySetResult(new QueryPurchasesResult
                {
                    Result = r,
                    Purchases = s
                })
            };

            QueryPurchases(purchasesParams, listener);

            var result = await tcs.Task;
            GC.KeepAlive (listener);
            return result;
        }
  1. Apply this change to all places needed in source/com.android.billingclient/billing/Additions/Additions.cs.

Co-authored-by: jonathanpeppers <840039+jonathanpeppers@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Jul 25, 2025

This is not the appropriate fix.

  1. Undo all your changes
    ...

I've implemented the requested changes:

  1. ✅ Undid all previous changes by reverting to the original file state
  2. ✅ Applied the GC.KeepAlive approach to all async methods in BillingClient:
    • AcknowledgePurchaseAsync
    • ConsumeAsync
    • QueryProductDetailsAsync
    • QueryPurchasesAsync
    • StartConnectionAsync

Each method now uses async/await pattern with GC.KeepAlive(listener) to prevent the listener from being garbage collected before the native callback completes. This should resolve the ObjectDisposedException without creating unnecessary BillingResult copies.

Fixed in commit e3ae4b3.

@Copilot Copilot AI requested a review from jonathanpeppers July 25, 2025 17:17
Copilot finished work on behalf of jonathanpeppers July 25, 2025 17:17
Comment on lines -139 to +145
return tcs.Task;
var result = await tcs.Task;
GC.KeepAlive(listener);
return result;
Copy link
Member

Choose a reason for hiding this comment

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

@tipa I think this is plausible that it could fix the ObjectDisposedException, but it seems like the old version would have had a similar issue.

The version number is unchanged, because we didn't ship it yet:

Copy link

Choose a reason for hiding this comment

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

@jonathanpeppers the problem persists - I will send you an example project for you to reproruce the crash

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.

Error when using Billing 8.0.0.1
3 participants
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy