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 8 commits into
base: main
Choose a base branch
from
63 changes: 46 additions & 17 deletions source/com.android.billingclient/billing/Additions/Additions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,18 @@

namespace Android.BillingClient.Api
{
public partial class BillingResult
{
internal BillingResult Clone()
{
return BillingResult.NewBuilder()
.SetResponseCode((int)ResponseCode)
.SetDebugMessage(DebugMessage)
.SetOnPurchasesUpdatedSubResponseCode(OnPurchasesUpdatedSubResponseCode)
.Build();
}
}

public class ConsumeResult
{
public BillingResult BillingResult { get; set; }
Expand Down Expand Up @@ -33,12 +45,7 @@ public QueryProductDetailsResult() { }

public BillingResult Result { get; set; }

[Obsolete ($"Use {nameof(ProductDetailsList)} instead")]
public IList<ProductDetails> ProductDetails
{
get => ProductDetailsList;
set { /* Obsolete property setter does nothing */ }
}
public IList<ProductDetails> ProductDetails { get; set; }
}

public class QueryPurchasesResult
Expand Down Expand Up @@ -129,9 +136,14 @@ public Task<QueryProductDetailsResult> QueryProductDetailsAsync(QueryProductDeta
{
var tcs = new TaskCompletionSource<QueryProductDetailsResult>();

// NOTE: this creates a new QueryProductDetailsResult to avoid ObjectDisposedException
var listener = new InternalProductDetailsResponseListener
{
ProductDetailsResponseHandler = (r, queryResult) => tcs.TrySetResult(queryResult)
ProductDetailsResponseHandler = (r, s) => tcs.TrySetResult(new QueryProductDetailsResult
{
Result = r,
ProductDetails = s
})
};

QueryProductDetails(productDetailsParams, listener);
Expand Down Expand Up @@ -196,7 +208,10 @@ internal class InternalAcknowledgePurchaseResponseListener : Java.Lang.Object, I
public Action<BillingResult> AcknowledgePurchaseResponseHandler { get; set; }

public void OnAcknowledgePurchaseResponse(BillingResult result)
=> AcknowledgePurchaseResponseHandler?.Invoke(result);
{
// Create a copy of the BillingResult to ensure it stays alive after the callback
AcknowledgePurchaseResponseHandler?.Invoke(result.Clone());
}
}

internal class InternalBillingClientStateListener : Java.Lang.Object, IBillingClientStateListener
Expand All @@ -209,36 +224,51 @@ public void OnBillingServiceDisconnected()
=> BillingServiceDisconnectedHandler?.Invoke();

public void OnBillingSetupFinished(BillingResult result)
=> BillingSetupFinishedHandler?.Invoke(result);
{
// Create a copy of the BillingResult to ensure it stays alive after the callback
BillingSetupFinishedHandler?.Invoke(result.Clone());
}
}

internal class InternalConsumeResponseListener : Java.Lang.Object, IConsumeResponseListener
{
public Action<BillingResult, string> ConsumeResponseHandler { get; set; }
public void OnConsumeResponse(BillingResult result, string str)
=> ConsumeResponseHandler?.Invoke(result, str);
{
// Create a copy of the BillingResult to ensure it stays alive after the callback
ConsumeResponseHandler?.Invoke(result.Clone(), str);
}
}

internal class InternalPriceChangeConfirmationListener : Java.Lang.Object //, IPriceChangeConfirmationListener
{
public Action<BillingResult> PriceChangeConfirmationHandler { get; set; }
public void OnPriceChangeConfirmationResult(BillingResult result)
=> PriceChangeConfirmationHandler?.Invoke(result);
{
// Create a copy of the BillingResult to ensure it stays alive after the callback
PriceChangeConfirmationHandler?.Invoke(result.Clone());
}
}

internal class InternalPurchaseHistoryResponseListener : Java.Lang.Object, IPurchaseHistoryResponseListener
{
public Action<BillingResult, IList<PurchaseHistoryRecord>> PurchaseHistoryResponseHandler { get; set; }

public void OnPurchaseHistoryResponse(BillingResult result, IList<PurchaseHistoryRecord> history)
=> PurchaseHistoryResponseHandler?.Invoke(result, history);
{
// Create a copy of the BillingResult to ensure it stays alive after the callback
PurchaseHistoryResponseHandler?.Invoke(result.Clone(), history);
}
}

internal class InternalPurchasesUpdatedListener : Java.Lang.Object, IPurchasesUpdatedListener
{
public Action<BillingResult, IList<Purchase>> PurchasesUpdatedHandler { get; set; }
public void OnPurchasesUpdated(BillingResult result, IList<Purchase> purchases)
=> PurchasesUpdatedHandler?.Invoke(result, purchases);
{
// Create a copy of the BillingResult to ensure it stays alive after the callback
PurchasesUpdatedHandler?.Invoke(result.Clone(), purchases);
}
}

[Obsolete("Use QueryProductDetailsAsync(QueryProductDetailsParams) instead")]
Expand All @@ -252,13 +282,12 @@ public void OnSkuDetailsResponse(BillingResult result, IList<SkuDetails> skuDeta

internal class InternalProductDetailsResponseListener : Java.Lang.Object, IProductDetailsResponseListener
{
public Action<BillingResult, QueryProductDetailsResult> ProductDetailsResponseHandler { get; set; }
public Action<BillingResult, IList<ProductDetails>> ProductDetailsResponseHandler { get; set; }

public void OnProductDetailsResponse(BillingResult result, QueryProductDetailsResult queryResult)
{
queryResult ??= new();
queryResult.Result = result;
ProductDetailsResponseHandler?.Invoke(result, queryResult);
// Create a copy of the BillingResult to ensure it stays alive after the callback
ProductDetailsResponseHandler?.Invoke(result.Clone(), queryResult?.ProductDetailsList);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,12 @@ Android.BillingClient.Api.QueryProductDetailsParams.Product.Zza() -> string!
Android.BillingClient.Api.QueryProductDetailsParams.Product.Zzb() -> string!
Android.BillingClient.Api.QueryProductDetailsParams.Zzb() -> string!
Android.BillingClient.Api.QueryProductDetailsResult
Android.BillingClient.Api.QueryProductDetailsResult.ProductDetails.get -> System.Collections.Generic.IList<Android.BillingClient.Api.ProductDetails!>!
Android.BillingClient.Api.QueryProductDetailsResult.ProductDetails.set -> void
Android.BillingClient.Api.QueryProductDetailsResult.ProductDetailsList.get -> System.Collections.Generic.IList<Android.BillingClient.Api.ProductDetails!>!
Android.BillingClient.Api.QueryProductDetailsResult.QueryProductDetailsResult() -> void
Android.BillingClient.Api.QueryProductDetailsResult.Result.get -> Android.BillingClient.Api.BillingResult!
Android.BillingClient.Api.QueryProductDetailsResult.Result.set -> void
Android.BillingClient.Api.QueryProductDetailsResult.UnfetchedProductList.get -> System.Collections.Generic.IList<Android.BillingClient.Api.UnfetchedProduct!>!
Android.BillingClient.Api.QueryPurchaseHistoryParams
Android.BillingClient.Api.QueryPurchaseHistoryParams.Builder
Expand Down