Reputation: 15
I have made the following BillingManager class to handle the Google Play Billing Library for my Android app:
public class BillingManager {
private List<ProductDetails> productDetailsList;
private PurchasesUpdatedListener purchasesUpdatedListener = (billingResult, purchases) -> {
// To be implemented in a later section.
};
private BillingClient billingClient = BillingClient.newBuilder(MyApplication.getAppContext())
.setListener(purchasesUpdatedListener)
.enablePendingPurchases(PendingPurchasesParams.newBuilder().enableOneTimeProducts().build())
.build();
public void establishConnection() {
billingClient.startConnection(new BillingClientStateListener() {
@Override
public void onBillingSetupFinished(BillingResult billingResult) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
Log.i("BillingManager", "Connected to Google Play.");
getProductDetails();
}
}
@Override
public void onBillingServiceDisconnected() {
Log.i("BillingManager", "Disconnected from Google Play.");
establishConnection();
}
});
}
private void getProductDetails() {
QueryProductDetailsParams queryProductDetailsParams =
QueryProductDetailsParams.newBuilder()
.setProductList(List.of(
QueryProductDetailsParams.Product.newBuilder()
.setProductId("full_version")
.setProductType(BillingClient.ProductType.INAPP)
.build()))
.build();
billingClient.queryProductDetailsAsync(
queryProductDetailsParams,
(billingResult, productDetailsList) -> {
this.productDetailsList = productDetailsList;
}
);
}
public List<ProductDetails> getProductDetailsList() {
return productDetailsList;
}
public void launchPurchaseFlow(Activity activity, ProductDetails productDetails) {
List<BillingFlowParams.ProductDetailsParams> productDetailsParamsList =
List.of(
BillingFlowParams.ProductDetailsParams.newBuilder()
// retrieve a value for "productDetails" by calling queryProductDetailsAsync()
.setProductDetails(productDetails)
.build()
);
BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder()
.setProductDetailsParamsList(productDetailsParamsList)
.build();
// Launch the billing flow
BillingResult billingResult = billingClient.launchBillingFlow(activity, billingFlowParams);
}
}
And this is an example of how I would want to use it in one of my FragmentDialog class:
BillingManager billingManager = new BillingManager();
billingManager.establishConnection();
MaterialButton button = view.findViewById(R.id.button);
// This causes an outOfBounds exception
button.setText(billingManager.getProductDetailsList().get(0).getOneTimePurchaseOfferDetails().getFormattedPrice());
button.setOnClickListener(v -> {
billingManager.launchPurchaseFlow(getActivity(), billingManager.getProductDetailsList().get(0));
});
}
Question:
I'm encountering an IndexOutOfBoundsException when trying to set the text of a button with a product price retrieved from the Google Play Billing Library. The line causing the issue is:
button.setText(billingManager.getProductDetailsList().get(0).getOneTimePurchaseOfferDetails().getFormattedPrice());
The problem stems from the asynchronous establishConnection and getProductDetails methods within my BillingManager class. When the fragment starts and attempts to access the product details to set the button's text, these operations haven't completed yet, resulting in an empty or uninitialized list.
How can I solve this and ensure that the button's text is set only after both the connection to Google Play Billing has been established and the product details have been successfully retrieved?
Upvotes: 0
Views: 28
Reputation: 3566
You can create an interface BillingStatusListener
interface BillingStatusListener {
void onProductListFetched(List<YourType> productList);
void onBillingSetupFinishedSuccessfully();
}
Make your FragmentDialog
implement this interface
class FragmentDialog implements BillingStatusListener {
@Override
void onProductListFetched(List<YourType> productList) {
// Setup your button text here
// You are sure to have data here, and no Null Pointer exceptions
}
}
Lastly, make your BillingManager
accept an implementation of the interface above and use it as required
public class BillingManager {
private List<ProductDetails> productDetailsList;
private PurchasesUpdatedListener purchasesUpdatedListener = (billingResult, purchases) -> {
// To be implemented in a later section.
};
private BillingStatusListener billingStatusListener; // Initialise this somewhere with the FragmentDialog class as the implementation
private BillingClient billingClient = BillingClient.newBuilder(MyApplication.getAppContext())
.setListener(purchasesUpdatedListener)
.enablePendingPurchases(PendingPurchasesParams.newBuilder().enableOneTimeProducts().build())
.build();
public void establishConnection() {
billingClient.startConnection(new BillingClientStateListener() {
@Override
public void onBillingSetupFinished(BillingResult billingResult) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
Log.i("BillingManager", "Connected to Google Play.");
getProductDetails();
}
}
@Override
public void onBillingServiceDisconnected() {
Log.i("BillingManager", "Disconnected from Google Play.");
establishConnection();
}
});
}
private void getProductDetails() {
QueryProductDetailsParams queryProductDetailsParams =
QueryProductDetailsParams.newBuilder()
.setProductList(List.of(
QueryProductDetailsParams.Product.newBuilder()
.setProductId("full_version")
.setProductType(BillingClient.ProductType.INAPP)
.build()))
.build();
billingClient.queryProductDetailsAsync(
queryProductDetailsParams,
(billingResult, productDetailsList) -> {
this.productDetailsList = productDetailsList;
// you will surely have data here
// so let's notify the interface
billingStatusListener.onProductListFetched(productDetailList);
}
);
}
public List<ProductDetails> getProductDetailsList() {
return productDetailsList;
}
public void launchPurchaseFlow(Activity activity, ProductDetails productDetails) {
List<BillingFlowParams.ProductDetailsParams> productDetailsParamsList =
List.of(
BillingFlowParams.ProductDetailsParams.newBuilder()
// retrieve a value for "productDetails" by calling queryProductDetailsAsync()
.setProductDetails(productDetails)
.build()
);
BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder()
.setProductDetailsParamsList(productDetailsParamsList)
.build();
// Launch the billing flow
BillingResult billingResult = billingClient.launchBillingFlow(activity, billingFlowParams);
}
}
Upvotes: 1