10

In the old android billing implementation you would build an sku list to query products:

List<String> skuList = new ArrayList<>();
        skuList.add(SKU_POTION);
        skuList.add(SKU_SWORD);
        skuList.add(SKU_BOW);
        SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder();
        params.setSkusList(skuList).setType(BillingClient.SkuType.INAPP);

The new billing implementation is more involved, and appears to limit you to adding just one product to a query list:

ImmutableList<QueryProductDetailsParams.Product> productList = ImmutableList.from(QueryProductDetailsParams.Product.newBuilder()
                    .setProductId(SKU_POTION)
                    .setProductType(BillingClient.ProductType.INAPP)
                    .build());
    
            QueryProductDetailsParams params = QueryProductDetailsParams.newBuilder()
                    .setProductList(productList)
                    .build();
    
            billingClient.queryProductDetailsAsync(
            params,
            new ProductDetailsResponseListener() {
                public void onProductDetailsResponse(BillingResult billingResult, List<ProductDetails> productDetailsList) {
                    if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK && productDetailsList != null) {
                        for (ProductDetails skuDetails : productDetailsList) {                    
                            mProductDetailsMap.put(skuDetails.getProductId(), skuDetails);                           
                        }
                    }
                   
                }
            }
    );

It makes you build the productList for the productDetailsList for the mProductDetailsMap that's needed to start the purchase flow:

puchasestring=SKU_POTION;
initiatePurchaseFlow(mProductDetailsMap.get(puchasestring));

How would I add multiple products to the productList that begins the implementation? I don't want to have to repeat the entire code segment for each item to add to the mProductDetailsMap, which is the Primitive Pete method I'm using for now.

Androidcoder
  • 4,389
  • 5
  • 35
  • 50

2 Answers2

17

You don't actually have to use an ImmutableList. The official examples use an ImmutableList for some reason to build the query, but it isn't necessary. The setProductList method just takes List<Product> as its input, so you could just do something like this:

List<String> skuList = Arrays.asList(SKU_POTION, SKU_SWORD, SKU_BOW);

ArrayList<Product> productList = new ArrayList<>();
for(String sku : skuList) {
    productList.add(
        Product.newBuilder()
            .setProductId(sku)
            .setProductType(BillingClient.ProductType.SUBS)
            .build()
    );
}

QueryProductDetailsParams params = QueryProductDetailsParams.newBuilder()
        .setProductList(productList)
        .build();

billingClient.queryProductDetailsAsync(params, new ProductDetailsResponseListener() {
    public void onProductDetailsResponse(BillingResult billingResult, List<ProductDetails> productDetailsList) {
        // handle response
    }
}

Anything that implements the List interface will work - ArrayList, ImmutableList, etc...

UPDATE

If you want to query a mix of INAPP and SUBS types, you can't do this with a single call. However, you can use flows to combine the two async calls and run them at the same time (faster than running them one-at-a-time and easier to deal with the results)

First, make a helper function to return a flow for each call to queryProductDetails (this requires the billing -ktx extension in gradle)

private fun getDetailsFlow(productIds: List<String>, type: String) : Flow<List<ProductDetails>> {

    val productList = productIds.map { productId ->
        QueryProductDetailsParams.Product.newBuilder()
            .setProductId(productId)
            .setProductType(type)
            .build()
    }

    val params = QueryProductDetailsParams.newBuilder().setProductList(productList).build()

    return flow {
        emit(billingClient?.queryProductDetails(params))
    }.map { result ->
        result?.productDetailsList ?: emptyList()
    }
}

Then zip together two flows to get a single list of products as a result.

fun queryProductDetails() {

    val inAppFlow = getDetailsFlow(listOf(Product.GOLD, Product.SILVER), BillingClient.ProductType.INAPP)
    val subsFlow = getDetailsFlow(listOf(Product.UNLIMITED), BillingClient.ProductType.SUBS)

    val allDetailsFlow = inAppFlow.zip(subsFlow) { inAppResult, subsResult ->
        return@zip inAppResult + subsResult
    }

    scope.launch {
        allDetailsFlow.collect { productList ->
            // handle the unified list of all products, subscription and in-app
        }
    }
}
Tyler V
  • 9,694
  • 3
  • 26
  • 52
  • 1
    Great answer just please note that `BillingClient.ProductType.SUBS` is actually `BillingClient.ProductType.INAPP` if you would like to query in app purchase products and not subscirptions. – Adam Varhegyi Feb 22 '23 at 12:48
  • I was facing this same issue. But your answer help me a lot. Great answer – K. Donon Jun 14 '23 at 21:58
5

For multiple products:

ImmutableList<QueryProductDetailsParams.Product> productList = ImmutableList.from(
QueryProductDetailsParams.Product.newBuilder()
                    .setProductId(SKU_POTION)
                    .setProductType(BillingClient.ProductType.INAPP)
                    .build(),
QueryProductDetailsParams.Product.newBuilder()
                    .setProductId(SKU_SWORD)
                    .setProductType(BillingClient.ProductType.INAPP)
                    .build(),
QueryProductDetailsParams.Product.newBuilder()
                    .setProductId(SKU_BOW)
                    .setProductType(BillingClient.ProductType.INAPP)
                    .build());
Androidcoder
  • 4,389
  • 5
  • 35
  • 50
  • 3
    Why do product types need to be the same? Why I can't query to both in apps and subs with a single query – tpbafk Aug 04 '22 at 11:51