Tennis Heal
Tennis Heal

Reputation: 15

Display Product data from BillingClient

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

Answers (1)

gtxtreme
gtxtreme

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

Related Questions